Стартовая статья про замечательный модуль Jelly не могла вместить всей многочисленной информации по использованию данного ORM‘а, поэтому планирую продолжать цикл «точечными» статьями. В данный момент попробую систематизировать знания, необходимые при объявлении моделей. В общем, поговорим про описание полей Jelly.
Вступление
Для начала вспомним, как мы описываем свойства модели:
class Model_Auth_User extends Jelly_Model { public static function initialize(Jelly_Meta $meta) { $meta->name_key('username') ->sorting(array('username' => 'ASC')) ->fields(array( 'id' => new Field_Primary, 'username' => new Field_String(array( 'unique' => TRUE, 'rules' => array( 'not_empty' => NULL, 'max_length' => array(32), 'min_length' => array(3), 'regex' => array('/^[\pL_.-]+$/ui') ) )), 'password' => new Field_Password(array( 'hash_with' => array(Auth::instance(), 'hash_password'), 'rules' => array( 'not_empty' => NULL, 'max_length' => array(50), 'min_length' => array(6) ) )), 'password_confirm' => new Field_Password(array( 'in_db' => FALSE, 'callbacks' => array( 'matches' => array('Model_Auth_User', '_check_password_matches') ), 'rules' => array( 'not_empty' => NULL, 'max_length' => array(50), 'min_length' => array(6) ) )), 'email' => new Field_Email(array( 'unique' => TRUE )), 'logins' => new Field_Integer(array( 'default' => 0 )), 'last_login' => new Field_Timestamp, 'tokens' => new Field_HasMany(array( 'foreign' => 'user_token' )), 'roles' => new Field_ManyToMany )); } } |
Все происходит в статическом методе initialize()
, где мы должны сохранить информацию о связанной с моделью таблице (имя таблицы, имена полей, связи). Для разных типов полей есть предопределенные классы, у каждого из них могут быть свои особенности в настройке и работе. Так как более-менее адекватного руководства я не нашел (плохо искал?), на свет появилась данная статья.
Общие свойства полей (класс Field_Core
)
Все доступные классы полей являются предками класса Field_Core
. Таким образом, все поля в моделях Jelly работают со следующими свойствами:
column
— имя поля в БД. Обычно совпадает с именем поля в модели, но это необязательно.
Если при создании поля в конструктор передавать строковое значение, а не массив (т.е.
new Field_Primary('myid'))
, то это значение будет расцениваться как переданное значение свойстваcolumn
.primary
— флаг первичного ключа (FALSE по умолчанию). Обычно явно не указывается, достаточно для нужного поля установить типField_Primary
(первичный ключ).unique
— флаг уникальности поля (FALSE по умолчанию). Автоматически выставляется в TRUE для полейField_Primary
. Добавление этого флага означает, что при валидации модели для данного поля будет применен дополнительный callback_is_unique()
. Фактически это приводит к дополнительному запросу БД.in_db
— флаг наличия в БД (TRUE по умолчанию). Если установить FALSE, получим аналог ignored_columns из ORM.default
— значение по умолчанию для поля (изначально NULL).null
— конвертировать или нет пустые (empty()
) значения в NULL. По умолчанию FALSE.
Кроме этих свойств есть еще несколько, не относящихся к СУБД, но важных при описании полей.
description
— описание поля. Оно напрямую нигде не используется, но может быть использовано, скажем, при построении форм (для расширенной подсказки о назначении поля). По умолчанию содержит пустую строку.name
— имя поля для генерации элемента формы (т.е. HTML-атрибут name).label
— имя поля в более удобном и понятном человеку формате, чемname
. Обычно используется для тэгов LABEL формы. Если не указано явно, примет значение свойстваcolumn
, обработанное методомinflector::humanize()
.filters, rules, callbacks
— эти три свойства используются для валидации полей. Там все стандартно, как и в ORM.
Логические поля (Field_Boolean
)
Вроде бы тут все ясно — либо ДА, либо НЕТ. Но разработчики предпочли гибкость, в результате даже в таком простом поле есть что понастраивать:
true
— значение, которое надо записывать в БД при положительном значении поля (масло масляное, ага). По умолчанию 1.label_true
— описание положительного значения (по умолчанию «Yes«). Может быть использовано при выводе значения на экран или для генерации формы.- false — аналогично свойству true, только действует для отрицательного значения поля. По умолчанию, естественно, равно 0.
label_false
— как вы уже наверняка догадались, метка для свойстваfalse
. По умолчанию «No«.
Скорее всего вы эти свойства менять и не будете. Но сама возможность не может не радовать.
Метка времени (Field_Timestamp
)
Весьма полезный класс, позволяющий обрабатывать разного рода временные значения. Доступны следующие настройки:
format
— формат метки времени, в котором значение должно храниться в БД. Синтаксис можно посмотреть в описании функции date(). NULL по умолчанию, т.е. никакого преобразования не происходит.pretty_format
— формат метки времени, предназначенный для человеческих глаз. Данный формат используется при генерации элемента формы стандартным шаблоном Jelly. Согласитесь, не каждого обрадует значение ‘2010-09-09‘. Синтаксис аналогичен свойствуformat
. Значение по умолчанию ‘r’ (дата в формате RFC 2822, например ‘Thu, 09 Sep 2010 12:00:00 +0300′).auto_now_create
— флаг, отвечающий за автоматическое заполнение поля текущей меткой времени при первоначальном сохранении (т.е. при создании записи). По умолчанию FALSE.auto_now_update
— аналогичноauto_now_create
, но действует при обновлении записи. По умолчанию FALSE.
Несмотря на все форматы, сама метка времени в поле хранится в виде целого числа (UNIX timestamp), т.е. можно не напрягать голову мыслями о текущем формате времени.
Числа с плавающей запятой (Field_Float
)
Данный класс располагает специальным свойство places, который определяет количество знаков после запятой в итоговом числе. Если данное свойство отлично от NULL и является числом (is_numeric()
), то значение будет округлено до places
знаков.
Стоит обратить внимание, что отрицательные и дробные значения тоже являются числами, и вполне могут быть использованы в данном свойстве. Например, если установить
places = 2.3
, то значение будет округлено до двух знаков после запятой (дробная часть отбрасывается). Если же значение отрицательное, то округлять будут целую часть числа, например приplaces = -2.3
число12345.6789
будет сохранено в базу как12300
. Это может быть полезно при форматировании дробных чисел.
Строковые и целочисленные поля (Field_String, Field_Text, Field_Integer
)
Тут все без каких-либо особенностей, просто значение явно преобразовывается в нужный тип (строковый и числовой соответственно).
На данный момент я не нашел различий между
Field_String
иField_Text
в части реализации. В комментариях написано, чтоField_Text
предназначен для работы с большими текстами, возможно это еще в @TODO.
Поле ограниченного выбора (Field_Enum
)
Данное поле предназначено для работы с неким заранее известным диапазоном значений. Например, это может быть пол (мужской/женский/не указан) или месяц года. Сами значения для выбора указываются в свойстве choices
в виде массива. Можно использовать стандартный массив вида array('male', 'female', 'unknown')
, а можно использовать осмысленные значения для ключей: array('1' => 'male', 2 => 'female', 0 => 'unknown')
. Это удобно при генерации списков выбора (select list), когда пользователю показывается текстовое описание, за которым скрывается конкретный идентификатор значения в БД.
Если указанное пользователем значение в списке
choices
отсутствует, то оно будет заменено значением изdefault
.
Вычисляемое поле (Field_Expression)
Очень интересный тип данных. Он позволяет перекладывать на СУБД функции вычисления каких-либо значений на основе полученных из таблицы данных. Например, у нас есть поле `birthday` (дата рождения). Чтобы получить возраст пользователя, создаем вычисляемое поле:
'age' => new Field_Expression(array( 'column' => DB::expr('(YEAR(CURRENT_DATE)-YEAR(birthday)) - (RIGHT(CURRENT_DATE,5)<RIGHT(birthday,5))') )), |
В результате запрос к БД на выборку данных из таблицы будет содержать дополнительное поле ‘age‘, заполняемое содержимым нашего выражения. Так как поле физически в таблице не существует, параметр in_db
автоматически устанавливается в FALSE.
Точнее, должно было содержать, по задумке авторов. На самом деле у меня
Field_Expression
не заработал, пока я не внес кое-какие поправки в классJelly_Meta_Core
. Тикет создан, жду реакцию разработчиков.
Поле для хранения пароля (Field_Password)
Пароль по сути ничем не отличается от обычного строкового поля (поэтому Field_Password extends Field_String
), за исключением уже привычной нам традиции шифровать значение при сохранении модели. Единственная доступная нам опция — свойство hash_with
, которое содержит имя функции для шифрования (по умолчанию «sha1» — указывать без скобочек!).
Slug-поле (Field_Slug)
Долго ломал голову, как же по-русски назвать этот термин, ничего не придумал Slug — это такое нормализованная строка, где могут быть использованы только маленькие латинские буквы, дефисы и слэши (я привел мнение разработчиков Jelly). Все остальное заменяется на дефисы. Например, строка ‘Hello, World!‘ будет автоматом преобразована в ‘hello-world‘. Никаких свойств нет, хотя в принципе напрашивается как минимум возможность установки собственного диапазона допустимых символов.
Поле адреса электронной почты (Field_Email
)
Обычное текстовое (Field_Text
) поле, только автоматически будет применяться правило Validate::email()
при сохранении.
Прячем объекты и массивы (Field_Serialized
)
Данное поле позволяет работать с сериализованными (serialized) переменными. Зачем это надо? Бывает удобно хранить массивы или целые объекты в БД (например, конфигурация ACL или прочие структуры в том же духе). При заполнении значения происходит попытка восстановить (unserialize) объект, а при сохранении записи в БД — обратная запаковка (сериализация). Единственный минус (и то, возможно, только мне так кажется) — вызовы serialize()
и unserialize()
предваряются «собачкой» для подавления ошибок.
Загрузка файлов (Field_File
)
Данное поле применимо для сохранения загруженных пользователем файлов. Имеется два параметра:
path
— путь к директории для сохранения. Естественно, директория должна быть доступна для записи. Это обязательное поле, у которого нет значения по умолчанию.delete_old_file
— данный флаг используется при замене одного файла другим. Если TRUE (а это значение по умолчанию), то при удачной загрузке новый файл заменит старый не только в БД, но и на диске (т.е. старый файл будет удален физически).
Сам по себе файл лежит на диске, а в поле хранится его имя. Таким образом, доступ к файлу можно получить через
$this->path.$this->value
.
Пора заканчивать
На этом стандартные типы полей Jelly закончились. Остались неразобранными поля для описания связей, но это уже будет тема для отдельной статьи.
Спасибо за обзор.
В частности ваша статья помогла перевесить мой выбор в сторону Jelly вместо hive
Очень хочется почитать про связи с формами и нестандартные поля, например картинки.
Собственно сам расскажу.
В unstable ветке уже давно была реализована привязка к изображениям
Спасибо за статью, все отлично. Достаточно давно пользуюсь Jelly, один вопрос, куда девается 50 мс процессорного времени при создании объекта, когда запрос к базе выполняется за 0.3 мс ? На кой черт в объектах orm аккамулирется столько мусора по мимо данных выборки?
Скорее всего это связано с первоначальной инициализацией объекта, ведь создается объект Meta с информацией о модели и т.д. Замеряли, сколько уходит на повторное создание объекта того же класса (в рамках одного реквеста)? По идее засчет кэширования должно получиться значительно меньше.
2biakaveron
Повторное создание конечно быстрей, но тоже далеко от идеала. Заметил, что на findfile тратится также 3/4 всего времени загрузки, лучше применять какой-нибудь ускоритель. С ORM пока борюсь кешированием, хотя если кешировать сами получаемые объекты, то тоже приходит абзац, из-за большого размера сериализация превращается в кошмар и по времени выигрыша практически нет.
А есть ли модуль авторизации/регистрации типа А1 на Jelly ? Auth не катит, у него поля не редактируются (
@Max Kamashev
*В unstable ветке уже давно была реализована привязка к изображениям *
Покажите пример, пожалуйста.
A1 поддерживает разные виды ORM
@madmax посмотрите вот на этот модуль http://github.com/raeldc/jelly-auth и для него даже существует демка http://github.com/rob/jelly-auth-demo
По поводу image вот пример http://github.com/jonathangeiger/kohana-jelly/blob/unstable/classes/jelly/field/image.php , но я на ветку unstable не смог перейти, так как там они отказались от генерации форм, и переписал их компонент под себя.
Если очень надо, то я выложу класс для изображений. Сейчас у меня гости и просто некогда
@taggi
А в сравнении под нагрузкой, кто себя лучше показывает родная ORM или Jelly ?
@biakaveron
Здорово! А я все по старинке юзаю старый вариант, где еще поддержки Спринга нет
@Max Kamashev
Спасибо! Если не сложно, выложите пожалуйста, интересно посмотреть.
Вообще кохане не хватает сайта, со сборником рецептов.
Иван, может организуете в своем блоке такую возможность? =)
@xbagir
Написал статью как использовать изображения. Не думал что много выйдет
http://uk0.us/2010/09/jelly-dobavlyaem-tip-polya-izobrazhenie/
@Max Kamashev
Спасибо большое.)) Только поправьте, пожалуйста, код — там теги местами проскакивают
2xbagir
Родную ORM не пробывал, но скорость Jelly что-то совсем не радует. Пока справляюсь кешированием, подумываю не вернуться ли к обычным моделям, теряя в гибкости.
что то in_db не хочет работать. В контроллере заполняю поле, которое в модели отмечено как in_db=FALSE, а во view оно уже содержит NULL
@uhamurad
А при чем тут in_db и NULL? Поля в принципе (не считая сохранения) обрабатываются одинаково, независимо от того, реальное оно или псевдо.