Контент


ORM. Основы

Перечитав заново написанную мной ранее статью об ORM, а также несколько тем на форуме, понял, что надо бы более подробно описать этапы построения моделей, формирования связей и прочие тонкости. Поэтому в данной статье я опишу принцип работы ORM-объекта без связей с «окружающим миром».

Создание модели

Существует базовый класс ORM (ORM_Core), который должен являться родительским для всех наших моделей. Собственно говоря, вся магия происходит именно в нем. Поэтому для создания модели Article необходимо написать следующее:

class Article_Model extends ORM {
}

Не забываем несколько правил:

  • Имя модели должно быть в единственном числе. Есть исключения, например слова типа News, существующие только во множественном числе.
  • Имя модели выбирается в зависимости от имени таблицы БД, с которой она работает. В частности, модель Article по умолчанию будет искать таблицу articles. Это тоже можно исправить (указав имя таблицы в свойстве $table_name), но прозрачность и предсказуемость моделей снижается.

Использование ORM

Итак, мы уже (!!) можем работать с данной моделью. Например, попробуйте создать объект статьи с идентификатором 1:

$article = ORM::factory('article', 1);

Мы не обговаривали структуру таблицы, предположим наличие в ней первичного ключа id (auto_increment), даты создания created, заголовка title и текста статьи text. Это самый простой вариант. Обратите внимание, что использование в качестве идентификатора автоинкрементного поля id практически закон для ORM. Хотя, опять же, можно указать первичный ключ в свойстве $primary_key.

Теперь можно получить все интересующие нас поля записи:

echo "Запись №: ", $article->id, "<br />";
echo "Заголовок: ", $article->title, "<br />";
echo "Текст: ", $article->text, "<br />";
echo "Время создания", $article->created, "<br />";


Заметьте, что мы не создавали в модели свойства $id, $title и т.д. — это все «магия» ORM. В библиотеке ORM существует метод __get($column), который позволяет обращаться к полям таблицы как к свойствам объекта. В объекте ORM хранится информация о полях, получаемая при его создании через обращение к драйверу БД (т.е. дополнительный запрос при каждом создании ORM-объекта). Если мы обращаемся к свойству $article->text, в методе __get() происходит поиск столбца ‘text‘ в массиве известных полей таблицы (свойство $table_columns) и если поле найдено, возвращается текущее значение данного поля активной записи.

Предположим, что записи с id=1 в таблице нет. В таком случае все равно объект $articles будет создан, свойства $articles->text и т.д. будут доступны, но проинициализированы пустыми значениями (в соответствии с типом поля). По сути, получилось то же самое, что если бы мы просто создали пустой объект ORM через конструктор, например как ORM::factory(‘article’). Чтобы определить, загрузился ли объект, используйте свойство $loaded, которое устанавливается в TRUE в случае успешного заполнения объекта данными:

$article = ORM::factory('article', $id);
if (! $article->loaded) {
  // сообщаем об ошибке
}
// работаем с объектом

Через ORM можно не только загружать объекты, но и изменять их, создавать новые и т.д. Поля таблицы доступны для записи, например так:

// меняем заголовок статьи
$article->title .= '[NEW]';
// но в БД заголовок не поменялся!
// сохраняем измененные поля
$article->save();
// сгенерировался запрос на update (только!) измененных полей


Обратите внимание, что автоматически изменения не сохраняются. Создание объектов происходит аналогично:

$article = ORM::factory('article');
$article->title = 'test title';
$article->text = 'test article';
$article->created = time();
$article->save();
if (! $article->saved) {
// произошла ошибка сохранения статьи
}


Как видите, создание новой записи в БД происходит тем же методом, что и изменение существующей. Метод save() с помощью все того же свойства ORM::$loaded выбирает, какой запрос совершить — UPDATE или INSERT. Для проверки сохранения объекта используем свойство $saved. После сохранения у объекта также становится TRUE‘шным свойство $loaded.

Мы не устанавливали значение для поля id, так как оно auto_increment‘ное и вычисляется автоматически средствами СУБД. Кроме того, в таблице также могут быть поля со значениями по умолчанию (например, CURRENT_TIMESTAMP для метки времени) или допускающие NULL-значения. Они также будут заполнены автоматически. Однако в объекте актуализации не произойдет. Будьте готовы к тому, чтобы вручную перегрузить объект заново, для этого есть метод reload().

Ну и удалить объект можно с помощью метода delete(). После этого объект заново инициализируется (все поля устанавливаются в NULL, можно обнулять и самостоятельно с помощью метода clear()). По умолчанию удаляется текущий объект, однако можно передать в качестве параметра id удаляемой записи. Для удаления нескольких записей создан метод delete_all(). По умолчанию он удаляет все записи, но можно передать массив идентификаторов. Кроме того, всегда можно использовать методы Query Builder‘а для добавления дополнительных условий (в частности, метод where()). После того, как условия выборки установлены, используйте метод find(). В результате получите объект ORM. Дополнительные условия (идентификатор записи или массив условий where) можно указать в качестве необязательного параметра этого метода.

Работа с несколькими записями

ORM может работать не только с одной записью. Для получения нескольких объектов служит метод find_all(), принимающий два дополнительных параметра — максимальное количество записей и начальное смещение (в некоторых СУБД может не работать). Например, так мы получим последние 10 статей:

