Новый функционал фреймворка - поведения (behavior)

В новой версии LS 2.0 появится функционал поведений (behaviors), который очень поход на подобный в Yii.
Суть сводится к тому, что теперь определенному типу объектов (пользователи, топики, блоги и т.п.) или конкретному объекту можно назначить через поведения определенный новый функционал.
Добавить новое поведение в объект достаточно просто, можно у объекта определить свойство $aBehaviors. Например:
	protected $aBehaviors=array(
		'category'=>'ModuleCategory_BehaviorCategory',
		'property'=>array(
			'class'=>'ModuleProperty_BehaviorPropertyEntity',
	        	'target_type'=>'article'
		)
	);

В этом примере добавляются два поведения — категории и дополнительные поля. В качестве ключа используется название поведение (далее по этому ключу можно обращаться к поведению), а в качестве значения идут параметры. Параметры идут произвольным массивом имя/значение, 'class' — это служебный параметр, означающий класс поведения. Если у повеления нет параметром, то его можно записать в короткой форме (смотри 'category'). Альтернативным (скорее дополнительным) способом добавления поведения является прямой вызов метода AttachBehavior, например:
$oTopic->AttachBehavior('property','ModuleProperty_BehaviorPropertyEntity');

// далее можно убрать поведение
$oTopic->DetachBehavior('property');


Сами классы поведения привязаны к конкретному своему модулю и находятся в его подкаталоге behavior, здесь полная аналогия с Entity. На рисунке пример модуля с поведениям:

Название файла имеет вид [Name].behavior.class.php, а сам класс Module[ModuleName]_Behavior[Name], который наследуется от класса Behavior.

В самом простом варианте класс поведения может выглядеть так:
<?php

class ModuleMain_BehaviorMy extends Behavior {

	public function NewMethod() {
		$sTwo=$this->getParam('two');
		// ....
	}

}

Поведения могут делать две вещи — добавлять новые методы в объект(есть исключение с Entity, об этом ниже) и вешать обработчики на хуки объекта.
Для добавления метода достаточно его описать в классе поведения и сделать у него видимость 'public', из примера выше это метод NewMethod(). Теперь если объект топика объявил использование нашего поведения ( 'bar'=>'ModuleMain_BehaviorMy' ), то можно выполнить из топика метод поведения $oTopic->NewMethod();, но лучше это делать через имя поведения — $oTopic->bar->NewMethod();, в связи с тем, то у разных поведений могут совпадать имена методов. $this->getParam('two') возвращает параметр с именем two, который был задан при объявлении поведения.

Обработчики хуков добавляются через свойство $aHooks:
<?php

class ModuleMain_BehaviorMy extends Behavior {

	protected $aHooks=array(
		'hook_one'=>array(
			'CallbackHookOne',1000 // приоритет
		),
		'hook_two'=>'CallbackHookTwo',
	);

	public function CallbackHookOne($aParams) {
		// ....
	}

	public function CallbackHookTwo($aParams) {
		// ....
	}

	public function NewMethod() {
		// ....
	}

}

При объявлении хуков указывается их имя и коллбэк (обработчик в виде имени метода поведения), дополнительно можно указать приоритет.
Здесь есть важное отличие от стандартных хуков в ЛС — хуки в поведениях привязываются к конкретному объекту. Т.е. при срабатывании хука 'hook_one' в объекте топика, он будет обработан только тем поведением, которое привязано к этому конкретному топику. Из-за этой особенности такие хуки объявляются в коде чуть по другому, а именно:
$this->RunBehaviorHook('hook_one');
$this->RunBehaviorHook('hook_two',array('param1'=>&$param1));
// вместо стандартного $this->Hook_Run('hook_one');

Часто бывает, что наряду с хуком внутри объекта необходимо вызвать еще глобальный хук, то для этого достаточно передать третий параметр = true
$this->RunBehaviorHook('hook_one',array(),true);
// аналогичен двойному вызову
$this->RunBehaviorHook('hook_one');
$this->Hook_Run('hook_one');


Доступ к исходному объекту внутри поведения получается через $this->oObject.
Если поведении принимает параметры и необходимо задать их дефолтные значения, то для этого достаточно просто в классе поведения объявить свойство $aParams.
<?php

class ModuleMain_BehaviorMy extends Behavior {

	protected $aParams=array(
            	'two'=>12345
	);

	public function NewMethod() {
		$iTopicId=$this->oObject->getId();
		// ....
	}

}


Про исключение. В ЛС у сущностей Entity идет автоматическая обработка методов get*/set*/reload*, поэтому если поведение объявляет публичный метод, например, getMyData(), то напрямую к нему обратиться нельзя. Т.е. такой вызов $oTopic->getMyData() не пройдет, для вызова метода нужно использовать имя поведения — $oTopic->my->getMyData(). Такое ограничение есть только у Entity, у остальных объектов его нет. Но нужно понимать, что лучше всегда обращаться к методам через имя поведения, чтобы не было конфликтов с другими поведениями.

