May 01

Давно не писал здесь ничего. В какой-то степени это связано с тем, что в последнее время навалилось много дел - как по учебе, так и по работе (кстати, теперь я работаю в Dimalex). С другой стороны - на той же работе я сейчас занимаюсь не написанием кода, а разработкой требований, написанием ТЗ, проектированием архитектуры (Kpumuk, aleks raiden - вам отдельное спасибо за подсказки и ответы на мои глупые вопросы :)). Так что… Интересных постов о том, о чем я писал раньше - PHP, Symfony, ZF, etc. Тут, скорее всего до лета не будет. Зато, возможно, будет что-то о Hadoop и параллельной обработке большого количества данных… Не знаю еще. Мне многому предстоит научиться, т.к. опыта разработки высокопроизводительных приложений я не имею, но как раз этому я очень рад.

Но именно сейчас это не главное, т.к. сегодня вечером (часиков, эдак, через 8) я уже буду на поезде, который повезет меня с моей девушкой в Крым. В планах - посетить ботанический сад, посмотреть на море и просто отдохнуть душой и телом, а вечером - на поезд обратно.

Всех с праздничком! :)

written by fxposter \\ tags: ,

Feb 06

Собственно, сабж.

Не спрашивайте - почему и из-за чего. Сам виноват. :)

Но ничего! Что не делается - то к лучшему. Теперь у меня будет больше свободного времени, которое я с удовольствием потрачу на себя. В планах:

  • Все-таки прочесть “Рефакторинг” Фаулера и “Code Complete” МакКоннелла (хотя некто говорит, что он очень нудно пишет)
  • Поближе познакомиться с Ruby/Rails
  • Прочесть что-нибудь по .NET, ASP.NET и поюзать ASP.NET MVC

Если у вас есть какие-то пожелания насчет блога (например, хотите увидеть статью по какой-нибудь тематике) - пишите в комментах!

PS. Хотя, надеюсь, в скором времени для меня найдется работа.

written by fxposter

Jan 31

Есть в Symfony такая штука, как генератор админки на основании описанных моделей. Подробно о самом генераторе админки лучше читать здесь (кстати, я не понял, а про propel:generate-module теперь в Symfony Book не рассказывается ничего, что ли?).

Я вчера столкнулся с багом, который ошибочно описал здесь, а затем правильно - здесь. Кто не хочет читать мой кривой английский - читаем дальше. Баг воспроизводится при наличии трех условий:

  1. класс myUser наседуется от sfGuardSecurityUser из плагина sfDoctrineGuardPlugin
  2. для всего приложения (или только для модуля админки) отключена проверка безопасности (is_secure: off, в общем)
  3. пользователь не авторизован

В этом случае вы получите вот это сообщение:

You don’t have the required permission to access this page.

Решение описано в первом моём тикете:

Нужно заменить в файле “<путь к библиотекам symfony>/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin/template/actions/actions.class.php” (это шаблоны для генератора админки Doctrine) эти строки:

if (!$this->getUser()->hasCredential($this->configuration->getCredentials($this->getActionName())))
{
  $this->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action'));
}

на эти:

credentials = $this->configuration->getCredentials($this->getActionName());
if (!empty($credentials) && !$this->getUser()->hasCredential($credentials))
{
  $this->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action'));
}

Баг некритичный, так что можно, в принципе, от него не избавляться, а подождать, пока изменения внесут в главный репозиторий. Просто если встретите его - не удивляйтесь. Лично я долго не мог понять - почему у меня не работает is_secure: off.

written by fxposter \\ tags: , ,

Jan 31

