Версия: 3.x
ORM объекты

Подсистема ORM (Object Relational Mapping) является важной составляющей системы ReadyScript. Данная подсистема предоставляет возможность абстрагироваться от базы данных и работать в PHP с сущностями в виде объектов, имеющими возможность сохранять и загружать свои свойства.

Пример работы с ORM-объектом 1:

$user = new User();
$user['name'] = 'Иван';
$user['surname'] = 'Петров';
$user['midname'] = 'Петрович';
$user->insert();
echo $user['id']; //Выведет: id только что добавленной записи
echo $user->getFio(); //Выведет: Петров Иван Петрович

Пример работы с ORM-объектом 2:

$user = new User(1); //Загрузит объект с id = 1
echo $user->getFio(); //Выведет Фамилию Имя Отчество пользователя с id = 1

Пример работы с ORM-объектом 3:

$user = User::loadByWhere( array('name' => 'Иван', 'surname' => 'Петров') ); //Загрузит объект по параметрам
echo $user['midname']; //Выведет Отчество

Пример работы с ORM-объектом 4:

$user = new User();
if ($user->load(1)) { //Загрузит объект с id = 1
$user['name'] => 'Федор'; //Изменяет имя у пользователя
$user->update(); //Обновляет запись
}

Абсолютно все сущности ReadyScript, хранящиеся в базе данных доступны для использования в PHP через соответствующий ORM-объект, например: пункт меню, товар, характеристика товара, связь товара и характеристики, мета теги для одной страницы, и.т.д.

Объявление класса ORM-объектов

Объектом ORM считается экземпляр класса, наследника RS::Orm::AbstractObject.

Пример простейшего объекта, описывающего пользователя:

// Класс объектов "пользователь"
class User extends \RS\Orm\AbstractObject
{
protected static
$table = "users"; //Имя таблицы в БД
// Инициализирует свойства ORM-объектов
// @return \RS\Orm\PropertyIterator
protected function _init()
{
return $this->getPropertyIterator()->append(array(
//Описываются поля(свойства) id, name, surname, midname, присутствующие в таблице БД
'id' => new \RS\Orm\Type\Integer(array(
'primaryKey' => true,
'autoincrement' => true
)),
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя'
)),
'surname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Фамилия'
)),
'midname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Отчество'
)),
));
}
//Возвращает свойство, являющееся первичным ключем
//@return string
public function getPrimaryKeyProperty()
{
return 'id';
}
}

Согласно принятым в ReadyScript правилам, например, в поле "id" после объявления массива в конструкторе RS::Orm::Type::Integer будут вызваны методы: setPrimaryKey(true), setAutoincrement(true), таким образом в современной IDE всегда можно узнать полный список возможных ключей массива, просто посмотрев методы соответствующего класса с префиксами set и add.

Cведения, описывающие поля объекта должны располагаться в методе _init. Данный метод вызывается только один раз для одного класса объектов в течении срока жизни скрипта. Вызов происходит при первом обращении к объекту поля ($orm['__field']). Таким образом для загрузки объекта и простого чтения значения свойства ($orm['field']), _init не вызывается.

Для хранения списка полей используется объект класса RS::Orm::PropertyIterator. Таким образом все объекты класса User будут использовать один экземпляр класса RS::Orm::PropertyIterator. Заданные у объекта поля являются по сути колонками в таблице БД. При сохранении ORM-объекта данные будут соответственно названиям полей раскинуты по колонкам таблицы, при загрузке ORM-объекта все произойдет с точностью до наоборот. Некоторые поля можно не связывать с БД, для этого достаточно объявить у поля свойство runtime => true (RS::Orm::Type::AbstractType::setRuntime)

В случае, если необходимо инициализировать значения переменных объекта по-умолчанию, необходимо перегрузить метод _initDefaults. Этот метод в отличие от метода _init вызывается при создании каждого объекта.

