Контент


Jelly — новое слово в ORM?

Если Вы интересуетесь ORM или просто читаете официальный форум Kohana, то наверняка успели обратить внимание на данную библиотеку. Ее разработчик, Jonathan Geiger, решил взять все самое лучшее из ORM и Sprig, дабы создать модуль с блэкджеком под собственные нужды. Поскольку ORMы — весьма популярная тема (в том числе и на данном сайте), решено ознакомиться с данной реализацией.

Итак, скачиваем модуль и начинаем исследование содержимого. Для начала отмечу, что в составе модуля поставляется документация в формате «родного» Userguide. То есть если в проекте подключены модули Jelly и Userguide, то в секции Modules документации появится соответствующий раздел Jelly. В документации присутствует и русский перевод (спасибо тебе, Сергей avis Гладковский). Документации вполне достаточно для быстрого старта, особенно если есть опыт работы с ORM или Sprig.

Модели

С чего обычно начинается описание ORM-подобных библиотек? Правильно, с моделей! Вот пример из документации:

class Model_Post extends Jelly_Model
{
    public static function initialize(Jelly_Meta $meta)
    {
        $meta->table('posts')
             ->fields(array(
                 'id' => new Field_Primary,
                 'name' => new Field_String,
                 'body' => new Field_Text,
                 'status' => new Field_Enum(array(
                     'choices' => array('draft', 'review', 'published'))),
                 'author' => new Field_BelongsTo,
                 'tags' => new Field_ManyToMany,
             ));
    }
}

Очень похоже на Sprig — все необходимые поля объявляются при инициализации (напомню, что ORM по умолчанию запрашивает описания полей у базы данных), для каждого вида поля создается свой класс (потомок Jelly_Field). У полей есть свойства $in_db (существует ли данное поле в таблице БД), $primary (булевый флаг, является ли поле первичным ключом), $default (значение по умолчанию) и т.д. — большинство свойств знакомы нам по Sprig. Методом table() устанавливается имя таблицы БД (на самом деле это необязательно, т.к. имя для таблицы соответствует рекомендациям ORM и было бы вычислено автоматически).

Параметры модели

Все, что описывает модель в части связи с БД (поля, имя таблицы, имя первичного ключа и т.д.), хранится в объекте класса Jelly_Meta. В вышеприведенном примере метод initialize() модели как раз заполнял переменную $meta, которая будет прикреплена к модели Model_Post. Эти данные выделены в отдельный объект не просто так — класс Jelly хранит (регистрирует) метаданные всех созданных ранее моделей в свойстве $_models, и в дальнейшем ознакомиться с ними можно будет без необходимости инициализировать саму модель. Достаточно вызвать Jelly::meta('post'), и на выходе получим Meta-объект для модели Model_Post.

CRUD

Для загрузки, создания, изменения и удаления объектов в Jelly используются различные методы (как и в Sprig).

Доступ к записям в БД

// загрузка записи с идентификатором = 1
$post = Jelly::select('post', 1);
// то же самое
$post = Jelly::select('post')->load(1);
// загрузка всех записей
$posts = Jelly::select('post')->execute();
// загрузка с условием
$approved_posts = Jelly::select('post')->where('is_approved', '=', 1)->execute();

В первом случае будет извлечена только одна запись, поэтому $post содержит объект Model_Post. В случае же вызова execute() на выходе будет объект класса Jelly_Collection, это что-то вроде ORM_Iterator из ORM второй ветки Kohana.

На самом деле, если вызвать метод limit() с параметром 1, то результат будет содержать одну модель, как и в случае с load(). По сути вызов load($id) просто краткая запись от where('id', '=', $id)->limit(1)->execute().

С объектом Jelly_Collection можно работать также, как и с ORM_Iterator или Database_Result: перебирать элементы в цикле, подсчитать общее число записей в нем, преобразовать в массив с помощью метода as_array($key, $value).

Новая запись

// создание нового объекта
$post = Jelly::factory('post');
// заполняем поля по одиночке
$post->title = 'new post';
// или разом
$post->set(array('text' => 'new post text', 'created' => time()));
// сохраняем
$post->save();

