May 02

Настало время возродить этот блог. За более чем полугодие на нем не появилось ни одной статьи и я наконец-то намерен прекратить этот “застой”. Да, по сравнению с временами, которые были раньше у меня стало существенно меньше времени, чтобы писать что-то большое и серьезное - работа, университет (если быть точнее - то написание диплома) и еще один “секретный сайд проект” о котором, возможно, я когда-нибудь всем расскажу.

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

Ну, во-первых, я теперь не пишу на PHP. Вообще не пишу. И не очень сильно слежу за последними веяниями моды, связанными с этим языком. Да, я все равно иногда появляюсь в группе симфонистов (кому нужен доступ туда - пишите, там новичков любят и на многие нубские вопросы отвечают :) ). Да, я иногда посматриваю на фреймворки, которые сейчас являются популярными в сфере PHP - всякие Yii, ZF, etc. Посматриваю исключительно для того, чтобы быть в курсе происходящего. Учить их и писать на них в общем-то не очень хочется.

На что я променял PHP? Естественно на Ruby. Я не восхищаюсь этим языком, не превозношу его над остальными, просто на работе я пишу на нем (кстати, я работаю над стартапом для бизнесов - WaysGo, запуск будет, я надеюсь, очень скоро) и ни на чем другом желания писать особо не возникает - с точки зрения веб-разработки Ruby меня полностью устраивает. Этот язык удобен и достаточно экспрессивен. А Ruby On Rails - отличное средство для тех, кто хочет писать веб-приложения. Ну, в общем, вы меня поняли. ;)

Кстати говоря, я немного соврал насчет того, что я пишу на Ruby на работе. С начала этой недели я пишу iPhone-приложение на Objective-C и, в связи с этим, шустренько осваиваю Mac, Xcode, сам Objective-C и всякую прочую фигню, которая нужна для разработки под iPhone. Определенного мнения по поводу мака я пока сказать не могу, а вот Objective-C с первого подхода отличается похожестью на руби с точки зрения вызовов методов у объектов и открытых классов, но в то же время отличается несколько некрасивым синтаксисом (квадратные скобки, рррррр). С другой стороны, к синтаксису Ruby у меня тоже изначально было плохое отношение. Это отношение, кстати, не изменилось - я по прежнему считаю, что Ruby - непонятный язык для новичка, и если код на, например, Python можно просто читать, то для того, чтобы почитать код на Ruby придется сначала посмотреть туториалы, или что-то в этом роде, потому что синтаксис кроме выразительности отличается еще и неочевидностью. После пары дней работы с Ruby проблемы отпадают сами собой, так что этот аспект меня уже не очень беспокоит. Собственно, я надеюсь, что рано или поздно я перестану замечать синтаксис Objective-C и буду просто писать код, который делает что мне нужно.

Вот, собственно, и все на сегодня. Вроде ничего не забыл. До скорого! :)

written by fxposter \\ tags: , , , , ,

Mar 10

В сообществе симфонистов праздник - Фабьен наконец-то рассказал о том, что можно ожидать от следующей мажорной версии фреймворка:

IOC-контейнер

В презентации это называлось Dependency Injection Container. Подробнее о том, что это такое можно узнать в википедии: IOC, Dependency Injection. Либо спросите знакомых Java EE программистов, они должны знать, что это такое. :) За примерами лучше, опять же, обращаться к Java: Pico Container Introduction (достаточно просто и понятно), Spring IOC-container. Вкратце - скармливаем контейнеру классы и зависимости между ними и можем строить новые обьекты, которые будут построены на этих зависимостях (если у вам один обьект зависит от двух других, то они будут в него автоматически вставлены - через конструктор, методы, свойства, etc.). На PHP я IOC-контейнера ни разу не видел (хотя они есть) и… Не знаю, насколько он будет действительно удобен и нужен. Будем смотреть.

Новый шаблонный движок

Лучше смотреть презентацию - всё полностью переписано, много новых возможностей: шаблоны теперь не только file-based, но и memory-based (memcache, apc), database-based и т.д, появилось наследование шаблонов (привет, Django), все возможности предыдущих версий (типа partials, slots), думаю, останутся. Всё это будет приправлено отсутствием зависимостей от самой Symfony (как я понимаю, под “Independent library” они имеют ввиду именно это).