class User extends \RS\Orm\AbstractObject
{
....
protected function _initDefaults()
{
$this['name'] = 'Значение по-умолчанию, для только что созданного объекта';
$this['surname'] = 'Значение по-умолчанию, для только что созданного объекта';
}
}

Наследование и расширение объектов ORM

В ООП принято общие свойства и общее поведение (группы методов) выносить в родительские классы, а более частное поведение реализовывать в наследниках. Применительно к ORM-объектам мы можем определить в родительских классах помимо методов, еще и общие поля. Например, у многих объектов есть поле id, так зачем же его описывать в каждом объекте? Гораздо эффективнее объявить его единожды. В ReadyScript имеется класс RS::Orm::OrmObject, который содержит в себе объявление поля id, который рекомендуется использовать в качестве родительского в изложенном выше случае.

Пример наследования объектов и расширения списка полей объектов:

class OrmObject extends AbstractObject
{
// Объявляет поле ID у объектов
// @return \RS\Orm\PropertyIterator
protected function _init() //инициализация полей класса. конструктор метаданных
{
return $this->getPropertyIterator()->append(array(
'id' => new Type\Integer(array(
'autoincrement' => true,
'primaryKey' => true, //Первичный ключ
'visible' => false //Не будет использоваться при генерации формы
))
));
//Возвращает свойство, являющееся первичным ключем
//@return string
public function getPrimaryKeyProperty()
{
return 'id';
}
}
}
//Создаем наследника
class MyObject extends OrmObject
{
protected static
$table = 'myobject'; //объявляем таблицу БД, в которой будут храниться значения свойств объектов ORM
protected function _init()
{
return parent::_init()->append(array(
//здесь объявляем остальные свойства
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя'
)),
//....
));
}
}

В данном примере класс MyObject расширяет класс OrmObject, добавляя ещё одно поле name, которое будет связано с одноименным полем в таблице БД.

Расширять список свойств ORM-объектов можно также с помощью обработки события orm.init.... Этот вариант удобно использовать, когда необходимо из одного модуля изменить состав полей ORM-объекта другого модуля.

Возможные типы полей

Каждый объект RS::Orm::Type..... ,характеризующий поле, имеет соответствующий тип в MySql. В системе присутствуют как простые типы, имеющие прямую связь с базой, например(в скобках указано название типа в БД): RS::Orm::Type::Varchar (VARCHAR), RS::Orm::Type::Blob(BLOB), RS::Orm::Type::Integer (INT), и.т.д., так и сложные типы, которые выполняют ряд действий перед сохранением и возвращают для записи в базу простое значение. К сложным полям относятся: RS::Orm::Type::File, RS::Orm::Type::Image, RS::Orm::Type::User, и др. Сложные поля могут также иметь сложное отображение в автоматически сгенерированных формах.

Тип в PHP Тип в MySQL Описание
RS::Orm::Type::Varchar VARCHAR Испльзуется для хранения строковых значений
RS::Orm::Type::Integer INT Испльзуется для хранения целочисленных значений
RS::Orm::Type::Real FLOAT Испльзуется для хранения чисел с плавающей точкой
RS::Orm::Type::Blob BLOB Испльзуется для хранения бинарных данных до 64 Kb
RS::Orm::Type::Mediumblob MEDIUMBLOB Используется для хранения бинарных данных до 16 Mb
RS::Orm::Type::Text MEDIUMTEXT Используеся для хранения текстовых данных
RS::Orm::Type::Enum ENUM Используеся для хранения значений из заранее известного списка
RS::Orm::Type::Richtext MEDIUMTEXT Используеся для хранения текстовых данных, отображается в форме в оболочке WYSIWYG редактора
RS::Orm::Type::User INT Сложное поле. Используется для хранения ID пользователя. Отображается в форме в виде поля с поиском пользователя по логину, фамилии, e-mail'у
RS::Orm::Type::Color VARCHAR Используеся для хранения значений цвета. Отображается в форме в виде поля с возможностью выбрать цвет из диалога
RS::Orm::Type::CurrentSite INT Исользуется для хранения ID сайта.
RS::Orm::Type::Datetime DATETIME Использется для хранения даты и времени
RS::Orm::Type::Decimal DECIMAL Используется для хранения денежных велечин, цен
RS::Orm::Type::File VARCHAR Сложное поле. Используется для хранения имени файла. Отображается в форме в виде поля для загрузки файла
RS::Orm::Type::Image VARCHAR Сложное поле. Используется для хранения информации об изображении. Отображается в форме в виде поля для загрузки файла-изображения
RS::Orm::Type::Timestamp TIMESAMP Используется для хранения временной метки
RS::Orm::Type::Hidden - Runtime поле. Отображается в форме в виде скрытого элемента input[type="hidden"]
RS::Orm::Type::Mixed - Runtime поле. Используется для хранения временных значений у объекта
RS::Orm::Type::ArrayList - Runtime поле. Используется для промежуточного хранения массивов
RS::Orm::Type::Captcha - Runtime поле. Отображается в форме в виде Капчи (картинки с цифрами) и формы для ввода этих цифр
RS::Orm::Type::UserTemplate - Runtime поле. Используется для отображения произвольного шаблона в форме

Синхронизация структуры БД

При описании класса объектов ORM, задаются все сведения, описывающие поля в базе данных. Это делается для того, чтобы объект мог создавать и обновлять структуру соответствующей таблицы в БД. Во время синхронизации недостающие поля будут добавлены в таблицу БД запросом ALTER TABLE..... В случае, если во время синхронизации в таблице БД присутствуют поля, не объявленные в ORM-объекте, эти поля останутся нетронутыми.

Используя метод RS::Orm::AbstractObject::addIndex можно также описывать простые или составные индексы, которые также будут синхронизироваться с базой данных. Индексы, не присутствующие в описании ORM-объектов будут удалены при синхронизации, т.к. наличие "лишних" индексов может негативно сказаться на производительности системы.

class User extends \RS\Orm\AbstractObject
{
protected static
$table = "users"; //Имя таблицы в БД
// Инициализирует свойства ORM-объектов
// @return \RS\Orm\PropertyIterator
protected function _init()
{
$plist = $this->getPropertyIterator()->append(array(
//Описываются поля(свойства) id, name, surname, midname, присутствующие в таблице БД
'id' => new \RS\Orm\Type\Integer(array(
'primaryKey' => true,
'autoincrement' => true
)),
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя'
)),
'surname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Фамилия'
)),
'midname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Отчество'
)),
));
$this->addIndex(array('name', 'surname'), self::INDEX_FULLTEXT);
return $plist;
}
}

Одиночные индексы также можно описывать при объявлении полей, например:

class User extends \RS\Orm\AbstractObject
{
....
protected function _init()
{
$plist = $this->getPropertyIterator()->append(array(
//Описываются поля(свойства) id, name, surname, midname, присутствующие в таблице БД
'id' => new \RS\Orm\Type\Integer(array(
'primaryKey' => true, //Будет создан первичный ключ
'autoincrement' => true
)),
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя',
'index' => true //Будет создан обычный BTREE индекс
)),
'surname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Фамилия',
'unique' => true //Будет создан UNIQUE индекс
))
));
return $plist;
}
}

Для синхронизации базы данных используется метод RS::Orm::AbstractObject::dbUpdate.

$user = new User();
$user->dbUpdate(); //Обновит структуру БД. Проверит наличие и соответствие полей id, name, surname, midname. Произведет проверку наличия полнотекстового индекса.

Группировка полей. Генерация формы.

Некоторые поля объектов можно организовывать в группы. Группы будут отображаться в форме редактирования объекта в виде закладок. Для определения группы, достаточно добавить текстовый элемент без указания ключа перед описанием поля.