$articles = ORM::factory('article')->orderby('created', 'DESC')->find_all(10);


В результате мы получим объект типа ORM_Iterator. Для нас он будет выглядеть как массив объектов Article_Model, к нему можно применять все привычные нам методы работы с массивами. Например, выведем в цикле все заголовки полученных статей:

foreach($articles as $article) {
  echo $article->title, "<br />";
}


Чтобы получить количество записей в массиве, применяем count($articles) или $articles->count(). Можно экспортировать записи в обычный массив, для этого есть метод as_array(). Если интересуют только первичные ключи записей, есть удобный метод primary_key_array(), а если нужен массив из пар «поле1″=>»поле2″, используйте select_list($key, $val). Диапазон записей получается в результате работы метода range($start, $end), причем на выходе уже обычный массив, а не ORM_Iterator.

К сожалению, добавить к этому объекту еще несколько элементов не получится — он только для чтения.

Сериализация

Отдельно хотелось бы поговорить про сериализацию/десериализацию ORM-объектов. Сериализация происходит, когда мы сохраняем объект (в файл, в сессию или еще куда-то). Например, при кэшировании или при работе модуля Auth. Объект сохраняет важные данные в виде строки, предоставляя таким образом возможность позже загрузить этот объект без потери типа элементов и внутренней структуры. Перечень свойств, подлежащих сохранению, описывается в функции __sleep(), которая вызывается каждый раз перед сериализацией:

	// файл system/libraries/ORM.php
	public function __sleep()
	{
		// Store only information about the object
		return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting');
	}


Сохраняются свойства object_name — имя класса, object — собственно данные ORM-объекта (значения полей таблицы БД), changed — флаг наличия измененных данных (по сравнению с исходной записью в БД), уже известные нам loaded и saved, а также sorting (применяемые правила сортировки). Вы можете для отдельно взятой модели изменить данный состав сохраняемых полей, переопределив этот метод.

При десериализации данная запись загружается в ORM-объект. При этом вызова конструктора не происходит! Поэтому профиль БД лучше присваивать не в конструкторе, а сразу в объявлении класса:

protected $db = 'test';


Если посмотреть данные Profiler‘а, при десериализации может происходить запрос к БД с извлечением данных, вроде бы хранящихся в загружаемой нами строке. Для того, чтобы уточняющий запрос не производился, установите в модели свойство $reload_on_wakeup в FALSE.

Сериализовывать объект ORM_Iterator бессмысленно, т.к. он работает с ресурсом СУБД, который после завершения работы текущего скрипта разрушается.

Прочие методы и свойства

  • Метод as_array() преобразует ORM-объект в массив. Иногда это удобно при сохранении переменных в шаблоне, когда объекты уже не нужны.
  • Метод select_list($key, $val) в принципе аналогичен такому же методу класса ORM_Iterator. Указываем поле для получаемого ключа массива и поле для значений. Удобно использовать для формирования выпадающих списков. Например, получить пары id=>created можно так:

    $keys = ORM::factory('article')->select_list('id', 'created');


    По умолчанию в качестве ключа берется id (или имя поля, указанное в свойстве $primary_key), а в качестве значения — name (свойство $primary_val).

  • Свойство object_name — имя модели (без суффикса _Model и в нижнем регистре), т.е. то имя, которое мы исспользуем для передачи в метод ORM::factory().
  • Свойство table_name — имя таблицы БД. Если не указано явно в модели, вычисляется в конструкторе на основании $object_name.
  • Свойство table_columns — массив с информацией о полях БД (название, тип, доп. информация типа значения по умолчанию или auto_increment).

Для изменения значений по умолчанию, в модели можно переопределить следующий свойства:

  • $db — имя профиля БД, по умолчанию ‘default‘.
  • $primary_key — имя первичного ключа в таблице, по умолчанию ‘id’.
  • $primary_val — имя поля, характеризующего запись в таблице (например, title для статей, username для списка пользователей). По умолчанию ‘name‘.
  • $table_names_plural — флаг, указывающий как вычислять имя таблицы. Если TRUE (по умолчанию), то имя таблицы берется во множественном числе, иначе оно совпадает с $object_name.
  • $reload_on_wakeup — запрашивать ли заново данные из БД при десериализации. По умолчанию TRUE.
  • $ignored_columns — столбцы, которые не надо пытаться сохранять в БД (как правило они не существуют в таблице), но они могут быть загружены в модель, скажем при валидации (повторный ввод пароля). В виде массива, по умолчанию array().

Постскриптум

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

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

Опубликовано в cправочник.

Теги: , , .


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

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

  1. vgray пишет:

    спасибо.

    наконец, стало более/менее понятно про ORM. очень жду статьи про описание связей между таблицами.

  2. ukko-pekka.livejournal.com/ пишет:

    + 1 к связям.. Спасибо за статью. :)

  3. Валерий пишет:

    хорошая статейка

  4. riddi пишет:

    А есть метод для подсчета количества возможных элементов объекта? Например чтобы рассчитать LIMIT / OFFSET

  5. riddi пишет:

    А, вот нашел. count_all()

    Спасибо за статью!



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

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