Можно использовать и Jelly::insert('post')->set(...)->execute(), но это не так удобно, по сравнению с созданием модели и ее заполнением. Зато быстрее!

Редактирование записи

$post = Jelly::factory('post');
// устанавливаем нужные значения
$post->title = 'edited post';
// сохраняем. Не забудьте передать идентификатор записи!
$post->save(1);

Похоже на создание новой записи, но тут мы заранее знаем идентификатор записи и меняем значения отдельных ее полей (в данном случае поле `title`) без загрузки самой записи. На самом деле можно было сделать и «по старинке»:

$post = Jelly::select('post', 1);
$post->title = 'edited post';
// модель загружена из БД, передавать идентификатор в метод save() не надо
$post->save();

Усложнение запросов

Как и в случае с ORM, в Jelly для непосредственной работы с базой данных используется Query_Builder, точнее его потомок, Jelly_Builder. Поэтому синтаксис запросов выглядит знакомым, доступны методы where(), join() и т.д.

А теперь представьте себе, что вы часто выбираете записи по определенному условию, например утвержденные администратором/модератором статьи:

$posts = Jelly::select('post')->where('is_approved', '=', 1)->execute();

В ORM такие вещи можно было спрятать в методе модели, примерно так:

public function approved($value = 1)
{
    $this->where('is_approved', '=', $value);
    return $this;
}
$posts = ORM::factory('post')->approved()->find_all();

Однако в Jelly методы select(), insert(), update() и delete() возвращают Jelly_Builder, а не текущую модель, поэтому в модели подобные методы логики приложения создавать бессмысленно. Зато можно создавать свои классы Jelly_Builder, например для модели Model_Post создаем Model_Builder_Post и в нем прописываем нужные методы:

class Model_Builder_Post extends Jelly_Builder {
  public function approved($value = 1)
  {
      return $this->where('is_approved', '=', $value);
  }
}

Jelly очень гибка и имеет множество настроек. В том числе есть и возможность поменять префикс для моделей и билдеров. Берем заглушку classes/jelly.php (Jelly использует ту же тактику избыточного именования моделей, что и Ko3) и меняем свойство $_model_prefix под свои нужды, например ‘myjelly_‘. В результате все модели и билдеры должны будут храниться в директории classes/myjelly, а имена этих классов начинаться не с ‘Model_‘, а с ‘Myjelly_‘. Но, конечно, мало кто будет с этим заморачиваться.

Псевдонимы

Случается, что первичные ключи называются не по «фэн-шую», например post_id или IdPost, а хотелось бы обращаться по привычному имени id. Выход есть — используйте псевдонимы. Существует два варианта, привычный и инновационный :)

  1. В методе fields() модели объявляете имя поля как Вам удобно, и указываете в свойстве ‘column‘ реальное имя поля. Например, так:

    'id'        => new Field_Primary(array('column' => 'post_id')),


    В данном способе нет ничего нового, такие псевдонимы доступны как в ORM, так и в Sprig.

  2. Сперва по всем правилам объявляете поле, а ниже создаете новые поля, «переадресовывая» их на ранее созданное. Поясню на примере:

    'post_id'        => new Field_Primary,
    'id' => 'post_id',

    Теперь можно использовать $post->id, в результате будет отрабатывать поле ‘post_id‘. Можно создать и второй псевдоним, тоже будет работать. А вот по цепочке (id1 -> id -> post_id) не работает, ну и ладно. ;)

Конечно, рекомендуется использовать метод id() для получения значения первичного ключа модели (в ORM его аналогом был метод pk()). Обращаться к именам полей напрямую лучше пореже, дабы не привязываться к потенциально изменчивой структуре БД.

Мета-псевдонимы