class User extends \RS\Orm\AbstractObject
{
protected static
$table = "users"; //Имя таблицы в БД
protected function _init()
{
return $this->getPropertyIterator()->append(array(
'Персональные данные', //Объявляем группу
'id' => new \RS\Orm\Type\Integer(array(
'primaryKey' => true,
'autoincrement' => true
)),
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя'
)),
'surname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Фамилия'
)),
'midname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Отчество'
)),
'Расширенные сведения', //Объявляем группу
'years' => new \RS\Orm\Type\Integer(array(
'description' => 'Возраст'
))
));
}
}

Генерация формы объекта

Любой ORM-объект способен генерировать для себя HTML форму с помощью метода RS::Orm::AbstractObject::getForm(). Метод имеет следующие аргументы:

  • $tpl_vars - array, необязательный, дополнительные параметры, передаваемые в шаблон
  • $switch - string, необязательный, контекст, в котором будет генерироваться форма. Позволяет скрывать какие-либо поля в зависимости от контекста
  • $is_multiedit - bool, необязательный, если true, то это форма мультиредактирования
  • $template - string, необязательный, имя файла генерируемого шаблона
  • $tpl_maker - string, необязательный, имя шаблона, по которому будет произведена генерация
  • $tpl_folder - string, необязательный, каталог для генерации шаблона

Генерация шаблона произойдет только в случае, если шаблона объекта еще не существует(файл не будет найден).

$user = new User();
echo $user->getForm();

Будет сгенерирован следующий шаблон:

1 <div class="formbox" >
2  <div class="tabs">
3  <ul class="tab-container">
4  <li class=" act first"><a data-view="tab0">{$elem->getPropertyIterator()->getGroupName(0)}</a></li>
5  <li class=""><a data-view="tab1">{$elem->getPropertyIterator()->getGroupName(1)}</a></li>
6  </ul>
7  <form method="POST" action="{urlmake}" enctype="multipart/form-data" class="crud-form">
8  <input type="submit" value="" style="display:none">
9 
10  <div class="frame" data-name="tab0">
11  <table class="otable">
12  <tr>
13  <td class="otitle">{$elem.__id->getTitle()}</td>
14  <td>{include file=$elem.__id->getRenderTemplate() field=$elem.__id}</td>
15  </tr>
16  <tr>
17  <td class="otitle">{$elem.__name->getTitle()}</td>
18  <td>{include file=$elem.__name->getRenderTemplate() field=$elem.__name}</td>
19  </tr>
20  <tr>
21  <td class="otitle">{$elem.__surname->getTitle()}</td>
22  <td>{include file=$elem.__surname->getRenderTemplate() field=$elem.__surname}</td>
23  </tr>
24  <tr>
25  <td class="otitle">{$elem.__midname->getTitle()}</td>
26  <td>{include file=$elem.__midname->getRenderTemplate() field=$elem.__midname}</td>
27  </tr>
28  </table>
29  </div>
30 
31  <div class="frame nodisp" data-name="tab1">
32  <table class="otable">
33  <tr>
34  <td class="otitle">{$elem.__years->getTitle()}</td>
35  <td>{include file=$elem.__years->getRenderTemplate() field=$elem.__years}</td>
36  </tr>
37  </table>
38  </div>
39  </form>
40  </div>
41 </div>

getForm - это низкоуровневый метод для генерации формы объекта. В интерфейсах административной части рекомендуется использовать контроллер, расширяющий класс RS::Controller::Admin::Crud. Такой контроллер берет на себя и формирование форм и обработку POST данных этих форм, причем как в обычном режиме, так и в режиме AJAX.

В ReadyScript помимо возможности сформировать форму для целого объекта есть возможность сгенерировать форму одного поля. Для этого существует метод RS::Orm::AbstractObject::getPropertyView(). Метод имеет следующие аргументы:

  • $key - string, обязательное, имя поля
  • $attributes - array, необязательное, аттрибуты, которые будут добавлены HTML элементу
  • $view_params - array, необязательное, параметры отображения. По умолчанию, с формой отображается и поле с ошибкой, если ошибка существует. С помощью этого параметра можно указывать какие HTML элементы нужно сгенерировать. Возможные варианты: ['form' => true, 'error' => true]. Учет этих параметров идет в шаблоне /system/coreobject/prop_form.tpl