Последнюю неделю работаю с Symfony 1.2 и Doctrine. Так вот, если раньше я хвалил Doctrine, то теперь… В общем, после нескольких дней работы с ней захотелось плеваться… На первый взгляд всё замечательно, но как только начинаешь копать глубже начинается ужас. Мне очень не хочется рассказывать про эти баги и недоделки (причина банальна - просто лень вспоминать, выискивать по истории icq/jabber о всех багах, которые я нашел). У меня было не очень хорошее впечатление о Propel, но, по крайней мере, когда я работал с ним, у меня была уверенность, что всё будет работать и ничего не сломается, если я что-то добавлю/изменю. С Doctrine такой уверенности лично у меня нет (хотя коллеги тут подсказывают, что всё наладится :)) - такое впечатление, что “еще чуть-чуть” - и все развалится. Это мнение исключительно субьективное, обьяснять я его не буду, если кто-то что-то желает узнать - прошу в icq/jabber, все контакты есть в сайдбаре моего блога.

Напоследок всё-же хочется привести пример одной из “недоделок”, которую при хорошем коде можно было бы легко исправить:

Имеем следующий код:

// Category has many Posts
$category = Doctrine::getTable('Category')->find(1);
$posts = $category->Posts;

Задача состоит в том, чтобы в $posts были посты, отсортированным по какому-нибудь полю бд. Причем, естественно, хочется, чтобы посты были отсортированы не только про вот такой их выборке через LazyLoading, но и при Eager Loading, т.е.:

$category = Doctrine::getTable('Category')->createQuery('c')->leftJoin('c.Posts')->fetchOne(); // выборка всех данных одним запросом
$posts = $category->Posts; // запроса к бд не проиходит

На самом деле, про Eager Loading я с самого начала не думал, но, я думаю, со мной никто спорить не будет, что если сортировка должна быть в одном случае - она обязанна быть и во втором.

Так вот - эту проблему в доктрине приходится решать вручную для каждого поля, причем кешование данных тоже приходится хендлить самому.

При вызове свойства $category->Posts, будет вызываться метод $category->getPosts(), который мы и будем переопределять:

class Category extends BaseCategory
{
  public function getPosts() // в идеале сюда должен передаваться параметр $load, но пока о нем забудем
  {
    return Doctrine::getTable('Post')
        ->createQuery()
          ->orderBy('column ASC')
          ->where('category_id = ?', $this->id)
        ->execute();
  }
}

Красиво? Как по мне - не очень, т.к. несмотря на наличие уже указанной связи - использовать её я не могу, приходится строить query заново.

Возможно, связью я воспользоваться могу, об этом нужно подумать… Пока писал пост пришла в голову идея, но проверять лень.

Запускаем. Пробуем:

$category = Doctrine::getTable('Category')->find(1);
$posts = $category->Posts;
$posts = $category->Posts;
$posts = $category->Posts;
$posts = $category->Posts;

И что мы видим? 5 запросов к бд. Хмм. Убираем метод getPosts. Запускаем тот же код. В итоге - два запроса. Я, конечно, понимаю, что это я такой плохой и лентяй еще к тому же, но ведь можно было сделать подобное “кеширование” для связей автоматически (если кто знает, как это сделать - дайте знать, походя дофига времени по кодам доктрины я не нашел место, где это можно сделать). Ладно, сделаем “кеширование”:

public function getPosts()
{
  if (!isset($this->_references['Posts']))
    $this->_references['Posts'] = Doctrine::getTable('Post')
        ->createQuery()
          ->orderBy('column ASC')
          ->where('category_id = ?', $this->id)
        ->execute();
  return $this->_references['Posts'];
}

Запускаем уже запомнившийся нам код. Ну слава богу - два запроса.

На самом деле нужно еще кое-что переопределять (типа Category::loadReference), но я на это забил, т.к. вряд ли я буду вызывать этот метод вручную. А его вызов системой мне, вроде бы не встречался.

Ну, вроде более-менее готово. По крайней мере - можно пользоваться, а нам только это и нужно.