Очень интересное и вкусное нововведение. Обычно при объявлении тех же связей мы пишем реальные имена полей (первичных ключей, внешних ключей и т.д.), аналогичные неудобства при заполнении условий Query_Builder‘а (что-то типа where('id', 'IN', $values)). А вот в Jelly есть специальные слова, позволяющие указать на нужные поля без их конкретного имени! Вот их перечень:

  • :primary_key — тут все понятно, будет заменено на имя первичного ключа.
  • :foreign_key — имя внешнего ключа для других моделей (обычно явно не указывается, поэтому формируется как имя_модели + '_id').
  • :name_key — имя «титульного» поля, например ‘username‘ для модели User или ‘title‘ для Post (если конечно Вы соответствующее свойство $name_key указывали в модели). Полезно для получения списка id=>value из набора записей, например $posts->as_array(':primary_key', ':name_key').
  • :unique_key. Данный мета-алиас предназначен для работы с уникальными ключами, в основном через Jelly_Builder (аналог стандартного Query_Builder‘а). По умолчанию в моделях Jelly есть только один уникальный ключ — это конечно первичный ключ. Даже если Вы добавите в свойствах какого-либо поля параметр $unique = TRUE, автоматически оно определяться не будет. Надо создавать свой билдер для модели и в нем прописывать метод unique_key($value). Вот как он изначально написан в Jelly_Builder:

    	public function unique_key($value)
    	{
    		return $this->_meta->primary_key();
    	}

    Если мы для модели Model_User захотим сделать уникальными поля email и username, то в класса Model_Builder_User создадим свой метод:

    	public function unique_key($id)
    	{
    		if ( ! empty($id) AND is_string($id) AND ! ctype_digit($id))
    		{
    			return validate::email($id) ? 'email' : 'username';
    		}
    		return parent::unique_key($id);
    	}

    В общем, все как и в старом-добром Auth. А дальше уже можно использовать:

    $user = Jelly::select('user')->where(':unique_key', '=', $_POST['login'])->load();
    // или более короткая запись
    $user = Jelly::select('user')->load($_POST['login']);
    // еще более короткая запись!
    $user = Jelly::select('user', $_POST['login']);

    Так как мы определили метод для уникальных полей, вместо id можно передавать имя пользователя или email — нам по сути неважно. В данном случае мы позволим пользователю входить как по логину, так и адресу эл.почты.

Связи

Естественно, есть поддержка привычных нам типов связей (используем классы Field_HasOne, Field_HasMany, Field_BelongsTo и Field_ManyToMany). Для настройки связи используются три параметра, которые передаются в конструктор:

  • column‘ — имя поля в таблице модели, которое является внешним ключом. Естественно, это доступно только для связей BelongsTo, т.к. в остальных случаях внешний ключ выносится в другую модель (и свойство $in_db, упомянутое выше, для этих видов связей равно FALSE).
  • foreign‘ — строка с описанием внешнего ключа. Обычно указывается в виде ‘model.field‘, например ‘article.id‘. Если данный параметр не указан, то будет автоматически сгенерировано значение исходя из имени связи, с использованием первичного ключа связанной модели. Также можно указать только имя модели, в этом случае к нему будет добавлен первичный ключ.
  • through‘ — этот параметр нужен для связей ManyToMany, чтобы указать промежуточную таблицу. Его можно указать как строку (тогда это должно быть имя промежуточной модели), как массив (поле ‘model‘ определяет имя промежуточной модели, а ‘columns‘ — массив полей с первичными ключами связанных моделей). Если не указывать вообще, сработает автозаполнение, принцип именования промежуточной модели аналогичен ORM‘у (Role_User для моделей User и Role).

Много текста, мало смысла, поэтому вот несколько примеров описания связей для модели Model_User:

// все по умолчанию, связь с моделью Model_Post по ключу user_id
'posts'  => new Jelly_Field_HasMany;
// добавляем отсебятины, пусть внешний ключ называется author_id
'posts'  => new Jelly_Field_HasMany(array('foreign' => 'post.author_id'));
// + меняем имя связи
'articles' => new Jelly_Field_HasMany(array('foreign' => 'post.author_id'));
// добавляем связь для получения отмодерированных статей
'moderated' => new Jelly_Field_HasMany(array('foreign' => 'post.moderator_id'));
 
// по умолчанию промежуточная модель Role_User должна содержить ключи role_id и user_id
'roles' => new Jelly_Field_ManyToMany;
// указываем явно, что в модели Role внешним ключом будет первичный ключ
'roles' => new Jelly_Field_ManyToMany(array('foreign' => 'role.role:primary_key'));
// используем нестандартное имя промежуточной модели
'roles' => new Jelly_Field_ManyToMany(array('through' => 'user_role'));
// в данном примере имя промежуточной модели и содержащиеся в ней ключи нестандартные
'roles' => new Jelly_Field_ManyToMany(array('through' => array('model' => 'user_role', 'columns' => array('userId', 'roleId'))));

Обратите внимание, что при объявлении имен полей в промежуточной модели (параметр ['through']['columns']) важен порядок их следования. Сперва указывается внешний ключ от текущей модели (в данном случае это userId), а потом — от связанной.

Валидация

При объявлении полей модели есть возможность указать и условия валидации (rules/filters/callbacks, а также label). Тут все в принципе аналогично Sprig. Для уникальных полей (свойство $unique == TRUE) автоматически добавляется callback-метод _is_unique() для проверки на наличие проверяемого значения в БД. В случае ошибок при проверке выбрасывается Validate_Exception, поэтому не забывайте оборачивать вызовы validate() и save() в конструкции try ... catch(). Метод validate() возвращает массив проверенных данных в случае успеха.

Работа с формами

Каждое поле модели реализует метод input($prefix, $data), который должен выводить поле ввода/выбора значения (через представление View). Первый аргумент ($prefix) позволяет указать свой путь для представления, по умолчанию он ‘jelly/field‘ (да, в views/jelly/field/ можно подсмотреть уже имеющиеся заготовки для стандартных типов данных Jelly). Параметр $data позволяет передать в шаблон дополнительные переменные, которые будут добавлены к публичным свойствам поля. Еще в шаблоне будет доступна переменная $field, которая содержит собственно сам выводимый объект Jelly_Field.

Заключение

Это был беглый обзор возможностей Jelly, основная цель которого — познакомиться с основами этой замечательной библиотеки. На данный момент могу выделить следующие плюсы и минусы:

+ неплохая документация, причем сразу в комплекте с модулем.
+ высокая гибкость и настраиваемость, даже выше, чем у Sprig.
+ введение объекта Meta позволяет уменьшить количество запросов к БД, когда нужны только метаданные о моделях.
- относительная сложность по сравнению со Sprig и тем более ORM.

Модуль очень интересный, и в целом сообществу очень понравился (достаточно посмотреть на количество топиков о нем). Разработкой заняты солидные люди, добавляются вспомогательные модули (типа Jelly-Auth), в общем обратите на него внимание :)

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.