Пример:

$user = new User();
$user['name'] = 'example';
echo $user->getPropertyView('name', array('class' => 'userclass'), array('form' => true));

Будет сгенерирована форма:

1 <input name="name" value="example" maxlength="100" size="25" type="text" class="userclass" />

Проверка значений полей

Контроллировать целостность данных можно с помощью прикрепления чекеров, специальных функций проверки, к полям. Для этого у каждого объекта поля есть метод setChecker(), который принимает следующие аргументы:

  • $callmethod - callback, обязательный. Callback, который будет вызван для проверки поля. Если будет передана строка, то это будет означать имя метода в классе со стандартными чекерами RS::Orm::Type::Checker.
    • chkAlias - Проверяет на валидность значения поля "псевдоним". Значение должно соответствовать маске: ^([a-zA-Z0-9-_])*$
    • chkEmpty - Проверяет значение поля PHP-методом empty. Поле не должно быть пустым.
    • chkPattern - Проверяет значение поля по маске регулярного выражения. Значение должно соответствовать маске.
    • chkCaptcha - Сравнивает значение поля с последним отображавшимся числом в капче.
    • chkNoselect - Возвращает ошибку, если значение равно -1. Удобно использовать для проверки селекторов на значение "не выбрано".
    • chkMinmax - Проверяет значение числового поля. Оно должно быть в рамках min и max
    • chkEmail - Проверяет значение поля на соответствие маски Email-адреса.
  • $errortxt - string, обязательный, текст ошибки
  • .... дополнительные аргументы, передаваемые в чекер. См. аргументы чекера, чтобы понять какие доп. параметры необходимо передать.

Для одного свойства может быть назначено и более одного чекера. В этом случае, чекеры будут выполняться последовательно, если в какой-то из них вернет ошибку, то последующие чекеры для данного свойства выполняться не будут.

Добавлять чекеры к полям можно с помощью сокращенного синтаксиса.

class User extends \RS\Orm\AbstractObject
{
protected static
$table = "myusers"; //Имя таблицы в БД
protected function _init()
{
return $this->getPropertyIterator()->append(array(
'id' => new \RS\Orm\Type\Integer(array(
'primaryKey' => true,
'autoincrement' => true
)),
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя',
'checker' => array('chkPattern', 'Имя должно быть не менее 3-х символов', '/^.{3,}$/'),
' checker' => array( //Устанавливаем второй чекер. В ключе добавляем пробел слева, чтобы ключи не склеивались.
//Чекер - Closure (замыкание)
function($_this, $value) {
return $value == 'Артем' ? 'Имя не может быть - Артем' : true;
}
),
)),
'surname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Фамилия',
'checker' => array('chkEmpty', 'Фамилия - обязательное поле')
)),
'midname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Отчество',
'checker' => array(array(__CLASS__, 'checkMidname'), 'Имя не может равным отчеству!')
))
));
}
//Чекер, который владеет значениями других полей
public static function checkMidname($_this, $value, $error_text)
{
if ($_this['name'] == $value) {
return $error_text;
}
return true;
}
}
$user = new User();
$user['name'] = 'Артем';
$user['midname'] = 'Артем';
var_dump($user->validate()); //Вернет false
var_dump($user->getErrors()); //Вернет: array(3) { [0]=> string(44) "Имя не может быть - Артем"
// [1]=> string(50) "Фамилия - обязательное поле"
// [2]=> string(53) "Имя не может равным отчеству!" }

В функцию проверки(чекер) поступают следующие аргументы:

  • $orm_object - ORM-объект, которому принадлежит поле. Через это свойство можно узнать значение других полей.
  • $value - значение поля, которому принадлежит чекер
  • $error_text - текст ошибки, установленный вторым аргументом метода RS::Orm::Type::AbstractType::setChecker
  • ... - остальные аргументы, переданные в метод RS::Orm::Type::AbstractType::setChecker

