Контент


Развитие Jelly ORM

Многие заметили, что развитие многообещающего ORM под названием Jelly подзадержалось (последний релиз был еще весной, с тех пор транк разработки содержит только небольшие летние багфиксы. Казалось бы, такой сильный проект загнулся? Как бы не так! Бравые ребята Jonathan Geiger и Paul Banks ушли в подполье и работают в поте лица, просто результатов работы так просто не видать…

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

Базовые изменения

  • Имена полей теперь должны начинаться с префикса Jelly_, т.е. если раньше было Field_BelongsTo, то теперь надо писать Jelly_Field_BelongsTo. Вполне логично, резко уменьшаем вероятность коллизий с другими подключаемыми модулями.
  • Метода Jelly::select() теперь нет. Его задачи разделены между Jelly::factory() и новым методом Jelly::query(). Первый теперь отвечает за создание пустой модели, плюс имеется возможность передать туда ключ для непосредственной загрузки нужной записи. Тут все стало как в ORM. Для всех остальных действий, связанных с записями в БД, используется Jelly::query(). Он возвращает объект типа Jelly_Builder.
    Например, можно удалить несколько записей с учетом примененных условий:

    // дело происходит внутри модели, поэтому передаем $this
    // а можно передать имя модели к примеру
    Jelly::query($this)
            ->where('expires', '<', time())
            ->delete();

    Впрочем, многие наверняка захотят заменить вызов ->where(…) на более элегантное ->expired(), используя расширение объекта Jelly_Builder.

События и Поведения (Events & Behaviors)

Ага, теперь есть набор заранее определенных событий, возникающих при стандартных действиях (сохранение записей, инициализация моделей и т.д.). Полный список на данный момент такой:

  • builder.call_<метод>
  • builder.before_select
  • builder.after_select
  • builder.before_insert
  • builder.after_insert
  • builder.before_update
  • builder.after_update
  • builder.before_delete
  • builder.after_delete
  • meta.before_finalize
  • meta.after_finalize
  • model.call_<метод>
  • model.before_validate
  • model.after_validate
  • model.before_save
  • model.after_save
  • model.before_delete
  • model.after_delete

Имя события состоит из объекта-инициатора (модель, Jelly_Builder, Jelly_Meta) и собственно описания этого события (как правило это префикс before_ или after_, и само событие). Чтобы обрабатывать эти события, используется еще одно понятие — поведение модели (behavior), реализовываемое классом Jelly_Behavior.

Сперва создается собственно класс, описывающий поведение модели. Например, для Model_User он будет называться Model_User_Behavior (на самом деле имя может быть практически любое):

class Model_User_Behavior extends Jelly_Behavior {
 
	public function model_before_save($user, $event)
	{
             // тут делаем свои проверки, можно писать логи и т.д.
	}
}

Имя метода зависит от имени нужного события (просто заменяем точку на подчеркивание). Параметры тоже могут различаться. Но первый — обязательно сама модель, а последний — событие (точнее, не Jelly_Event, а Jelly_Event_Data). Модель нам нужна для получения «пациента», а событие — для обратной связи (влияние на работу самого события и инициировавшего его процесса).

Если что-то нас не устраивает (данные в модели не подходят для сохранения), устанавливаем параметр $event->return в FALSE, а $event->stop в TRUE. Свойство return содержит результат работы события (успешным считается любое не-FALSE значение), а stop означает, что дальше это событие отрабатываться не будет (да-да, можно вешать несколько обработчиков-поведений на одну модель).

Помните, что просто установить свойство return в FALSE может быть недостаточно, т.к. последующие обработчики это значение могут запросто затереть.

Подключать обработчики надо, естественно, в методе initialize($meta):

$meta->behaviors(array(
   'user'  => new Model_User_Behavior, 
));

Ветка behaviors развивалась еще весной, но сейчас там код, сильно отличающийся от unstable, поэтому я его отбросил. Хотя там были интересные реализации, например SoftDelete для «мягкого» удаления записей в БД.

Поведения довольно удобная штука, например я за полчаса добавил возможности MPTT из старой своей библиотеки через них. Собственно, разработчики модуля в планах ставят создание поведений для MPTT и ClosureTable (такой способ хранения данных, когда все дерево хранится отдельно в виде пары предок-узел, полностью описывающих всех предков узла до самого корня).

Изменения в валидации

Валидация в модели теперь осуществляется своими силами. В модуле появился целый набор классов: Jelly_Validator, Jelly_Validator_Rule, Jelly_Validator_Callback и Jelly_Validator_Filter. Соответственно валидация осуществляется методом validate(), а сам валидатор получить можно через validator($data), где $data — необязательный массив данных для проверки (по умолчанию данные берутся из модели).

Появилось понятие «контекст» — это что-то вроде псевдонима для объекта или какого-то значения. Например, при создании модели автоматически создается контекст ‘:key‘, содержащий значение ключа, и ‘:model‘, содержащий ссылку на текущую модель. Вот пример того, как можно использовать контекст :model:

// поле test, к которому мы хотим добавить callback
'test'   => new Jelly_Field_String(
   'callbacks' => array(
         array(array(':model', 'test'), array(':validate', ':callback', ':field', ':value')),
   )
),


Теперь при валидации поля будет осуществлен вызов метода test() текущей модели (контекст :model) и туда будут переданы объект Jelly_Validator (контекст :validate), текущий объект Jelly_Validator_Callback (:callback), название проверяемого поля и его значение поля (контексты :field и :value). Эти контексты создаются автоматически при проверке каждого поля. Ну и конечно же, можно задавать свои контексты, используя методы context() и contexts() валидатора модели.

Прочие мелочи

Добавился класс Jelly_Field_Image. Помимо просто загрузки и сохранения изображения он позволяет сразу создавать превьюшки (указываются в параметрах поля). Можете почитать вот тут поподробнее.
В Jelly_Builder‘е появился метод key($value), который является сокращением от ->where($this->unique_key($value), '=', $value), т.е. позволяет быстро добавить условие по ключу. Удобно.
Также там добавился метод type($type). С его помощью можно на лету менять тип запроса (Database::SELECT, Database::INSERT и т.д.).

В моделях появлась возможность сбросить все изменения (вернуться к первоначально загруженному состоянию), с помощью метода revert().

Неразобранное

  • Встречал в нескольких местах упоминание поля Jelly_Field_Polymorphic и некоего ключа polymorphic_key. Но вроде бы это все еще не реализовано, работы судя по всему велись/ведутся ветке Polymorphic. Либо часть реализации будет лежать на плечах пользователей модуля (?).
  • Скорее всего для этих же целей вводится понятие «дочерних» моделей (children). Класс Jelly_Meta позволяет устанавливать и получать дочерние модели (названия классов) через метод children(). При этом поля, являющиеся Jelly_Field_Polymorphic (они по идее и должны содержать имена моделей), автоматически
    добавляют свои значение в свойство $_children.

Процесс идет…

Интересностей много. Многое еще наверняка скрыто в недрах этого замечательного модуля. Тем не менее, он выглядит вполне работоспособным.

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в Kohana3.

Теги: , , , , .


Комментарии (18)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. ukko пишет:

    Спасибо за обзор.

    Иван, как впечатления о стабильности нестабильной ветки? :)

    Можно ли начинать его использовать в боевых проектах? Т.е. понятно что всё ещё сыро, но может это уже более доработано чем 0.9.x ?

  2. Alexander Kupreev пишет:

    молодцы!

    встроенное MPTT и ClosureTable реально интересно

  3. biakaveron пишет:

    @ukko
    В ближайшее время планирую поглубже потестировать. Но уже одно то, что Jelly_Field_Expression работает — вдохновляет ;)

  4. avis пишет:

    Попробовал перевести пишущийся в настоящее время проект на unstable jelly и понял, что нужно переписывать очень многое… :(
    Снахрапа что-то одолеть не удалось. Попробую чуть попозже. Самому Jelly_Field_Expression позарез необходим, да и Behaviors задействовал бы без текущих харкодных расширений билдера и модели для внедрения умного кэширования.

  5. ukko пишет:

    @avis тоже привязывался к формогенерации?

  6. avis пишет:

    @ukko если речь про fileds() методы meta класса, то это не было проблемой: файнд-реплейс решил проблему.
    У меня после перехода вылезла ошибка, сообщающая об отсутствии метода trigger() в объекта класса event при вызове в конструкциях вида

    $meta->events()->trigger();


    Например, в методе __call() в Jelly_Model.
    Пришлось расширять все проблемные методы в классах и комментировать эти триггеры. Пока не успел разобраться: это оттого, что что-то сделали и забыли поправить, либо тупо не проверив внедрили код, а потом забыли. Или же проблемы у меня в коде (хотя, я в здравом уме и ясной памяти: не помню, чтобы я в в предыдущей реализации кода, пользующего master Jelly что-то творил с классом Event’ов… :) ).

  7. biakaveron пишет:

    @avis
    Наверное надо проверить, что возвращает $meta->events(). Может там тупо NULL или что-то в этом роде. По идее триггеры у них отрабатывают (тестил не по полной правда) нормально.

  8. avis пишет:

    Проверял. NULL, кажется, и был. Вопрос в другом — почему в core методах нет обработки ошибок этого…
    Уже полностью перевёл проект на unstable, сейчас попробую поправить ситуацию с триггерами без комментирования их работы.

  9. avis пишет:

    Вернее, не NULL, а пустой массив был.

  10. avis пишет:

    Плюс ко всему, мне казалось, что $meta->events() должен вернуть объект, так как в $meta->finalize() чётко написано

    // Set up event system
    $this->_events = new Jelly_Event($model);

  11. biakaveron пишет:

    Ага, и больше он вроде нигде не меняется…

  12. ukko пишет:

    Ребята, а что скажете всё таки насчёт развития Jelly?

    Я смотрю запрос Ивана на пул уже с месяц висит, и почти месяц назад был последний коммит. Так ли уж идёт развитие желе?

    Иван, ты же на передовой, что слышно с твоей стороны?

    PS Очень много думаю над прикручиванием doctrine2, и всё жду.. Может таки Jelly, зачем чужеродный код приплетать?

  13. avis пишет:

    По мне так вяленько пошла разработка как Джонатан ушёл в отпуск.
    Однако многие моменты в реализации ожидают релиза Kohana 3.1. Та же валидация (чтобы не городить собственный велосипед, а использовать оригинальный модуль). Чую до этого момента (релиза 3.1) развитие модуля будет идти черепаховыми темпами. Однако, уже в текущей unstable имеется куча бонусов (как и исключение таких плюшек как формогенерация, которую, как я понял, вынесут отдельным модулем (?)), которые при должной сноровке покрывают большое количество задач.

    Ну а как будет 3.1, там поглядим. Конкурирующие ОРМ библиотеки наступают на пятки. Пока не понятно, воодушевит ли это разработчиков, либо наоборот ввергнет в апатию…

  14. xbagir пишет:

    Что-то мне подсказывает, что будет совсем плохо с производительностью Jelly и объемом потребляемых ресурсов, из за такого колличества ‘cлоев’ кода.

    Пока родная ОРМ пошустрее в скорости, но топорнее в работе.

  15. xbagir пишет:

    avis
    *Конкурирующие ОРМ библиотеки наступают на пятки.*

    А конкурируещие, это Hive или еще что-то интересное есть?

  16. CHEZCH пишет:

    Я так и не понял. Как здесь вставить новую строку в базу.
    Я новичок в ОРМ. Может кто-что подскажет?

  17. biakaveron пишет:

    @CHEZCH
    Вот тут описаны базовые действия с моделями Jelly.

Продолжение обсуждения

  1. Встречайте – Kohana 3.1 RC1! | Изучаем Web ссылается на эту запись on 10 января 2011

    [...] аналогичен тому, что я недавно описывал в статье Развитие Jelly. Метод bind($key, $value) позволяет связать между собой некие [...]



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.