Многие заметили, что развитие многообещающего 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
.
Процесс идет…
Интересностей много. Многое еще наверняка скрыто в недрах этого замечательного модуля. Тем не менее, он выглядит вполне работоспособным.
Спасибо за обзор.
Иван, как впечатления о стабильности нестабильной ветки?
Можно ли начинать его использовать в боевых проектах? Т.е. понятно что всё ещё сыро, но может это уже более доработано чем 0.9.x ?
молодцы!
встроенное MPTT и ClosureTable реально интересно
@ukko
В ближайшее время планирую поглубже потестировать. Но уже одно то, что Jelly_Field_Expression работает — вдохновляет
Попробовал перевести пишущийся в настоящее время проект на unstable jelly и понял, что нужно переписывать очень многое…
Снахрапа что-то одолеть не удалось. Попробую чуть попозже. Самому Jelly_Field_Expression позарез необходим, да и Behaviors задействовал бы без текущих харкодных расширений билдера и модели для внедрения умного кэширования.
@avis тоже привязывался к формогенерации?
@ukko если речь про fileds() методы meta класса, то это не было проблемой: файнд-реплейс решил проблему.
У меня после перехода вылезла ошибка, сообщающая об отсутствии метода trigger() в объекта класса event при вызове в конструкциях вида
Например, в методе __call() в Jelly_Model.
Пришлось расширять все проблемные методы в классах и комментировать эти триггеры. Пока не успел разобраться: это оттого, что что-то сделали и забыли поправить, либо тупо не проверив внедрили код, а потом забыли. Или же проблемы у меня в коде (хотя, я в здравом уме и ясной памяти: не помню, чтобы я в в предыдущей реализации кода, пользующего master Jelly что-то творил с классом Event’ов… ).
@avis
Наверное надо проверить, что возвращает $meta->events(). Может там тупо NULL или что-то в этом роде. По идее триггеры у них отрабатывают (тестил не по полной правда) нормально.
Проверял. NULL, кажется, и был. Вопрос в другом — почему в core методах нет обработки ошибок этого…
Уже полностью перевёл проект на unstable, сейчас попробую поправить ситуацию с триггерами без комментирования их работы.
Вернее, не NULL, а пустой массив был.
Плюс ко всему, мне казалось, что $meta->events() должен вернуть объект, так как в $meta->finalize() чётко написано
// Set up event system
$this->_events = new Jelly_Event($model);
Ага, и больше он вроде нигде не меняется…
Ребята, а что скажете всё таки насчёт развития Jelly?
Я смотрю запрос Ивана на пул уже с месяц висит, и почти месяц назад был последний коммит. Так ли уж идёт развитие желе?
Иван, ты же на передовой, что слышно с твоей стороны?
PS Очень много думаю над прикручиванием doctrine2, и всё жду.. Может таки Jelly, зачем чужеродный код приплетать?
По мне так вяленько пошла разработка как Джонатан ушёл в отпуск.
Однако многие моменты в реализации ожидают релиза Kohana 3.1. Та же валидация (чтобы не городить собственный велосипед, а использовать оригинальный модуль). Чую до этого момента (релиза 3.1) развитие модуля будет идти черепаховыми темпами. Однако, уже в текущей unstable имеется куча бонусов (как и исключение таких плюшек как формогенерация, которую, как я понял, вынесут отдельным модулем (?)), которые при должной сноровке покрывают большое количество задач.
Ну а как будет 3.1, там поглядим. Конкурирующие ОРМ библиотеки наступают на пятки. Пока не понятно, воодушевит ли это разработчиков, либо наоборот ввергнет в апатию…
Что-то мне подсказывает, что будет совсем плохо с производительностью Jelly и объемом потребляемых ресурсов, из за такого колличества ‘cлоев’ кода.
Пока родная ОРМ пошустрее в скорости, но топорнее в работе.
avis
*Конкурирующие ОРМ библиотеки наступают на пятки.*
А конкурируещие, это Hive или еще что-то интересное есть?
Я так и не понял. Как здесь вставить новую строку в базу.
Я новичок в ОРМ. Может кто-что подскажет?
@CHEZCH
Вот тут описаны базовые действия с моделями Jelly.