Теги: , , , .


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

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

  1. Chodex пишет:

    Спасибо, шикарная статья. В закладки на будущее. Вопрос есть, можно ли без особого опыта работы со стандартным ORM использовать в дальнейшем Jelly, стоит ли пытаться изучать сразу Jelly или необходимо сначала попрактиковаться с ORM?

  2. BIakaVeron пишет:

    В принципе документации хватает на то, чтобы сделать первые шаги. Дальше методом тыка + изучение исходников (комментарии качественные, читать легко). Да и на форуме уже хватает топиков по Jelly

  3. Rafi B. пишет:

    Great article. I would really like though to hear about advantages over using the official ORM..

  4. BIakaVeron пишет:

    Hi Rafi!

    I’m waiting for ORM v3.1.0 release ;)

  5. Rafi B. пишет:

    Hey.. I tried asking jheatco about the 3.1 release.. Do you have a clue when will it be released?

  6. BIakaVeron пишет:

    All I know is 3.1.0 roadmap and his feature request

  7. Максим Нагайченко пишет:

    Привет! У тебя в коде теги похерены

    $post->save(1);</pre.

    Это в разделе Редактирование записи.

    Статья отличная. Я вот все никак не решаюсь на ОРМ перейти, но сейчас для меня самое то!

    Спасибо.

    ЗЫ ты надеюсь сделал в куках запоминание данных для каммента?? :lol:

  8. BIakaVeron пишет:

    Ага, спасибо, исправил. А куки — хз, вроде как были проблемы у отдельных пользователей. Я сейчас в положении буриданова осла — этот блог обновлять давно пора, но WP не хочу, а на Коханке свой написать времени особо нет :lol:

  9. Ъ пишет:

    Да, я сам недавно нашел эту ОРМ-ку. Хочу ее отдельно от Коханы заюзать (в своем фреймворке), не знаю: получится ли.

  10. Xobb пишет:

    Однако в Jelly методы factory(), insert(), update() и delete() возвращают Jelly_Builder, а не текущую модель,

    Ну-ну, не сбивайте читателя с толку:


    public static function factory($model, $values = NULL)
    {
    $class = Jelly::class_name($model);

    return new $class($values);
    }

    На factory возвращается модель.

  11. Rafi B. пишет:

    Эй, ребята .. Если вы обнаружили Jelly гораздо удобнее, можете ли вы объяснить, почему? Я пытаюсь исследования различия, но есть немного времени.
    Спасибо

  12. Ъ пишет:

    да, кстати, про преимущества Jelly — я на гитхабе видел форк от русского чувака, который переводит доку на русский

  13. Zares пишет:

    Я бы не сказал, что Jelli — новое слово в ORM.
    Скорее всего это напоминает игру в доганялки…
    С некоторым запаздыванием любимых ЗВЕЗД.
    Что-то подобные появилось почти год тому назад в DooPHP!

  14. avis пишет:

    Иван, спасибо тебе за обзор! Как всегда очень понятно и по делу… :)

    Как плюс (по сравнению со Sprig) отметил бы нативное использование query builder’а при построении запросов. В Sprig это сделано не особо удобно, что повышало порог вхождения и вносило элемент отторжения у людей, кто активно пользовал Kohana ORM. Jelly благодаря этому для них стал бы как раз легче в понимании, мне кажется…

    Xobb прав — factory() метод возвращает модель. CUD же методы — Jelly_Builder…

    Так же недавно ввели фабричный метод для определения типа полей Jelly::field(‘FieldType’). Так что можно определять типы по-старинке названием класса, а можно и красивым новым способом (работая в Netbeans, пользование фабричных методов у меня приводит к тренировке памяти, что не может не радовать…:))

  15. BIakaVeron пишет:

    @Xobb, @avis
    Конечно же, это не factory(), а select(). Исправил.

  16. Alexander пишет:

    спасибо за статью
    сейчас пытаюсь портировать sprig-mptt в jelly-mptt — не так просто это сделать, Jelly действительно посложнее будет

  17. Satisfaction пишет:

    Шикарная статья ! в прочем как всегда =)
    После беглого осмотра Sprig вызвал отторжение, до сих пор юзаю ORM, а после вашей статьи как то начал задумыватся о переходе на Jelly.
    Надеюсь следующая версия ORM догонит по функциональности других собратьев =)

  18. Werewolf пишет:

    Добавили with хорошо, уже отрыв от Sprig. Но все же нехватает оптимизации загрузки данных для hasMany/ManyToMany.
    Пример.
    Есть таблица languages(языки) и зависимая tests(тесты). Отношение language has many tests.
    Пробегая языки в цикле и дергая зависимые тесты мы генерируем запрос на каждый язык. А надо вторым запросом получать все тесты, внешние ключи которых равны ID выбранных языков.

  19. Werewolf пишет:

    Даже топик открыли на гитхабе
    http://github.com/jonathangeiger/kohana-jelly/issues#issue/19

  20. Андрей пишет:

    Здравствуйте Иван. Я уже задал вопрос на форуме сообщества Коханы, но решил еще спросить здесь. Так как с ORM познакомился недавно возникает много вопросов, которые ранее с легкостью решал с помощью обычных запросов. Например как получить данные из 2-х таблиц с связкой через LEFT JOIN.
    Имею 2 таблицы:

    // Таблица, в которой хранится общая информация
    CREATE TABLE `posts` (
    `id` int(5) NOT NULL auto_increment,
    `author` varchar(32) default NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8

    // Таблица, в которой храним мультиязычные данные
    CREATE TABLE `postsdescs` (
    `id` int(11) NOT NULL default ’0′,
    `lang` varchar(2) default ‘ru’,
    `name` varchar(32)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8

    Например получим все новости на русском языке:
    SELECT posts.*, postsdescs.name FROM posts LEFT JOIN postsdescs ON (posts.id = postsdescs.id) WHERE lang = ‘ru’;

    Или же все варианты конкретной новости с ID 1:
    SELECT posts.*, postsdescs.name FROM posts LEFT JOIN postsdescs ON (posts.id = postsdescs.id) WHERE posts.id = 1;

    Подскажите пожалуйста как получить подобные данные с помощью библиотеки Jelly?
    Нужно ли создавать отдельную модель на каждую таблицу или же только на основную?

    В процессе «экспериментов» я создал 2 модели:

    post.php

    class Model_Post extends Jelly_Model {
    public static function initialize(Jelly_Meta $meta) {
    $meta->name_key(‘id’)->fields(array(
    ‘id’ => new Field_Primary,
    ‘author’ => new Field_String,
    ‘postdesc’ => new Field_HasMany(array(‘model’=>’postsdesc’, ‘foreign’=>’id’))
    ));
    }
    }

    и postdesc.php

    class Model_PostsDesc extends Jelly_Model {
    public static function initialize(Jelly_Meta $meta) {
    $meta->fields(array(
    ‘id’ => new Field_BelongsTo,
    ‘name’ => new Field_String,
    ));
    }
    }

    Данные пытаюсь получить так:
    Jelly::select(‘post’)->where(‘lang’,'=’,'ru’)->execute();

    Но получаю ошибку:
    Database_Exception [ 1054 ]: Unknown column ‘lang’ in ‘where clause’ [ SELECT `posts`.`id` AS `id`, `posts`.`author` AS `author` FROM `posts` WHERE `lang` = 'ru' ]

    Я осознаю то, что наворотил и скорее всего неправильно, но прошу строго не судить а указать на путь истинный )). Где я ошибся и как правильно построить модель(и)? Заранее спасибо.

  21. taggi пишет:

    Добрый день, можно сделать нечто вроде UPDATE table SET a = a + 1 одним запросом используя Jelly ?
    Как можно подать запрос на старт транзакции «TRANSACTION START» ?

  22. biakaveron пишет:

    @taggi
    Так как класс Jelly_Builder является потомком класса Database_Query_Builder, то можно все делать аналогично обычной работе с модулем Database. Т.е. что-то вроде Jelly::update(‘model’)->set(array(‘a’ => (new Database_Expression(‘a+1′))))->execute().

  23. taggi пишет:

    2biakaveron

    Спасибо, а есть ли возможность написать такой запрос с помощью конструктора
    «SELECT * FROM table LOCK IN SHARE MODE» ?

  24. biakaveron пишет:

    Любые нестандартные запросы (блокировка таблиц к примеру) лучше оформить в виде отдельного метода модели (или сделать наследование соответствующего драйвера БД). Не забывайте, что Query Builder предназначен для построения запросов без привязки к конкретной СУБД.

  25. taggi пишет:

    2biakaveron

    Да, Вы безусловно правы, лучше попытаться расширить драйвер mysql для таких вещей. Если готовых решений нет, то придется поэкспериментировать :~)

  26. taggi пишет:

    Кстати нашел один глюк в Jelly при навешивание правил валидации на поле типа Integer получается не совсем корректная вещь, Jelly автоматом приводит значение к целому типу, а потом уже применяет валидацию.

    Например мы хотим повесить правила:
    не пустое
    целочисленное

    при любом текстовом значение поля например ‘qwer’ мы будем получать ошибку ‘поле должно содержать значание’, т.е. срабатывает правило 1, хотя это неверно.

    Выход либо объявлять поле как String, либо приводить любой текст в форме к целому типу. И то, и то не совсем верно, но работает :~)

  27. VanSanblch пишет:

    Боюсь показаться тупицей, но нигде не могу найти следующую информацию.

    Вроде как модуль коханы для работы с базой не поддерживает mssql. Вопросов, собсвенно два — так ли это и решается ли этот вопрос использованием jelly?

    Просто я веду разработку сайта под с использованием sqlite или mysql, но на «боевой» машине СОВЕРШЕННО НЕОЖИДАННО может оказаться только mssql…

  28. biakaveron пишет:

    Поддерживает — через драйвер PDO. Вроде как есть и модуль на github’е, но он еще сырой.
    По поводу Jelly — не путайте, ORM всего лишь библиотека для взаимодействия с БД, никаких собственных драйверов она не содержит.

  29. VanSanblch пишет:

    Спасибо, попробую этот kohana-mssql.

  30. Макушкин пишет:

    Здравствуйте, у меня возник вопрос в связи с валидацией.
    Собственно я пробую реализовать UnitTest’ы и поэтому проверяю, валидируется ли моя модель с неправильными данными, к моему удивлению она валидируется!
    Если при вызове validate(), поле не инициализировано, то _все_ правила игнорируются, включая not_empty, что собственно противоречит здравому смыслу! Если же инициализировать поле с пустым значением то все правила срабатывают.
    Я заметил что при вызове метода save() правила срабатывают и неинициализированное поле выбрасывает ошибку.
    Но дело в том что для тестирования мне не нужно сохранять что-то в базу, мне нужно всего-лишь проверить валидацию!
    Если честно, то я не знаю, баг ли это или фича, или же я что-то не понял. Пожалуйста, подскажите.

    class model_testmodel extends Jelly_Model
    {
        public static function initialize(Jelly_Meta $meta)
        {
            $meta->fields(array(
                              'id' => new Field_Primary,
                              'name' => new Field_String(array(
                                  'rules' => array(
                                      'not_empty' => NULL
                                  )
                              )),
                          ));
        }
     
    }
     
    $test = Jelly::factory('testModel');
    // exeption не бросается, несмотря на то, что поле 'name' не инициализировано
    $test->validate();
    //но как только я его установлю, валидация срабатывает
    $test->name = '';
    $test->validate();

  31. biakaveron пишет:

    @Макушкин
    На самом деле в методе validate() существует проверка на наличие измененных данных. Так как Вы ничего не меняли (случай первый), то валидация на этой проверке и заканчивается. Во втором случае уже есть измененное поле (`name`), оно участвует в проверке, поэтому и выбрасывается Exception.
    Как вариант, можно использовать тот факт, что метод validate() возвращает массив полей, участвовавших в проверке. Так что можно добавить примерно следующие строки:

    // пробуем сохранить модель, но не факт, что были изменения
    try ($data = $test->validate())
    {
       if (empty($data))
       {
          // предупреждаем пользователя, что изменений не было
       }
    }

    В целом, это проблема не конкретного поля, т.е. это не игнор правила not_empty, а механизм обработки изменений модели.

  32. Макушкин пишет:

    Спасибо за ответ. Теперь я понимаю почему валидация себя так ведет. Это очень логично если мы имеем дело с загруженной моделью, ведь не имеет смысла проверять поля которые были уже проверены раньше.
    Но в моем случае модель создается заново, т.е. все поля просто необходимо проверить.
    Хорошо что все опенсорс :) Мне хотелось бы добавить к методу validate() возможность проверки всех данных, как это делает метод save(), например вызывая его вот так: jelly_model->validate_all(), где этот метод всего лишь обертка для $this->validate($this->_changed + $this->_original).
    Пока я Вам писал, даже решение придумал :)
    Скорее всего сделаю я свой класс-обертку для Jelly_Model и засуну эту ф-ю в него.

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

  1. Желє ссылается на эту запись on 20 июля 2010

    [...] детальніше (проте не так весело) написано в Івана Броткіна. Також офіційна документація не відмінялась. [...]

  2. Описание полей Jelly | Изучаем Web ссылается на эту запись on 11 сентября 2010

    [...] Стартовая статья про замечательный модуль Jelly не могла вместить всей многочисленной информации по использованию данного ORM‘а, поэтому планирую продолжать цикл “точечными” статьями. В данный момент попробую систематизировать знания, необходимые при объявлении моделей. В общем, поговорим про описание полей Jelly. [...]



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

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