Ожидается, что функция проверки вернет:

  • bool true, в случае, если нет ошибок
  • иначе string с текстом ошибки

Проверка полей автоматически происходит при каждой попытке сохранить или обновить объект через метод RS::Orm::AbstractObject::save(). Чтобы проверить корректность текущих значений полей можно воспользоваться методом RS::Orm::AbstractObject::validate().

Кастомизация внешнего вида полей

Первично внешний вид HTML-формы поля зависит от его класса. Именно в классе указан шаблон по-умолчанию, который будет использован для генерации формы.

  • RS::Orm::Type::Text - отображается в виде HTML элемента <textarea>.
  • RS::Orm::Type::Varchar - отображается в виде HTML элемента <input type="text">
  • RS::Orm::Type::File - отображается в виде HTML элемента <input type="file">
  • и.т.д.

Но также существует несколько модификаторов, которые изменяют внешний вид полей.

Если указать список значений поля с помощью методов RS::Orm::Type::AbstractObject::setList или RS::Orm::Type::AbstractObject::setListFromArray, то шаблон формы будет изменен и поле отобразится в виде HTML элемента <select>.

class User extends \RS\Orm\OrmObject
{
protected static
$table = "myusers"; //Имя таблицы в БД
protected function _init()
{
return parent::_init()->append(array(
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя',
'listFromArray' => array(array( //Подключаем справочник к полю
'Артем',
'Иван',
'Федор'
))
)),
'surname' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Фамилия',
'list' => array(array(__CLASS__, 'getSurnames')) //Указываем callback для загрузки справочника
)),
));
}
//callback, который будет вызван во время генерации формы поля surname
public static function getSurnames()
{
return array(
'Иванов',
'Петров',
'Сидоров'
);
}
}

Изменить внешний вид на checkbox можно с помощью метода RS::Orm::Type::AbstractObject::setCheckboxView(), у которого имеются аргументы:

  • $on_value - значение отмеченного checkbox
  • $off_value - значение не отмеченного checkbox
class User extends \RS\Orm\OrmObject
{
//...
protected function _init()
{
return parent::_init()->append(array(
'is_man' => new \RS\Orm\Type\Integer(array(
'description' => 'Это мужчина?',
'checkboxView' => array(1,0) //1 - onValue, 0 - offValue
))
));
}
//...
}

Установить свой собственный шаблон формы поля для обычного редактирования и группового редактирования можно с помощью методов RS::Orm::Type::AbstractObject::setTemplate() и RS::Orm::Type::AbstractObject::setMeTemplate() соответственно.

namespace ModuleName\Model\Orm;
class User extends \RS\Orm\OrmObject
{
//...
protected function _init()
{
return parent::_init()->append(array(
'name' => new \RS\Orm\Type\Varchar(array(
'maxLength' => '100',
'description' => 'Имя',
'template' => '%modulename%/field_name_form.tpl'
))
));
}
}

Рассмотрим, какие переменные поступают в шаблон формы.

Пример файла field_name_form.tpl:

{*
$elem - ORM Объект
$field - объект поля
*}
<p>Это произвольная форма поля</p>
<span class="wrapper">
{include file=$field->getOriginalTemplate()} {* Подключаем стандартную форму текущего поля*}
</span>
{* В этом шаблоне можно вывести и формы других полей, например: *}
{include file=$elem.__surname->getRenderTemplate() field=$elem.__surname}

Размещение произвольного шаблона на отдельной закладке

Иногда возникает необходимость разместить абсолютно произвольный HTML-код на отдельной закладке формы ORM-объекта. Для этой цели необходимо описать поле с типом RS::Orm::Type::UserTemplate.

