MZZ.Framework 0.3.x: Документация
Разделы

3.6 ORM

1. Общая информация
2. Мапперы
3. Схема объекта
4. Хуки
5. Плагины
1. Общая информация

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.

2. Мапперы

Мапперы это активная составляющая реализации паттерна 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 свойства:

Следует также обратить внимание на переменную $class. Эта переменная является опциональной и представляет собой имя класса, с которым работает данный маппер (в нашем случае это news). Если эта переменная не задана явно, то за имя класса берется имя таблицы.

3. Схема объекта

Свойство маппера $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'. Приведем примеры нескольких опций:

 

Рассмотрим основные методы для работы с мапперами:

Также для удобства имеется ряд методов для получения записей:

Естественно, что в ваших мапперах вы можете расширить данный список методов для поиска. Так например метод searchByLogin (метод для поиска пользователя по его логину) будет выглядеть следующим образом:

<?php
 
class userMapper extends simpleMapper
{
    [...]
    /**
     * Выполняет поиск объекта по логину
     *
     * @param string $login логин
     * @return object
     */
    public function searchByLogin($login)
    {
        return $this->searchOneByField('login', $login);
    }
}
 
?>

Экранировать значения, передаваемые в аргументах, не нужно. За вас это сделает генератор запросов.

О работе с критериями можно узнать в соответствующем разделе.

4. Хуки

В 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');
        }
    }
 
    [...]
}
 
?>

В этом коде продемонстрировано, как автоматически можно менять время изменения и создания публикации во время изменения и создания нового объекта новости соответственно.

5. Плагины

Плагины предназначены для расширения функциональности мапперов без непосредственно их модификации. Функционал плагинов базируется на системе событий (на которой также построены хуки). Устройство плагинов рассмотрим на примере:

<?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 поставляются следующие плагины: