Подсистема ORM (Object Relational Mapping) является важной составляющей системы ReadyScript. Данная подсистема предоставляет возможность абстрагироваться от базы данных и работать в PHP с сущностями в виде объектов, имеющими возможность сохранять и загружать свои свойства.
Пример работы с ORM-объектом 1:
Пример работы с ORM-объектом 2:
Пример работы с ORM-объектом 3:
Пример работы с ORM-объектом 4:
Абсолютно все сущности ReadyScript, хранящиеся в базе данных доступны для использования в PHP через соответствующий ORM-объект, например: пункт меню, товар, характеристика товара, связь товара и характеристики, мета теги для одной страницы, и.т.д.
Объектом ORM считается экземпляр класса, наследника RS::Orm::AbstractObject.
Пример простейшего объекта, описывающего пользователя:
Согласно принятым в 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 вызывается при создании каждого объекта.
В ООП принято общие свойства и общее поведение (группы методов) выносить в родительские классы, а более частное поведение реализовывать в наследниках. Применительно к ORM-объектам мы можем определить в родительских классах помимо методов, еще и общие поля. Например, у многих объектов есть поле id, так зачем же его описывать в каждом объекте? Гораздо эффективнее объявить его единожды. В ReadyScript имеется класс RS::Orm::OrmObject, который содержит в себе объявление поля id, который рекомендуется использовать в качестве родительского в изложенном выше случае.
Пример наследования объектов и расширения списка полей объектов:
В данном примере класс 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-объектов будут удалены при синхронизации, т.к. наличие "лишних" индексов может негативно сказаться на производительности системы.
Одиночные индексы также можно описывать при объявлении полей, например:
Для синхронизации базы данных используется метод RS::Orm::AbstractObject::dbUpdate.
Некоторые поля объектов можно организовывать в группы. Группы будут отображаться в форме редактирования объекта в виде закладок. Для определения группы, достаточно добавить текстовый элемент без указания ключа перед описанием поля.
Любой ORM-объект способен генерировать для себя HTML форму с помощью метода RS::Orm::AbstractObject::getForm(). Метод имеет следующие аргументы:
Генерация шаблона произойдет только в случае, если шаблона объекта еще не существует(файл не будет найден).
Будет сгенерирован следующий шаблон:
getForm - это низкоуровневый метод для генерации формы объекта. В интерфейсах административной части рекомендуется использовать контроллер, расширяющий класс RS::Controller::Admin::Crud. Такой контроллер берет на себя и формирование форм и обработку POST данных этих форм, причем как в обычном режиме, так и в режиме AJAX.
В ReadyScript помимо возможности сформировать форму для целого объекта есть возможность сгенерировать форму одного поля. Для этого существует метод RS::Orm::AbstractObject::getPropertyView(). Метод имеет следующие аргументы:
Пример:
Будет сгенерирована форма:
Контроллировать целостность данных можно с помощью прикрепления чекеров, специальных функций проверки, к полям. Для этого у каждого объекта поля есть метод setChecker(), который принимает следующие аргументы:
Для одного свойства может быть назначено и более одного чекера. В этом случае, чекеры будут выполняться последовательно, если в какой-то из них вернет ошибку, то последующие чекеры для данного свойства выполняться не будут.
Добавлять чекеры к полям можно с помощью сокращенного синтаксиса.
В функцию проверки(чекер) поступают следующие аргументы:
Ожидается, что функция проверки вернет:
Проверка полей автоматически происходит при каждой попытке сохранить или обновить объект через метод RS::Orm::AbstractObject::save(). Чтобы проверить корректность текущих значений полей можно воспользоваться методом RS::Orm::AbstractObject::validate().
Первично внешний вид HTML-формы поля зависит от его класса. Именно в классе указан шаблон по-умолчанию, который будет использован для генерации формы.
Но также существует несколько модификаторов, которые изменяют внешний вид полей.
Если указать список значений поля с помощью методов RS::Orm::Type::AbstractObject::setList или RS::Orm::Type::AbstractObject::setListFromArray, то шаблон формы будет изменен и поле отобразится в виде HTML элемента <select>.
Изменить внешний вид на checkbox можно с помощью метода RS::Orm::Type::AbstractObject::setCheckboxView(), у которого имеются аргументы:
Установить свой собственный шаблон формы поля для обычного редактирования и группового редактирования можно с помощью методов RS::Orm::Type::AbstractObject::setTemplate() и RS::Orm::Type::AbstractObject::setMeTemplate() соответственно.
Рассмотрим, какие переменные поступают в шаблон формы.
Пример файла field_name_form.tpl:
Иногда возникает необходимость разместить абсолютно произвольный HTML-код на отдельной закладке формы ORM-объекта. Для этой цели необходимо описать поле с типом RS::Orm::Type::UserTemplate.
Если идет POST запрос из формы ORM объекта, то достаточно выполнить метод save, чтобы объект был создан или обновлен. Метод имеет следующие параметры:
Полям ORM-объекта устанавливаются занчения с идентичным ключем из массива POST данных. Чтобы запретить какому-либо полю устанавливать значения из POST, необходимо у этого поля вызвать метод RS::Orm::Type::AbstractType::setUseToSave(false);
Для ограничения полей, заполняемых из POST, также существует метод RS::Orm::AbstractObject::usePostKeys().
Пример, как может выглядеть код сохранения 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.afterwrite.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА | КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... | array. Элементы:
| Вызывается после записи ORM объекта в базу данных |
orm.afterload.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА | КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... | array. Элементы:
| Вызывается сразу после загрузки объекта |
orm.delete.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА | КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... | array. Элементы:
| Вызывается перед удалением ORM объекта из базы данных |
Пример расширения ORM-объекта с помощью события orm.init
ORM объекты имплементируют интерфейс RS::Behaior::AcceptBehaviorInterface, что позволяет добавлять методы к ORM объектам из сторонних модулей. Подробнее о расширении поведения написано здесь.
ORM объект имплементирует интерфейсы ArrayAccess, Iterator, что позволяет работать с ним также как с массивом.
Часто при написании модуей импорта данных, требуется выполнять запросы вида INSERT INTO ... VALUES(field1,filed2,field3) ON DUPLICATE KEY UPDATE field2 = VALUES(field2), field3 = VALUES(field3) Такая конструкция позволяет вставлять записи, а в случае наличия записи с такими же данными по уникальным ключам, производить обновление некоторых данных. По сути вставка или обновление происходит в 1 запрос, что существенно лучше, чем пытаться предварительно загрузить запись по уникальным ключам и уже потом принимать решение вставлять или обновлять запись. Возможность выполнения таких запросов предусмотрена в ORM объектах.