Заметки
На одной закладке может быть размещена либо таблица с полями, либо шаблон, установленый в поле RS::Orm::Type::UserTemplate. Таким образом, наличие поля RS::Orm::Type::UserTemplate в группе, отключит возможность отображения других полей на этой же закладке.
namespace ModuleName\Model\Orm;
class User extends \RS\Orm\OrmObject
{
//...
protected function _init()
{
return parent::_init()->append(array(
'Основные сведения',
'name' => new \RS\Orm\Type\Varchar(array(
'description' => 'Имя',
)),
'Дополнительные параметры',
'_property_' => new Type\UserTemplate('%modulename%/properties.tpl'),
));
}
}

Сохранение объекта из POST

Если идет POST запрос из формы ORM объекта, то достаточно выполнить метод save, чтобы объект был создан или обновлен. Метод имеет следующие параметры:

  • $primaryKeyValue - mixed, необязательный, - заполняется, если необходимо обновить объект
  • $user_post - array, необязательный, массив который добавляется с замещением к массиву с POST данными.
  • $post_var - array, необязательный, массив с POST данными. Если null, то используется массив $_POST.
  • $files_var - array, необязательный, массив с FILES данными. Если null, то используется массив $_FILES

Полям ORM-объекта устанавливаются занчения с идентичным ключем из массива POST данных. Чтобы запретить какому-либо полю устанавливать значения из POST, необходимо у этого поля вызвать метод RS::Orm::Type::AbstractType::setUseToSave(false);

class User extends \RS\Orm\OrmObject
{
//...
protected function _init()
{
return parent::_init()->append(array(
'name' => new \RS\Orm\Type\Varchar(array(
'description' => 'Имя',
'useToSave' => false //Не будет заполняться из POST, во время сохранения
))
));
}
}

Для ограничения полей, заполняемых из POST, также существует метод RS::Orm::AbstractObject::usePostKeys().

Пример, как может выглядеть код сохранения ORM-объекта в контроллере

class ExampleController extends \RS\Controller\Front
{
function actionIndex()
{
$id = $this->url->request('id', TYPE_INTEGER) ?: null; //Если будет передан ID, то произойдет обновление объекта, иначе создание
$article = new \Article\Model\Orm\Article($id);
//Можно ограничить список полей, которые будут заполняться из POST. Остальные поля будут нетронуты,
//соответственно они не будут присутствовать в SQL запросе, а значит в базе останется default значение колонки.
//$article->usePostKeys(array('title', 'alias', 'content', 'parent', 'dateof', 'image', 'short_content'));
if ($this->url->isPost()) { //Идет POST запрос
if ($article->save($id)) {
echo "Объект успешно сохранен!";
} else {
//Произошла ошибка сохранения данных
print_r($article->getFormError());
}
}
return $this->result->setTemplate( 'article_form.tpl' );
}
}

События ORM-объектов

ORM объекты генерируют следующие события:

Идентификатор события Правила формирования идентификатора Тип параметра Когда происходит
orm.init.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - формируется из имени класса контроллера включая namesapce, путем замены обратных слешей на минусы, и исключения секции -model-orm. Минусы по краям удаляются. Вся строка приводится к нижнему регистру. Например, если имя контроллера: \Catalog\Model\Orm\Product, то его сокращенное имя будет: catalog-product RS::Orm::AbstractObject Инициализируемый ORM объект. Вызывается после вызова метода init у ORM объектов. Данное событие может использоваться для изменения структуры полей ORM объекта
orm.beforewrite.КОРОТКОЕИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
  • flag - флаг опреации. Может принимать значения: insert, update, delete
  • on_duplicate_update_keys - поля, которые необходимо обновить в случае если запись уже существует
Вызывается перед записью ORM объекта в базу данных
orm.afterwrite.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
  • flag - флаг опреации. Может принимать значения: insert, update, delete
  • on_duplicate_update_keys - поля, которые необходимо обновить в случае если запись уже существует
Вызывается после записи ORM объекта в базу данных
orm.afterload.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
Вызывается сразу после загрузки объекта
orm.delete.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
Вызывается перед удалением ORM объекта из базы данных