Сейчас в ЛС реализованы поведения для модуля дополнительных полей (Property) и на его основе они подключены к топику.

17 комментариев

avatar
LS всё больше становится похож на Yii. Кстати, behaviours были уже в первой ветке этого фреймворка :)
avatar
Просветите неопытных в каких случаях описанное в топики можно и лучше всего применять? Лучше всего в жизненных примерах)
avatar
например, мы можем написать модуль универсальных категорий с реализованным поведением. А теперь захотели к блогам прикрутить эти самые категории, достаточно объявить в классе блога, что он использует поведение категорий и все должно заработать :) Собственно так сделано с топиками и дополнительными полями. Но топики не ORM объекты, поэтому содержат еще инжекцию кода property, а вот для ORM объектов это должно ограничиться исключительно навешиванием поведений.
avatar
Правильно ли я понимаю: по сути это «инжект» дополнительных методов в объект и решение проблемы отсутствия множественного наследования в пхп?
avatar
практически + возможность повесить обработчик на события конкретного объекта
avatar
1.
$sTwo=$this->getParam('two');

получает параметр-значение из расширенной записи
protected $aBehaviors=array()

т.е. как в уии? Или из:
protected $aParams=array()


если из aParams, то что такое «target_type»:
		'property'=>array(
			'class'=>'ModuleProperty_BehaviorPropertyEntity',
	        	'target_type'=>'article'
		)

если:
В этом примере добавляются два поведения — категории и дополнительные поля. В качестве ключа используется название поведение (далее по этому ключу можно обращаться к поведению), а в качестве значения идут параметры. Параметры идут произвольным массивом имя/значение, 'class' — это служебный параметр, означающий класс поведения.


2. не совсем понятна работа хуков:
Здесь есть важное отличие от стандартных хуков в ЛС — хуки в поведениях привязываются к конкретному объекту. Т.е. при срабатывании хука 'hook_one' в объекте топика, он будет обработан только тем поведением, которое привязано к этому конкретному топику
если мы прицепили хуки в поведения, а поведение к сущности топика, то вызов:
$this->RunBehaviorHook('hook_one');

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

3. «category» и «property» — это имя поведения? т.е:
$oTopic->category->NewMethod()

то почему, тогда в примере указано имя «my» модуля поведения
$oTopic->my->getMyData()

?
avatar
1. Параметры из $aBehaviors транслируются в массив $aParams. В конечном итоге все параметры оказываются в массиве $aParams. target_type — это имя произвольного параметра.

2. $this->RunBehaviorHook('hook_one'); запустит только один хук внутри конкретного объекта, где он был вызван, т.е. конкретного топика.

3. да, имена. Имя может быть произвольным, поэтому в некоторых примерах 'my' (примеры независимы друг от друга)
avatar
1. Параметры из $aBehaviors транслируются в массив $aParams. В конечном итоге все параметры оказываются в массиве $aParams.
т.е. параметры, заданные в создании поведения, мержатся (смотрю сегодняшний коммит) с параметрами, указанными прямо в модуле поведения? и:
$this->getParam

берет параметр именно из aParams, в который попали из aBehaviors.

2.1. т.е. он зависим от контекста?
2.2. т.е. такой хук можно запускать только в сущности?
2.3. только в сущности, где было прицеплено поведение с этим хуком?

3.
(примеры независимы друг от друга)
теперь понятно, но лучше об этом в топике написать либо примеры подправить чтобы уловить логику.
avatar
2. такой хук можно запускать в любом объекте, который в конечном счете наследуется от LsObject, т.е. по сути любой объект LS

Сейчас такие хуки прописаны в ModuleORM и EntityORM
avatar
2. в любом объекте, который имеет поведение, в котором указан этот хук?
avatar
возможность запуска у объекта хука (RunBehaviorHook) и наличие у него поведения никак не связано. Поведение только дает возможность подключить свой обработчик на такой хук.
avatar
т.е. поведение можно объявить в сущности топика, а вызвать хук — в экшене?
просто не могу тогда понять:
$this->RunBehaviorHook('hook_one');
запустит только один хук внутри конкретного объекта, где он был вызван, т.е. конкретного топика.
avatar
если запустить хук внутри экшена, то для его отлова нужно уже приаттачивать поведение к самому экшену

Еще раз — хук привязывается к тому объекту, от куда он был вызван через $this->RunBehaviorHook()
avatar
А когда новая версия движка ожидается?
avatar
тоже интересует! ort?
avatar
Работа идет, конкретных сроков обычно не называют разработчики. Вот здесь можете следить сколько примерно до новой версии github.com/livestreet/livestreet/milestones
комментарий был удален
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.