Теперь об Eager Loading. О нем я сам даже и не вспоминал, т.к. у меня не было ни одной идеи - как его реализовать (нужно было опять копаться в исходниках Doctrine, а это мне уже, порядком, поднадоело). Как вдруг, откуда ни возьмись, появился пост в блоге доктрины: Cookbook Recipe: Relation DQL Behavior. Советую прочесть его и комментарии (благо, их там немного). Вы увидите в комментариях и меня, говорящим спасибо за Eager Loading, а также указавшим на то, что в случае Lazy Loading-а решение не работает. На что мне ответили:

You’re right the above won’t work, but you shouldn’t ever be doing that :) You should always load your data through full DQL queries and avoid lazy loading data

Ага. Благодарю покорно. Зачем тогда вообще делать Lazy Loading, если его не нужно юзать. Может, мне банально удобнее написать $category->Posts и получить посты тогда, когда это реально нужно, а не при выборке категории. Да, я получу два запроса вместо одного, ну и что? Это не O(n), а я не привык оптимизировать то, что нормально работает и так.

И напоследок: на самом деле проблему вполне можно было бы решить путем наследования от класса связи и его расширения. Однако сделать это в Doctrine сне не судилось, т.к. в отношениях hasOne, hasMany намертво зашиты стандартные классы связей Doctrine, а жаль. Такие связи - это плохо, т.к. они статичны и не способствуют хорошему расширению системы. С другой стороны - не мне критиковать jwage, самбы я вряд ли сделал что-то подобное, по крайней мере сейчас.

PS. Долго думал, как назвать пост. Так и не придумал… :(

written by fxposter \\ tags: ,

Jan 19

Впечатления от ОС положительные - работает быстро, памяти кушает меньше чем Vista (~600Mb против ~1Gb), интересный таскбар (хотя и некоторые моменты в нем не нравятся, а именно работа с сгруппироваными окошками), большинство програм установилось и вполне себе нормально работают. Из недостатков могу выделить нерабочий IE6 (IETester работает, но табы с IE6 показывать отказывается), а таже невозможность установить VMWare Workstation или VirtualBox (обе программы банально не хотят устанавливаться).

Если кто-нибудь знает, как на Windows 7 поставить IE6 или как сюда поставить какую-нибудь виртуальную машину - буду благодарен за подсказку.

PS.  Да, еще здесь очень тормознутый WMP. Не знаю, с чем это связано, но 50% процессора при работе он кушает стабильно (я так понимаю, что по 50% имеется ввиду 100% одного ядра, т.к. проессор двухядерный).

written by fxposter \\ tags:

Dec 31

С Новым Годом!

Удачки! :)

written by fxposter

Nov 28

Да, да, я знаю, что в наш век все уже давно перешли на использование utf-8 при построении веб-сайтов, но остались еще и такие, которые пользуються “допотопными”, “отстойными”, и вообще “не-тру” кодировками типа CP1251 (также известной как Windows-1251), KOI8-R и прочих.

Ни для кого ни секрет, что кодировкой получаемых через Ajax данных по-умолчанию принимается UTF-8. Не все знают, что это умолчание можно изменить, указав кодировку явно через заголовок “Content-Type: text/html; charset=cp1251″. Сегодня вот столкнулся с интересным багом, на который убил довольно много времени.

Сервер отдает некоторый текст по запросу (ajax) в кодировке CP1251, предварительно установив соответствующий заголовок - “Content-Type: text/html; charset=cp1251″. На клиенте использовалась библиотека jQuery:

$('#books').load('books.php', {action: 'list'});

Всё работало замечательно… До тех пор, пока я не открыл страничку в IE7. Как ни странно, но ничего не загрузилось. После длительных раздумий, установки alert-ов куда нужно, и куда нет код был заменен следующим кодом:

$.ajax({
  method: 'get',
  url: 'books.php',
  success: function(data) {
    $('#books').html(data);
  },
  error: function(xhr, textStatus, errorThrown) {
    alert(textStatus);
  },
  data: {action: 'list'}
});