sfRequestHandler

Коротко и ясно - “Rails Metal in Symfony”.

Кстати говоря, довольно интересен тот факт, что засуетились все только сейчас, а ведь это не первая презентация о возможностях, которые нам приподнесут в Symfony 2.0 - на Symfony Camp 2008 об этом уже говорили. :)

written by fxposter \\ tags:

Feb 07

Под “скрытыми” записями сегодня будут пониматься “unapproved”-записи в таблице. Кому лень ходить по сылкам: иногда не все записи какой-нибудь таблицы нужно показывать пользователю, например - если я не хочу показывать некоторые посты в блоге. Обычно для этого я делаю поле, например, is_hidden, а затем выбираю все записи, где is_hidden = 0. Проблема состоит в том, что я обычный человек и могу забыть поставить нужное мне условие. Поэтому я хочу получить какое-нибудь простое, но очень эффективное решение такой проблемы. В ActiveRecord этим решением является default_scope. А я вам сегодня расскажу, как этого добиться в Doctrine.

Итак, представим, что у нас есть табличка Post:

Post:
  actAs:
    Timestampable: ~
  columns:
    title:     { type: string(128), notnull: true }
    text:      { type: text, notnull: true }
    is_hidden: { type: boolean, notnull: true, default: 0 }

Самый простой, на первый взгляд, подход - переопределять PostTable::createQuery(), чтобы этот метод возвращал уже Query с нужным нам “WHERE is_hidden = 0″. К сожалению, не всегда это помогает. Например, при выборке постов для какой-либо категории через $category->Posts этот метод не сработает.

Есть гораздо более простой способ сделать то, что нам нужно - использовать listener-ы. В Doctrine есть довольно много событий, которые мы можем “слушать” и на которые мы можем реагировать. В данном случае нам подходит событие “preDqlSelect”, которое входит в группу “DQL Hooks“, и которое вызывается перед выполнением запроса на выборку записей. Как нам нужно прореагировать на событие: взять Doctrine_Query из Doctrine_Event и добавить в него дополнительные условия выборки.

Самый простой способ - переопределить метод preDqlSelect в самой записи:

class Post extends BasePost
{
  public function preDqlSelect(Doctrine_Event $event)
  {
    $params = $event->getParams();
    $event->getQuery()->addWhere("{$params['alias']}.is_hidden = 0");
  }
}

В первой строке метода мы получаем параметры запроса, из которых нам нужен alias - можете считать это обычным alias-ом таблицы из SQL (в данном случае это alias таблицы в DQL), т.е. при таком DQL:

FROM Post p

alias-ом будет “p”.

Во второй строке мы получаем текущую query и добавляем в неё условие “is_hidden = 0″.

Мы не можем использовать ->where(), т.к. этот метод сотрет все имеющиеся части WHERE в запросе. В то же время ->addWhere и ->andWhere ведут себя так же, как и ->where при отсутствии where-части запроса.

Собственно, вот и всё - тепер у нас будут выбираться только “видимые” посты.

Во второй части будет показано, как сделать созданный в этом посте код более реюзабельным, а также как сделать так, чтобы в backend-е посты показывались полностью.

written by fxposter \\ tags: ,

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: ,

Aug 26

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

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

written by fxposter \\ tags: , ,

Aug 26

На Хабре появилась замечательная статья “Наследование шаблонов в Smarty“. Впервые эта штука мне встретилась в Django, теперь вот есть и для Smarty. Посмотрел на систему view в Symfony… Эх… Для реализации подобного, навскидку, прийдется довольно сильно переделать внутренности этого фреймворка.

written by fxposter \\ tags: , , ,

Aug 20

На днях вышла долгожданная новая версия достаточно популярного в узких кругах ORM - Propel 1.3.

Из интересных мне нововведений:

  • В основе Propel 1.3 лежит новый механизм работы с базами данных - PDO, который заменил Creole и привел к ощутимому повышению скорости работы.
  • Теперь не нужно вручную загружать классы Propel и свои модели - теперь это делается автоматически (через autoloading в PHP)
  • “Object Instance Pooling”, или, как его называет небезызвестный Фаулер - Identity Map. Причем, что самое интересное, это работает не только с retrieveByPk(), но и со всему doSelect*()-методами (в зависимости от переданных аргументов), что позволяет ускорить работу засчет отсутствия лишних запросов к бд, а также ненужности построения одинаковых обьектов несколько раз.
  • Связи один-к-одному теперь поддерживаются нативно (блин, хочу много-ко-многим!)
  • Куча мелких (и не очень) исправлений и улучшений