Пример расширения ORM-объекта с помощью события orm.init

namespace ModuleName\Config;
class Handlers extends \RS\Event\HandlerAbstract
{
function init()
{
$this->bind('orm.init.catalog-product');
}
// Обработчик события "Инициализация ORM объекта Товар".
// Не забудьте переустановить модуль каталог через меню Веб-сайт->Настройка модулей. Каталог товаров -> переустановить
//
// @param \Catalog\Model\Orm\Product
// @return void
public static function ormInitCatalogProduct(\Catalog\Model\Orm\Product $orm_product)
{
$orm_product->getPropertyIterator()->append(array( //Добавляем свойства к объекту
'Новая закладка', //Закладка, появится в форме редактирования товара
'test_property' => new \RS\Orm\Type\Integer(array( //Тип поля. Задает тип в базе INT
'maxLength' => 1, // Длина поля в базе будет INT(1)
'description' => 'Тестовый флаг', //Название поля
'checkboxView' => array(1,0), //1 - значение отмеченного checkbox, 0 - для неотмеченного
//также здесь можно:
//- задавать произвольный шаблон для отображения данного свойства. см. методы \RS\Orm\Type\Integer
//- менять общий шаблон формы товара. см. методы \RS\Orm\AbstractObject
))
));
}
}

Расширение поведения ORM объектов

ORM объекты имплементируют интерфейс RS::Behaior::AcceptBehaviorInterface, что позволяет добавлять методы к ORM объектам из сторонних модулей. Подробнее о расширении поведения написано здесь.

Примеры работы с объектами ORM

ORM объект имплементирует интерфейсы ArrayAccess, Iterator, что позволяет работать с ним также как с массивом.

$ormObject = new User();
$ormObject['name'] = 'Имя';
$ormObject['surname'] = 'Фамилия';
foreach($ormObject as $key => $property) {
echo $key.' = '.$property->get(); //$key - в данном случае имя свойства, $property - объект свойства \RS\Orm\Type\......
}
//Выведет:
// name = Имя
// surname = Фамилия
foreach($ormObject->getValues() as $key => $value ) {
echo $key.' = '.$value; //$key - в данном случае имя свойства, $value - значение свойства
}
//Выведет:
// name = Имя
// surname = Фамилия

Часто при написании модуей импорта данных, требуется выполнять запросы вида INSERT INTO ... VALUES(field1,filed2,field3) ON DUPLICATE KEY UPDATE field2 = VALUES(field2), field3 = VALUES(field3) Такая конструкция позволяет вставлять записи, а в случае наличия записи с такими же данными по уникальным ключам, производить обновление некоторых данных. По сути вставка или обновление происходит в 1 запрос, что существенно лучше, чем пытаться предварительно загрузить запись по уникальным ключам и уже потом принимать решение вставлять или обновлять запись. Возможность выполнения таких запросов предусмотрена в ORM объектах.

$ormObject = new \Catalog\Model\Orm\Product();
//xml_id поле в базе с уникальным индексом
$ormObject['xml_id'] = '314b5cb6';
$ormObject['barcode'] = 'NEW-ARTICUL';
$ormObject['title'] = 'Новое название товара';
$ormObject->insert(false, //Не использовать insert ignore
array('barcode', 'title'), //Обновлять поля, если такой товар уже есть в базе
array('xml_id') //поля, которые входят в уникальный индекс. Будут использованы для загрузки
);
//Будет выполнен запрос:
// INSERT INTO product('xml_id', 'barcode', 'title')
// VALUES('314b5cb6', 'NEW-ARTICUL', 'Новое название товара')
// ON DUPLICATE KEY UPDATE `barcode` = VALUES(barcode), `title` = VALUES(title)
//
//В случае если будет произведено обновление записи, ReadyScript выполнит запрос на загрузку объекта:
//SELECT * FROM product WHERE `xml_id` = '314b5cb6'
echo $ormObject['id']; //Выведет ID новой или уже имеющейся в базе записи