В FF всё продолжало работать, а вот IE начал выдавать странную надпись “parsererror”. Гугление никаких результатов не дало - ни одного похожего случая… После, примерно, 10 минут попыток “запустить” страничку в IE, что-то меня дернуло посмотреть на серверный код. Глаза сразу упали на строку установки Content-Type-а. Недолго думая, я поставил charset=utf-8, и, о чудо, IE загрузил всё как положено (правда в неправильной кодировке, но всё же - уже плюс). Догадавшись в чем может быть дело, я установил кодировку в “windows-1251″, после чего всё заработало на ура.

Собственно, мораль сей басни такова - юзайте, люди, юникод, и будет вам счастье.

written by fxposter \\ tags: , ,

Oct 13

Начинаю писать Data Mapper на PHP. На вопрос “почему” всего лишь 2 ответа - “хочется” и “нужен по работе простой, но удобный ORM”. Кто захочет присоединиться и в свободное время помогать мне его развивать - милости прошу.

На данный момент это всё находится в ОЧЕНЬ начальной стадии - ничего не работает, да и как будут работать некоторые вещи я еще не решил. Сейчас работа с бд строиться через mysqli, переход на PDO или поддержка других СУБД не планируется (но если кто-нибудь захочет - сделаем, там не так уж и много работы).

Текущее состояние можно будет посмотреть в SVN. Тем, кто захочет помочь, нужно будет зарегистрироваться на Assembla и сказать мне свой логин.

Буду ОЧЕНЬ рад обсудить технические стороны проекта, а также его нужность.

written by fxposter \\ tags: , ,

Aug 26

Вопрос, так, невзначай. Хотели бы вы аналог получить в Symfony аналог наследования шаблонов из Django?

PS. Палюсь… Ой как палюсь. :)

written by fxposter \\ tags: , ,

Aug 25

В Zend Framework всё замечательно! …пока не начинаешь его использовать на полную катушку…

Сегодня я просто приведу пример трех абсолютно различных синтаксисов, которые используются в Zend_Db для фетчинга, добавления и удаления записей.

В Zend_Db везде можно использовать array в качестве where, только нигде не сказано, как этот array должен выглядеть, а аналогий не прослеживается…

$productsTable = new Products();

Обычная вставка нового продукта:

$productsTable->insert(array(
    'name'  => 'Fucked Product!',
    'price' => 999.99,
  ));

Всё OK.

Поиск продукта по аналогии со вставкой:

$productsTable->fetchRow(array(
    'name'  => 'Fucked Product!',
    'price' => 999.99,
  ));

Выдаст SQL типа такого:

SELECT products.* FROM products WHERE (name) AND (price)

Что приведет к выдаче первого попавшегося продукта (что, естественно, нам не подходит).

Правильный вариант поиска продукта:

$productsTable->fetchRow(array(
    'name = ?'  => 'Fucked Product!',
    'price = ?' => 999.99,
  ));

Удаление продукта по аналогии с поиском:

$productsTable->delete(array(
    'product_id = ?' => 1000,
  ));

Выдаваемый SQL:

DELETE FROM products WHERE 1000

Приведет к удалению всех продуктов (sic!).

Причем самое интересное, что в случае со строкой - у вас будет ошибка, а вот в случае с числом, которое escape’ить не нужно - будут проблемы. Я с ними на днях столкнулся, и… получил пустую таблицу, в которой были важные данные. :( Благо, бэкап был.

Удаление продукта по аналогии со вставкой:

$productsTable->delete(array(
    'product_id' => 1000,
  ));

Полностью аналогично предыдущему случаю.

Правильный вариант удаления продукта:

$productsTable->delete(
    $productsTable->getAdapter()->quoteInto('product_id = ?', 1000)
  );

Вот вам и офигенный ZF. Если использовать его не постоянно, а изредка для отдельных задач - натыкаешься на подобные грабли.

written by fxposter \\ tags: ,