Оригинал: 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 по умолчанию перейдёт к одному из вариантов в следующей последовательности:

  1. Загрузить Модель "по умолчанию". Это специальный класс Модели ComponentModelDefault (например, TodoModelDefault), описанный в файле default.php в папке моделей компонента.

  2. Если Модель "по умолчанию" не найдена, 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 способов добавить поведение в модель:

  1. Использовать свойство модели default_behaviors. Это массив с именами поведений, которые должны быть загружены при создании модели. По умолчанию массив содержит только поведение filters. Можно переопределить это свойство в декларации класса модели:
    class FoobarModelTodos extends F0FModel {
        protected $default_behaviors = array('filters', 'access', 'enabled');
    }
  2. Передать список поведений в параметре $config конструктора класса модели:
    class FoobarModelTodos extends F0FModel {
        public function __construct($config = array()) {
            if (!isset($config['behaviors'])) {
                $config['behaviors'] = array('filters', 'access', 'enabled');
            }
            parent::__construct($config);
        }
    }
  3. Передать список поведений в параметре config при создании экземпляра модели:
    $model = F0FModel::getTmpInstance('Todos', 'FoobarModel', array(
        'behaviors' => array('filters', 'access', 'enabled'))
    );
  4. Динамически добавить нужные поведения используя метод addBehavior
    class FoobarModelTodos extends F0FModel {
        public function __construct($config = array()) {
            parent::__construct($config);
            if ($iWantToAddIt) {
                $this->addBehavior('enabled');
            }
        }
    }
  5. Добавить поведения в представление, используя 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 будет искать это поведение в следующем порядке:

  1. Сначала будет попытка найти ComponentnameModelViewnameBehaviorName (например, FoobarModelTodosBar)
    Внимание, Viewname должно быть во множественном числе.
  2. Затем будет попытка найти ComponentnameModelBehaviorName (например, FoobarModelBar)
  3. Затем 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;
                }
            }
        }
    }
}