- 1. Общая информация
- 2. Мапперы
- 3. Схема объекта
- 4. Хуки
- 5. Плагины
ORM в mzz предназначен для упрощения работы с данными в БД. Стандартные классы, входящие в ORM обладают минимально необходимым функционалом - CRUD (Create, Retrieve, Update, Delete).
ORM построен на базе The Data Mapper Pattern (рекомендуем ознакомиться со статьей перед продолжением чтения документации). Вкратце: в контектсте этого паттерна мы оперируем двумя терминами - маппер и доменный объект (ДО). Упрощённо: доменный объект - контейнер для данных, маппер - класс для заполнения ДО данными. Все действия по модификации также осуществляются через маппер.
Также отметим, что ДО является отображением данных приложения и данных в БД (в пределах 1 сеанса). Из этого следует, что до тех пор, пока объект не был сохранён специальным методом маппера, он будет выдавать "старые" данные (именно те, которые сейчас находятся в БД). Проиллюстрирую это на примере:
<?php // $news - Доменный Объект // $newsMapper - его маппер $news = $newsMapper->searchById(1); echo $news->getId(); // 1 echo $news->getTitle(); // "Заголовок для новости 1" $news->setTitle('Новый заголовок'); echo $news->getTitle(); // "Заголовок для новости 1" $newsMapper->save($news); echo $news->getTitle(); // "Новый заголовок" ?>
DO лежат в корневом каталоге каждого модуля, мапперы - в подкаталоге mappers.
Мапперы это активная составляющая реализации паттерна The Data Mapper pattern. Именно мапперы производят отображение записей базы данных на объекты приложения и обратно, организуют связи между объектами разных типов, различными средствами автоматизируют рутинные задачи.
Пример типичного маппера:
<?php /** * $URL: svn://svn.subversion.ru/usr/local/svn/mzz/trunk/docs/documentation/codes/structure.orm.mapper-1.php $ * * MZZ Content Management System (c) 2005-2007 * Website : http://www.mzz.ru * * This program is free software and released under * the GNU/GPL License (See /docs/GPL.txt). * * @link http://www.mzz.ru * @version $Id: structure.orm.mapper-1.php 3292 2009-06-02 09:57:30Z zerkms $ */ fileLoader::load('news'); fileLoader::load('orm/plugins/acl_extPlugin'); fileLoader::load('modules/comments/plugins/commentsPlugin'); fileLoader::load('modules/jip/plugins/jipPlugin'); fileLoader::load('modules/i18n/plugins/i18nPlugin'); /** * newsMapper: маппер для новостей * * @package modules * @subpackage news * @version 0.3 */ class newsMapper extends mapper { /** * Имя класса DataObject * * @var string */ protected $class = 'news'; protected $table = 'news_news'; protected $map = array( 'id' => array( 'accessor' => 'getId', 'mutator' => 'setId', 'options' => array( 'pk', 'once', ), ), 'folder_id' => array( 'accessor' => 'getFolder', 'mutator' => 'setFolder', 'relation' => 'one', 'foreign_key' => 'id', 'mapper' => 'news/newsFolderMapper' ), 'title' => array( 'accessor' => 'getTitle', 'mutator' => 'setTitle', 'options' => array( 'i18n', ), ), 'editor' => array( 'accessor' => 'getEditor', 'mutator' => 'setEditor', 'relation' => 'one', 'foreign_key' => 'id', 'mapper' => 'user/userMapper' ), 'annotation' => array( 'accessor' => 'getAnnotation', 'mutator' => 'setAnnotation', 'options' => array( 'i18n', ), ), 'text' => array( 'accessor' => 'getText', 'mutator' => 'setText', 'options' => array( 'i18n', ), ), 'created' => array( 'accessor' => 'getCreated', 'mutator' => 'setCreated', 'options' => array( 'once', ), ), 'updated' => array( 'accessor' => 'getUpdated', 'mutator' => 'setUpdated', ), ); public function __construct() { parent::__construct(); $this->plugins('acl_ext'); $this->plugins('jip'); $this->plugins('i18n'); $this->plugins('comments'); } protected function preInsert(& $data) { if (is_array($data)) { $data['updated'] = $data['created']; } } protected function preUpdate(& $data) { if (is_array($data)) { $data['updated'] = new sqlFunction('UNIX_TIMESTAMP'); } } /** * Выполняет поиск объектов по идентификатору каталога * * @param integer $id идентификатор папки * @return array */ public function searchByFolder($folder_id) { return $this->searchAllByField('folder_id', $folder_id); } public function convertArgsToObj($args) { if (isset($args['id'])) { $news = $this->searchByKey($args['id']); if ($news) { return $news; } } throw new mzzDONotFoundException(); } } ?>
В общем случае каждый маппер должен лишь переопределить 2 свойства:
- $table — имя таблицы БД
- $map — массив, представляющий собой правила наложения данных БД на объектную модель. (ссылка на подраздел Maps в этом же разделе)
Свойство маппера $map представляет собой набор правил наложения данных БД на объектную модель. Вот несколько упрощенный пример map:
protected $map = array( 'id' => array( 'accessor' => 'getId', 'mutator' => 'setId', 'options' => array( 'pk', 'once', ) ), 'title' => array( 'accessor' => 'getTitle', 'mutator' => 'setTitle' ) )
Ключами массива $map являются имена полей таблицы БД (которая задается в параметре $table маппера, ссылка). В общем случае у каждого поля должно быть описано имена двух методов — accessor и mutator.
Имя accessor'а имеет префикс "get" и используется для получения данных, которые хранятся в данном поле.
Mutator, соответственно, имеет префикс "set" и используется для сохранения данных в DO
У каждого поля может содержаться любое количество дополнительных параметров в ключе 'options'. Приведем примеры нескольких опций:
- pk — присутствие такой опции будет говорить о том, что данное поле является Первичным Ключом (Primary Key) для таблицы.
- once — поля с этой опцией будут доступны для сохранения только один раз при создании объекта. При попытке задать такое поле вторично, будет сгенерировано исключение
- ro — поле доступно только для чтения. При попытке задать значения через соответствующий мутатор, будет сгенерировано исключение.
Рассмотрим основные методы для работы с мапперами:
create() - создание объекта.
save($object) - сохранение объекта $object.
delete($id) - удаление объекта, в качестве идентификатора используется первичный ключ $id или сам объект.
Также для удобства имеется ряд методов для получения записей:
searchByKey($id) - поиск объекта по первичному ключу.
searchByKeys($id) - поиск объектов по первичным ключам. В качестве аргумента передаётся массив.
searchOneByField($field, $value) - поиск объекта по полю $field со значением $value.
searchAllByField($field, $value) - поиск объектов по полю $field со значение $value.
searchOneByCriteria(criteria $criteria) - поиск объекта по критерию.
searchAllByCriteria(criteria $criteria) - поиск объектов по критерию.
searchAll($orderCriteria = null) - выборка всех объектов. В качестве аргумента может быть передан критерий для сортировки.
Естественно, что в ваших мапперах вы можете расширить данный список методов для поиска. Так например метод searchByLogin (метод для поиска пользователя по его логину) будет выглядеть следующим образом:
<?php class userMapper extends simpleMapper { [...] /** * Выполняет поиск объекта по логину * * @param string $login логин * @return object */ public function searchByLogin($login) { return $this->searchOneByField('login', $login); } } ?>
Экранировать значения, передаваемые в аргументах, не нужно. За вас это сделает генератор запросов.
О работе с критериями можно узнать в соответствующем разделе.
В ORM mzz на основе паттерна The Observer реализована система хуков. Каждый из хуков вызывается после (или перед) определённого действия в маппере. Также в хуки передаются данные, собственно с которыми код хука и должен работать. Полный список хуков может быть уточнён вами в файле orm/observer.php. Пример работы маппера с хуками:
<?php class newsMapper extends mapper { /** * Имя класса DataObject * * @var string */ protected $class = 'news'; protected $table = 'news_news'; protected $map = array(...); [...] protected function preInsert(& $data) { if (is_array($data)) { $data['updated'] = $data['created']; } } protected function preUpdate(& $data) { if (is_array($data)) { $data['updated'] = new sqlFunction('UNIX_TIMESTAMP'); } } [...] } ?>
В этом коде продемонстрировано, как автоматически можно менять время изменения и создания публикации во время изменения и создания нового объекта новости соответственно.
Плагины предназначены для расширения функциональности мапперов без непосредственно их модификации. Функционал плагинов базируется на системе событий (на которой также построены хуки). Устройство плагинов рассмотрим на примере:
<?php class obj_idPlugin extends observer { protected $options = array( 'obj_id_field' => 'obj_id' ); protected function updateMap(& $map) { $map[$this->options['obj_id_field']] = array( 'accessor' => 'getObjId', 'mutator' => 'setObjId', 'options' => array('once') ); } public function postCreate(entity $object) { if (!$object->getObjId()) { $obj_id = systemToolkit::getInstance()->getObjectId(); $map = $this->mapper->map(); $object->{$map[$this->options['obj_id_field']]['mutator']}($obj_id); $this->mapper->save($object); } } public function preInsert(array & $data) { $data[$this->options['obj_id_field']] = systemToolkit::getInstance()->getObjectId(); } } ?>
Этот плагин добавляет в маппер возможность работать с полем obj_id (оно активно используется в acl и некоторых других задачах, когда нужно уникально в пределах приложения определить объект). В этом плагине определено свойство $options, в котором хранится имя поля, в котором будет собственно сам уникальный счётчик. Метод updateMap будет автоматически вызван классом-предком observer и в качестве аргумента будет передана схема объекта, в которую, как видно из кода, будет добавлен новый метод.
Событие postCreate вызывается сразу после создания объекта. В обработчике проверяется, есть ли уже какое-то значение obj_id для данного объекта. Если нет - генерируется новое значение, передаётся объекту, после чего объект сохраняется. Таким образом это событие используется в случае, когда плагин подключен для уже имеющегося набора данных, для которого ещё не определены obj_id. Событие preInsert предназначено для объектов, которые только создаются - в этом случае уникальный номер генерируется и сразу передаётся в массив данных объекта.
Подключение плагинов происходит с помощью двух методов маппера:
$this->plugins('obj_id');
Это простой метод подключения, в котором невозможно задать различные настройки плагину (если он их подразумевает). Второй метод:
$this->attach(new tree_mpPlugin(array('path_name' => 'id')));
В этом случае аргументом является объект плагина.
Общесистемные плагины располагаются в директории system/orm/plugins. Местоположение плагинов модулей жёстко не декларируется, но предпочтительно их размещать в поддиректории plugins директории с модулями.
В настоящее время вместе с mzz поставляются следующие плагины:
obj_id— плагин, позволяющий добавить в объекты поле obj_id для уникальной идентификации в приложении;acl_ext— плагин acl, расширенная версия. Позволяет разграничивать права в приложении с точностью до объекта;acl_simple— плагин acl, упрощённая версия. Позволяет разграничивать права в приложении с точностью до типа объекта;tree_mp— плагин для работы с древовидными структурами. Иерархическая структура хранится в виде Materialized Path;pager— плагин для постраничного вывода коллекций объектов. Вручную подключать его в маппер вам вряд ли понадобиться. Ознакомиться с этим плагином Вы можете в соответствующем разделе документации.