Поподробнее можно прочитать на RedoTheWeb, в также на офсайте Propel.

PS. Сам еще не юзал, но нужно будет попробовать. На мой взгляд, самый интересный момент связан с тем, можно ли переопределить фетчинг записей, например - выбирать только записи с is_hidden = false, да так, чтобы переопределять пришлось только одну функцию.

Update: Как написали на официальном блоге Symfony - “Object Instance Pooling is not an Identity Map because the database request is still needed”. Нужно будет изучить этот вопрос.

written by fxposter \\ tags: , ,

Aug 17

Если вы используете Symfony, планируете это делать или просто интересуетесь этим фреймворком - значит нам нужно поговорить. Мои контакты:

  • ICQ: 625585
  • Jabber: fxposter@gmail.com
  • Skype: fxposter

written by fxposter \\ tags: ,

Jul 20

Собственно, о том, что вышла Symfony 1.1 писали многие, и вы об этом наверняка уже слышали. Но для тех, кто об этом еще не знает, повторю нововведения, а также выскажу о них своё [авторитетное :)] мнение:

  • Новая архитектура. Явное улучшение - компоненты теперь меньше зависят друг от друга.
  • Новая система конфигурирования. К счастью, конфигурация через YAML-файлы никуда не делась. Просто сама структура конфигов переделана.
  • “The new object-oriented form framework“. Звучит действительно круто. А смысл такой - генерация форм динамически, вместо статических хелперов (которые, кстати, остались), а также - вся работа с формами (указание полей, их валидаторов, настройка свойств показа формы и прочее)  теперь делается в одном месте, что очень удобно. Есть интеграция форм с Propel, например - автоматическая генерация форм по модели.
  • Новая система консольных комманд. Честно сказать - если вы не разрабатываете плагины, то разницу вы увидите только в том, что некоторые комманды поменяли своё название.
  • Новый парсер YAML. Разве что изменили выдачу инфы об ошибках. Ну и кое-что добавили. Как по мне - для конечного девелопера разница очень мала.
  • Теперь плагины - это обычные пакеты PEAR, со всеми вытекающими последствиями, из которых главным, на мой взгляд, является возможность указаниязависимостей от других плагинов.
  • Теперь можно выдавать различные вьюхи в зависимости от того, какоt значение принимает хедер Accept. Поподробнее - здесь.
  • Собствено, ORM теперь полностью вынесен в плагины. Symfony 1.0 содержала в себе Propel 1.2, и была возможность заменить его на Propel 1.3/Doctrine через плагины. Теперь же и Propel 1.2 вынесен в отдельный плагин. Скорее всего эта возможность пришла вследствии пункта №1.
  • Для того, чтобы приложения, написанные на Symfony 1.0 запускались на версии 1.1 был сделан sfCompat10Plugin, который, насколько я понял, в версии 1.2 уберут совсем. К сожалению, проекты всё таки прийдется дорабатывать, чтобы они работали на Symfony 1.1, подробнее об этом можно прочесть здесь.
  • Переписан класс routing’а. Теперь все пути кешируются + от этого класса можно отнаследоваться и переделать, от чего бы я не отказался.

А теперь посмотрите и скажите - что из этого может реально пригодится? ИМХО:

  • Form Framework
  • Зависимости в плагинах
  • Различные вьюхи в зависимости от того, какоt значение принимает хедер Accept

Причем последние два пункта лично я пока не использовал бы нигде. Просто нет необходимости. К чему это я клоню - этот релиз чуть ли не официально был признан “промежуточным” - минимум новых интересных фич и очень сильная переработка кода, причем зачастую внутреннего, для облегчения дальнейшей поддержки и добавления новых фич в будущем. В частности - в версии 1.2 нововведений меньше, но они гораздо больше коснуться конечных пользователей фреймворка (коснуться - в хорошем смысле). Кстати говоря, версия 1.2 выйдет уже в октябре, через 3 месяца… Но это уже несколько друга история, о которой мы поговорим, скорее всего завтра.

written by fxposter \\ tags: ,