Оригинал: Copyright © 2013 Akeeba Ltd
Перевод: Copyright © 2014 Фёдоров Александр
Содержание данной документации, являюется объектом авторского права и доступно согласно лицензии Joomla! Electronic Documentation License (JEDL), если не указано иное. Вы можете обратиться к разделу частые вопросы по JEDL, чтобы уточнить как вы можете использовать данный материал. Если у вас есть какие-либо вопросы, касающиеся лицензирования данного материала, или вы хотите сообщить о возможном нарушении условий лицензии на материалы данного сайта, пожалуйста, напишите по электронной почте: Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра..
Соглашения об именовании классов и файлов
Классы Модели именуются следующим образом: ComponentModelView
. Для компонента com_todo
, и Вида items
название класса модели будет таким: TodoModelItems
. Надо отметить, что последняя часть имени класса должна быть во множественном числе, поддержка имён в единственном числе (таких как TodoModelItem
) в будущих версиях FOF будет прекращена.
Название файла Модели должно дублировать последнюю часть названия класса. Т.е. для класс TodoModelItems
файл будет называться items.php
, в то время как для TodoModelItem
он бы назывался item.php
.
Все файлы Модели размещаются в подпапке models
компонента, соответственно для пользовательской и административной частей сайта. Если файл Модели отсутствует в структуре пользовательской части, то он будет загружен из административной части сайта и наоборот. Если класс Модели не загружен, а соответствующий файл не найден, FOF по умолчанию перейдёт к одному из вариантов в следующей последовательности:
-
Загрузить Модель "по умолчанию". Это специальный класс Модели
ComponentModelDefault
(например,TodoModelDefault
), описанный в файлеdefault.php
в папке моделей компонента. -
Если Модель "по умолчанию" не найдена, FOF создаст экземпляр объекта соответствующим образом настроенного класса
F0FModel
, используя "соглашения поверх конфигурации" (описано далее), чтобы определить что же должен делать объект Модели.
Настройка специальными классами
В отличии от оригинальной Joomla!, работая с FOF нет необходимости копировать код. Наше основное правило - если вы застали себя за копированием кода из класса F0FModel в отдельный класс вашей модели, значит вы делаете что-то не так.
Модели FOF очень легко могут быть настроены с помощью методов onBeforeSomething
/ onAfterSomething
. Something
- это название метода Модели, к которому относятся выполняемые действия. Например, onBeforeSave
выполняется непосредственно перед методом save()
, а onAfterSave
сразу после того же метода save()
. Специфические замечания по каждому методу изложены в соответствующем блоке документации.
Настройка с использованием событий плагинов
Модели FOF спроектированы таким образом, чтобы после определённых действий вызывать определённые события, на которые могут быть подписаны плагины типа "content". Все возможные события описаны в качестве защищённых свойств модели:
event_before_delete
(аналог в Joomla:onContentBeforeDelete
) вызывается перед удалением записиevent_after_delete
(аналог в Joomla:onContentAfterDelete
) вызывается после удаления записиevent_before_save
(аналог в Joomla:onContentBeforeSave
) вызывается перед сохранением записиevent_after_save
(аналог в Joomla:onContentAfterSave
) вызывается после сохранения записиevent_change_state
(аналог в Joomla:onContentChangeState
) вызывается после изменения состояния записи, например, опубликован / не опубликован и т.п.event_clean_cache
(аналог в Joomla: отсутствует) вызывается при сбросе кэша.
Кроме того, если формы описываются с помощью XML, срабатывает событие onContentPrepareForm
, которое вызывается перед рендерингом формы.
Перечисленные события аналогичны стандартным событиям Joomla. Таким образом плагин, разработанных для стандартной Joomla, может быть быстро доработан для учёта событий FOF.
Всякий раз, когда Joomla запрашивает передачу контекста в событие плагина, используется соглашение об именах component.view
, например, com_todo.items
для компонента под названием com_todo
и модели для представления items
.
Поведение модели
Введение
Модель может реализовывать сложную многократно используемую функциональность с помощью поведений. Поведения модели - это практичный способ изменять стандартные действия F0FModel. Модель работает с использованием стандартной системы событий Joomla.
До и После определённых действий (onBefore*
/ onAfter*
) модель вызывает событие, которое размножается на каждое зарегистрированное поведение данной модели. Поведения в свою очередь, регистрируясь на эти события, могут изменять содержание той или иной операции.
Можно регистрировать несколько поведений на одну модель, они могут добавляться по умолчанию или генерироваться на лету уже после создания модели.
В FOF уже предусмотрено некоторое количество стандартных поведений, которые могут быть использованы любым объектом F0FModel. Связанные с поведениями классы в настоящее время находятся в папке model/behavior
фреймворка FOF.
FOF поставляется с некоторым количеством встроенных поведений модели. Эти поведения используются для реализации базового функционала. В целях повышения производительности изначально к модели подключено только поведение filters
. Далее в этом разделе будет показано что делает каждое поведение.
В каждом конкретном случае можно комбинировать различные поведения.
Стандартные поведения модели в FOF
access
Добавление этого поведения к объекту модели фильтрует вывод данных в пользовательской части сайта согласно уровню доступа, которым обладает пользователь.
Это поведение требует соблюдения следующих условий:
- Поведение
filters
должно быть включено. Если поведениеfilters
не будет добавлено модели, поведениеaccess
не окажет никакого воздействия на просмотр списков (представление типа browse), однако будет работать в представлениях просмотра и редактирования единичной записи. - В таблице модели должно присутствовать "волшебное" поле
access
.
enabled
Добавление этого поведения к объекту модели фильтрует вывод записей в пользовательской части сайта и отображает только опубликованные записи (enabled=1).
Для работы этого поведения в таблице модели должно присутствовать "волшебное" поле enabled
.
filters
Описание
Это поведение работает только на представлениях со списками (представление типа browse).
Добавление этого поведения к объекту модели добавляет фильтр на основании вводимых данных. Например, если в запросе передаётся параметр &foobar=1
, или, в общем случае, если имеется некая переменная состояния foobar со значением равным 1, SQL-запрос, отвечающий за выборку списка записей, будет выводить только записи со значением 1 в колонке foobar.
Поведение filters
автоматически распознаёт тип колонки в таблице и применяет фильтр соответствующего типа. Предусмотрено несколько различных методов фильтрации в зависимости от типа поля. Чтобы выбрать метод, отличный от стандартного (когда фильтруется простой текст), в качестве переменной необходимо передать хэш-массив по примеру следующего array('method'=>'between', 'from'=>1, 'to'=>10)
, что в переложении на запрос в адресной строке выглядит как &foobar[method]=between&foobar[from]=1&foobar[to]=10
.
Далее рассмотрены типы фильтров для соответствующих типов полей.
Числовые поля
Для числовых полей можно использовать следующие методы фильтрации:
- exact - это метод по умолчанию. Можно просто передать значение переменной, по которому необходимо отфильтровать список. Если хочется использовать хэш-массив, можно использовать следующие ключи:
method
:exact
value
: значение для поиска
- partial - для числовых полей - это по сути ссылка на метод exact
- between - задаёт границы диапазона, в который должны попасть фильтруемые значения (включительно). Ключи, которые можно использовать:
method
:between
from
: левая граница числового диапазонаto
: правая граница числового диапазона
Например, при значениях параметровfrom=1
иto=10
будут отображать все значения между 1 и 10 включительно.
- outside - задаёт границы диапазона, вне которого должны быть фильтруемые значения. Используемые ключи:
method
:outside
from
: левая граница числового диапазонаto
: правая граница числового диапазона
Например, при значениях параметровfrom=1
иto=10
будут отображаться все значения меньше 1 и больше 10 (1 и 10 не включая).
- interval - задаёт начало и интервал диапазона, в который должны попасть фильтруемые значения (включительно). Используемые ключи:
method
:interval
value
: начальное значение интервалаinterval
: величина интервала
Например, при значениях параметровvalue=5
иinterval=2
будут отображаться значения между 3 и 7 включительно.
- range - задаёт границы диапазона, в который должны попасть фильтруемые значения (включительно). Отличатся от between тем, что обязательно необходимо указать только одну границу. Используемые ключи:
method
:range
from
: левая граница числового диапазонаto
: правая граница числового диапазона
Например, при значении параметраfrom=1
будут отображаться все записи больше 1 включительно, при значениях параметровfrom=1
иto=10
будут отображаться все записи между 1 и 10 включительно.
- modulo - задаёт начальное значение диапазона и интервал, кратно которому фильтруются значения (арифметическая прогрессия)
method
:modulo
value
: начальное значение интервалаinterval
: интервал, кратно которому ищутся значения
Например, для значений параметровvalue=7
иinterval=3
будут отображаться значения 7, 10, 13, 16 и т.д.
Логические (булевы) поля
Для логических полей (tiny integer) можно использовать следующие методы:
- exact - это метод по умолчанию. Можно просто передать значение переменной, по которому необходимо отфильтровать список. Если хочется использовать хэш-массив, можно использовать следующие ключи:
method
:exact
value
: значение для поиска
Текстовые поля
Для текстовых полей можно использовать следующие методы:
- partial - это метод по умолчанию. Можно просто передать значение переменной, по которому необходимо отфильтровать список. Будут отображены все записи, содержащие вхождение указанного текста. Можно использовать следующие ключи:
method
:partial
value
: текст, который необходимо найти
- exact - позволяет осуществить поиск по точному совпадению. Значение в полях должно быть точно равно указанному значению параметра. Используемые ключи:
method
:exact
value
: текст, для поиска полного совпадения
Поля даты
Для полей дата и дата/время можно использовать следующие методы:
- exact (прим. переводчика: похоже на ошибку в документации) - это метод по умолчанию. Выполняет поиск по точному совпадению. Используемые ключи:
method
:exact
value
: точная дата или дата/время для поиска
- partial (прим. переводчика: похоже на ошибку в документации) - это метод по умолчанию. Можно просто передать значение переменной, по которому необходимо отфильтровать список. Будут отображены все записи, содержащие вхождение указанного текста. Можно использовать следующие ключи:
method
:partial
value
: текст, который необходимо найти
- between - возвращает записи, значения которых попадают в указанный интервал включая границы. Можно использовать следующие ключи:
method
:between
from
: левая граница промежутка датto
: правая граница промежутка дат
- outside - возвращает записи, значения которых находятся вне указанного диапазона дат, границы также исключая. Можно использовать следующие ключи:
method
:outside
from
: левая граница промежутка датto
: правая граница промежутка дат
- interval - в настоящее время работает только с MySQL. Возвращает записи, значения полей которых соответствуют заданному интервалу (арифметической прогрессии)
method
:interval
value
: начальное значение интервалаinterval
: период интервала. Интервал может быть задан строкой или массивом. При задании строкой параметр должен содержать знак ("+" чтобы идти в будущее, "-" чтобы идти в прошлое), количество в виде числа и единицу измерения (день, неделя, месяц, год).
Например:+1 month
, чтобы найти все записи с датой в будущем месяце или-1 month
, чтобы найти все записи в прошедшем месяце.
В качестве массива эти параметры можно передать следующим образомarray('sign' => '+', 'value' => '1', 'unit' => 'month')
- range - задаёт границы диапазона, в который должны попасть фильтруемые значения (включительно). Отличатся от between тем, что обязательно необходимо указать только одну границу. Используемые ключи:
method
:range
from
: левая граница числового диапазонаto
: правая граница числового диапазона
emptynonzero.php
Это поведение расширяет поведение filters и указывает FOF не считать нулевые значения пустыми. Например, если в адресной строке будет передан параметр &foobar=0
, SQL запрос отберёт все строки в колонке foobar которых указан 0. Без поведения emptynonzero.php колонка foobar должна быть пустой.
Важно. Это поведение требует включённого поведения filters
.
language
Это поведение позволяет отфильтровать вывод записей в пользовательской части сайта, отображая только записи, соответствующие установленному в настройках языку пользовательской части сайта. Естественно это имеет смысл только на мультиязычных сайтах при включённом языковом плагине Joomla.
Важно. Это поведение требует наличия "волшебного" поля language
.
private
Это поведение отфильтровывает записи в пользовательской части сайта и выводить только записи, созданные текущим пользователем. Остальные записи не отображаются.
Важно. Это поведение требует наличия "волшебного" поля created_by
.
Добавление готового поведения в модель
Есть 5 способов добавить поведение в модель:
- Использовать свойство модели
default_behaviors
. Это массив с именами поведений, которые должны быть загружены при создании модели. По умолчанию массив содержит только поведениеfilters
. Можно переопределить это свойство в декларации класса модели:
class FoobarModelTodos extends F0FModel { protected $default_behaviors = array('filters', 'access', 'enabled'); }
- Передать список поведений в параметре
$config
конструктора класса модели:
class FoobarModelTodos extends F0FModel { public function __construct($config = array()) { if (!isset($config['behaviors'])) { $config['behaviors'] = array('filters', 'access', 'enabled'); } parent::__construct($config); } }
- Передать список поведений в параметре
config
при создании экземпляра модели:
$model = F0FModel::getTmpInstance('Todos', 'FoobarModel', array( 'behaviors' => array('filters', 'access', 'enabled')) );
- Динамически добавить нужные поведения используя метод
addBehavior
class FoobarModelTodos extends F0FModel { public function __construct($config = array()) { parent::__construct($config); if ($iWantToAddIt) { $this->addBehavior('enabled'); } } }
- Добавить поведения в представление, используя
fof.xml
<?xml version="1.0" encoding="utf-8"?> <fof> <frontend> <view name="todos"> <config> <option name="behaviors">filter,access,enabled</option> </config> </view> </frontend> </fof>
Создание нового поведения
Можно создать новое поведение для своего компонента. Название поведения имеет очень большое значение, т.к. FOF будет искать поведение по его имени.
Если есть желание добавить поведение bar
в компонент foo
, FOF будет искать это поведение в следующем порядке:
- Сначала будет попытка найти
ComponentnameModelViewnameBehaviorName
(например,FoobarModelTodosBar
)
Внимание,Viewname
должно быть во множественном числе. - Затем будет попытка найти
ComponentnameModelBehaviorName
(например,FoobarModelBar
) - Затем
F0FModelBehaviorBehaviorName
(например,F0FModelBehaviorBar
)
Это позволяет создавать поведения как для конкретной модели, так и для компонента в целом.
Кроме того возможно переопределить существующее поведение. Например, для того, чтобы переопределить поведение enabled
достаточно создать класс под названием FoobarModelBehaviorEnabled
).
Важно. Поведения автоматически не загружаются, поэтому необходимо предусмотреть либо свой загрузчик, либо загружать классы поведений в конструкторе модели перед вызовом метода parent::__construct(…)
.
Доступные для поведений модели события
В каждое поведение модели можно добавить логику работы с использованием следующих методов:
/** * Это событие вызывается перед сохранением данных в модели * * @param F0FModel &$model Модель, вызывающая данное событие * @param array &$data Данные для сохранения * * @return void */ public function onBeforeSave(&$model, &$data) { } /** * Это событие вызывается перед удалением записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onBeforeDelete(&$model) { } /** * Это событие вызывается перед копирование записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onBeforeCopy(&$model) { } /** * Это событие вызывается перед публикацией записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onBeforePublish(&$model) { } /** * Это событие вызывается перед изменением счётчика переходов на запись в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onBeforeHit(&$model) { } /** * Это событие вызывается перед перемещением записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onBeforeMove(&$model) { } /** * Это событие вызывается перед изменением порядкового номера записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onBeforeReorder(&$model) { } /** * Это событие вызывается перед построением запроса для получения списка записей * в модели * * @param F0FModel &$model Модель, вызывающая данное событие * @param JDatabaseQuery &$query Запрос, который строится * * @return void */ public function onBeforeBuildQuery(&$model, &$query) { } /** * Это событие вызывается после сохранения записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterSave(&$model) { } /** * Это событие вызывается после удаления записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterDelete(&$model) { } /** * Это событие вызывается после копирования записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterCopy(&$model) { } /** * Это событие вызывается после публикации записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterPublish(&$model) { } /** * Это событие вызывается после изменения порядкового номера записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterHit(&$model) { } /** * Это событие вызывается после перемещения записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterMove(&$model) { } /** * Это событие вызывается после изменения порядкового номера записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * * @return void */ public function onAfterReorder(&$model) { } /** * Это событие вызывается после построения запроса для получения списка записей * в модели
* * @param F0FModel &$model Модель, вызывающая данное событие * @param JDatabaseQuery &$query Запрос, который строится * * @return void */ public function onAfterBuildQuery(&$model, &$query) { } /** * Это событие вызывается после получения единичной записи в модели * * @param F0FModel &$model Модель, вызывающая данное событие * @param F0FTable &$record Запись, загруженная в модели * * @return void */ public function onAfterGetItem(&$model, &$record) { }
Пример нового поведения: Published
Давайте для нашего компонента создадим поведение Published, которое будет фильтровать записи на основании полей publish_up и pulish_down.
class F0FModelBehaviorPublished extends F0FModelBehavior { /** * Добавим фильтр записей на основании полей publish_up и publish_down после построения запроса * * @param F0FModel &$model Вызываемая модель * @param JDatabaseQuery &$query Создаваемый запрос * * @return void */ public function onAfterBuildQuery(&$model, &$query) { // Это поведение работает только для пользовательской части сайта if (!F0FPlatform::getInstance()->isFrontend()) { return; } // Получим имя поля publish_up (поддержка псевдонимов) $table = $model->getTable(); $publishUpField = $table->getColumnAlias('publish_up'); // Получим имя поля publish_down (поддержка псевдонимов) $publishDownField = $table->getColumnAlias('publish_down'); // Проверим, что поля существуют $publishUpExists = in_array($publishUpField, $table->getKnownFields()); $publishDownExists = in_array($publishDownField, $table->getKnownFields()); // Если хотя бы одного из полей нет, выходим, т.к. поведение не сможет отработать if (!$publishUpExists && !$publishDownExists) { return; } // Получим текущую дату $now = new JDate() $now = $now->toSql(); $db = JFactory::getDbo(); // Сначала фильтруем по полю publish_up if ($publishUpExists) { $query->where($db->qn($publishUpField) . ' <= ' . $db->q($now)); } // Затем фильтруем по полю publish_down if ($publishDownExists) { $query->where($db->qn($publishDownField) . ' >= ' . $db->q($now)); } } /** * Обнуляет результат getItem, если загруженная запись не опубликована * * @param F0FModel &$model Вызываемая модель * @param F0FTable &$record Загруженная из БД запись * * @return void */ public function onAfterGetItem(&$model, &$record) { if ($record instanceof F0FTable) { // Поведение применимо только к пользовательской части сайта if (!F0FPlatform::getInstance()->isFrontend()) { return; } // Получим имя поля publish_up (поддержка псевдонимов) $publishUpField = $record->getColumnAlias('publish_up'); // Получим имя поля publish_down (поддержка псевдонимов) $publishDownField = $record->getColumnAlias('publish_down'); // Проверим, что поля существуют $publishUpExists = in_array($publishUpField, $record->getKnownFields()); $publishDownExists = in_array($publishDownField, $record->getKnownFields()); // Если хотя бы одного из полей нет, выходим, т.к. поведение не сможет отработать if (!$publishUpExists && !$publishDownExists) { return; } // Получим текущую дату $now = new JDate() $now = $now->toSql(); if ($publishUpExists) { $publish_up = new JDate($record->$publishUpField); if ($publish_up > $now) { $record = null; return; } } if ($publishDownExists) { $publish_down = new JDate($record->$publishDownField); if ($publish_down < $now) { $record = null; return; } } } } }