Версия: 3.x
Расширение поведения объектов из сторонних модулей

Представим ситуацию, что вы разрабатываете собственный модуль Custom, который выводит товары, срок годности которых скоро истекает. Ваш фронт-контроллер с помощью модели успешно сформировал выборку объектов Catalog::Model::Orm::Product и в шаблоне вам необходимо вывести количество дней, оставшихся до истечения каждого продукта. Наиболее наглядно в шаблоне выглядел бы вызов метода {$product->getRemainingDays()}, для получения количества оставшихся дней до истечения срока годности. Однако, метода getRemainingDays нет у объекта из модуля Catalog. Что делать?

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

Внедрение поддержки расширяемости

"Расширение" извне поддерживают классы потомки RS::Behavior::AcceptBehavior, имплементирующие интерфейс RS::Behavior::AcceptBehaviorInterface. Интерфейс описывает следующие методы:

  • static attachClassBehavior - подключает библиотеку методов ко всем объектам класса
  • static detachClassBehavior - отключает библиотеку методов из всех объектов классов
  • attachInstanceBehavior - подключает библиотеку методов к одному объекту класса
  • detachInstanceBehavior - отключает библиотеку методов от одного объекта класса
  • getAttachedBehaviors - возвращает подключенные библиотеки методов

"Расширение" классов реализовано в методе __call, который перехватывает вызовы несуществующих функций. В теле функции __call происходит поиск запрошенного метода в подключенных "библиотеках" и если запрошенный метод обнаружен, то он вызывается, в противном случае выбрасывается исключение RS::Behavior::Exception.

В настоящее время интерфейс RS::Behavior::AcceptBehaviorInterface поддерживают все ORM объекты в системе, потомки RS::Orm::AbstractObject

Создание библиотеки методов

Библиотекой методов (далее Библиотека) называется класс, потомок RS::Behavior::BehaviorAbstract, содержащий методы, которые будут подключены в последующем к другим классам.

В момент первого обращения к методу из библиотеки классов, создается отдельный экземпляр класса библиотеки, который привязан к расширяемому объекту, далее вызывается функция инициализация ModuleName::Model::Behavior::init, в которой происходит сохранение объекта расширяемого класса в свойство owner. Таким образом внутри библиотеки, обратиться к расширяемому объекту всегда можно с помощью $this->owner.

Рекомендуем размещать все классы "библиотеки" в пространстве имен не ниже .

Рассмотрим пример библиотеки методов.

namespace ModuleName\Model\Behavior;
//Класс, расширяющий методы объекта \Catalog\Model\Orm\Product
class CatalogProduct extends \RS\Behavior\BehaviorAbstract
{
//Возвращает поле $field товара
function getCustomField($field)
{
$product = $this->owner; //$this->owner - содержит объект, к которому подключена библиотека
return 'field value:'.$product[$field];
}
//Возвращает hello!
function getSomeData()
{
return 'hello!';
}
}
//Подключаем ко всем классам Product
//Вызываем
$product = new \Catalog\Model\Orm\Product();
$product['name'] = 'Товар';
$product->getCustomField('name'); //Вернет field value:Товар
$product->getSomeData(); //Вернет hello!

Привязка библиотеки методов к "чужому" объекту

Подключать библиотеки методов к сторонним классам рекомендуется в момент возникновения события initialize. Это самое первое событие за время выполнения скрипта, специально предназначенное для низкоуровневых инициализаций.

Подключение библиотеки методов ко всем объектам класса происходит с помощью вызова статического метода attachClassBehavior

namespace ModuleName\Config;
class Handlers extends \RS\Event\HandlerAbstract
{
function init()
{
//Подписываемся на событие initialize
$this->bind('initialize');
}
function initialize()
{
//Подключаем библиотеку методов ко всем объектам товаров
\Catalog\Model\Orm\Product::attachClassBehavior(new \ModuleName\Model\Behavior\CatalogProduct);
}
}

Подключить библиотеку методов можно и к отдельному экземпляру класса на любом участе кода, например в контроллере, с помощью метода attachInstanceBehavior.

namespace ModuleName\Controller\Front;
class MyController extends \RS\Controller\Front
{
function actionIndex()
{
$id = $this->url->get('id', TYPE_INTEGER); //Получим id товара из GET
$product = new \Catalog\Model\Orm\Product($id); //Загрузим объект
$product->attachInstanceBehavior(new \ModuleName\Model\Behavior\CatalogProduct); //Подключим библиотеку методов
$this->view->assign(array(
'product' => $product //Передадим в шаблон "расширенный" объект товара
));
return $this->result->setTemplace('template.tpl'); //Укажем шаблон для рендеринга
}
}

Другие примеры расширения поведения:

Подключение, отключение библиотек методов ко всем объектам

//Подключаем поведение ко всем объектам класса
\Catalog\Model\Orm\Product::attachClassBehavior(new \ModuleName\Model\Behavior\CatalogProduct);
$product = new \Catalog\Model\Orm\Product();
echo $product->getSomeData(); // выведет hello
//Отключаем поведение от всех объектов классов
\Catalog\Model\Orm\Product::detachClassBehavior(new \ModuleName\Model\Behavior\CatalogProduct);
echo $product->getSomeData(); // бросит исключение \RS\Behavior\Exception - "Метод getSomeData не найден в классе Catalog\Model\Orm\Product"

Подключение, отключение библиотек методов к одному объекту

$product = new \Catalog\Model\Orm\Product();
$product->attachInstanceBehavior(new \ModuleName\Model\Behavior\CatalogProduct);
echo $product->getSomeData(); // выведет hello
$product->detachInstanceBehavior(new \ModuleName\Model\Behavior\CatalogProduct);
echo $product->getSomeData(); // бросит исключение \RS\Behavior\Exception - "Метод getSomeData не найден в классе Catalog\Model\Orm\Product"

Получение списка подключенных библиотек методов

//Подключаем поведение ко всем объектам класса
$product = new \Catalog\Model\Orm\Product();
//Подключаем поведение к одному объекту
$product->attachInstanceBehavior(new CatalogProduct4);
$product->attachInstanceBehavior(new CatalogProduct5);
$product->attachInstanceBehavior(new CatalogProduct6);
$list = $product->getAttachedBehaviors(); //Возвращает список подключенных поведений
print_r($list);
//Вернет:
//Array
//(
// [CatalogProduct1] => class
// [CatalogProduct2] => class
// [CatalogProduct3] => class
// [CatalogProduct4] => instance
// [CatalogProduct5] => instance
// [CatalogProduct6] => instance
//)