Блоки в Zend Framework. Финальная версия.
02/09/2009 13:08
Я продолжил свои изыскания на тему блоков в Zend Framework. Я все искал, как сделать работу с блоками на сайте максимально удобной.
До этого, я написал несколько статей, посвященных блокам:
- Меню администратора, или блоки в Zend Framework
- Блоки в Zend Framework
- Блоки в Zend Framework, версия 2
Но не далее, как неделю назад я придумал еще более удобный способ работы с блоками. Некоторые мои предыдущие утверждения я опровергнул :)
Блоки в новой версии так же работают на основе конфига блоков. Но теперь блоки можно как включать, так и отключать в любом месте контроллера. Именно отключения блоков и не было в прошлых версиях - как мне тогда показалось, оно было излишним. Но практика показала, что это не так.
Итак, в этой версии у нас есть помощник действия (action helper) и плагин фронт-контроллера (controller plugin). При помощи помощника действия мы указываем, какие блоки нужно будет загрузить или отключить, а плагин фронт-контроллера управляет непосредственно загрузкой блоков, которая происходит сразу после диспетчеризации действия в контроллере.
Сначала приведу листинг конфига блоков. Например, у нас будет конфиг с двумя определенными блоками:
[toolbar]
module=application
controller=toolbar
action=index
[banner]
module=application
controller=banner
action=show
Дальше, давайте посмотрим на код помощника действия:
/**
* @author http://torqueo.net/
*/
class Project_Controller_Action_Helper_Block extends Zend_Controller_Action_Helper_Abstract
{
/**
* An instance of Project_Controller_Plugin_Block plugin
* @var object
*/
protected $_blocksPlugin = null;
/**
* Initialize Project_Controller_Plugin_Block plugin
* and blocks config
*/
public function init()
{
$front = Zend_Controller_Front::getInstance();
if (!$front->hasPlugin('Project_Controller_Plugin_Block')) {
$this->_blocksPlugin = new Project_Controller_Plugin_Block();
$front->registerPlugin($this->_blocksPlugin, 93);
} else {
$this->_blocksPlugin = $front->getPlugin('Project_Controller_Plugin_Block');
}
$blocksConfig = new Zend_Config_Ini(APPLICATION_PATH . "/config/blocks.ini");
$this->_blocksPlugin->setConfig( $blocksConfig );
}
/**
* Add block on page by block name
* @param string $blockName
*/
public function add($blockName)
{
$this->_blocksPlugin->add($blockName);
}
/**
* Remove block from page by block name
* @param string $blockName
*/
public function remove($blockName)
{
$this->_blocksPlugin->remove($blockName);
}
/**
* Remove all blocks from page
*/
public function removeAll()
{
$this->_blocksPlugin->removeAll();
}
}
Теперь взглянем, как выглядит плагин фронт-контроллера, отвечающий за загрузку блоков:
/**
* @author http://torqueo.net/
*/
class Project_Controller_Plugin_Block extends Zend_Controller_Plugin_Abstract
{
/**
* Here's a states for each block (enabled or disabled)
* @var array
*/
protected $_blocks = array();
/**
* Blocks config with specified module, controller
* and action for each block
* @var array
*/
protected $_config = array();
/**
* Enable block by block name (set enabled flags)
* @param string $blockName
* @return void
*/
public function add($blockName)
{
$this->_blocks[ $blockName ] = true;
}
/**
* Disable block by block name (set disabled flags)
* @param string $blockName
* @return void
*/
public function remove($blockName)
{
$this->_blocks[ $blockName ] = false;
}
/**
* Disable all blocks (set disabled flags to all)
* @param string $blockName
* @return void
*/
public function removeAll()
{
foreach ($this->_blocks as $blockName=>$blockEnabled) {
$this->_blocks[ $blockName ] = false;
}
}
/**
* Hook on postDispatch event. Disable or enable blocks.
* @param Zend_Controller_Request_Abstract $request
* @return void
*/
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
if (!$request->isXmlHttpRequest() && !empty($this->_config)) {
$this->_layout = Zend_Layout::getMvcInstance();
$this->_view = $this->_layout->getView();
foreach ($this->_config as $blockName=>$options) {
if (isset($this->_blocks[ $blockName ]) && $this->_blocks[ $blockName ] === true) {
$this->_layout->{$blockName} = $this->_view->action(
$options['action'],
$options['controller'],
$options['module'],
$request->getParams()
);
}
}
}
}
/**
* Set blocks config
* @param Zend_Config $config
* @return void
*/
public function setConfig(Zend_Config $config)
{
$this->_config = $config->toArray();
}
}
В итоге в любом контроллере нашего приложения мы можем работать с блоками вот так:
class NewsController extends Zend_Controller_Action
{
public function preDispatch()
{
// Add toolbar block on page
$this->_helper->block->add('toolbar');
// Add banners block on page
$this->_helper->block->add('banner');
}
public function viewNews()
{
// Disable banners block in this action
$this->_helper->block->remove('banner');
// ... or disable all blocks
$this->_helper->block->removeAll();
}
}
В макете (layout) любой блок можно вывести так, по имени блока:
<?=$this->layout()->toolbar?>
<?=$this->layout()->banner?>
По комметариям в коде должно быть понятно, как нужно работать с блоками. Можно еще добавить в помощник метод addAll() для загрузки всех блоков, в противоположность методу removeAll() - но это уже на ваш вкус.
Надеюсь, вам помогла эта заметка. Буду рад комментариям.
Выглядит красиво.
А зачем в
public function setConfig(Zend_Config $config)
{
$this->_config = $config->toArray();
}
переводить в массив?
Вообще-то да, справедливое замечание. Zend_Config имеет итератор, я что-то про это совсем забыл. Так что можно не приводить к массиву, а бегать по объекту.
А почему здесь используется плагин фронт-контроллера?
action-хелперы ведь тоже позволяют встроить postDispatch-событие.
Тогда вместо двух - у вас будет один класс
Плагин будет вызываться один раз, а хелпер столько раз, сколько контроллеров у нас запускается. Или я неправ?
=) Спасибо, как говорится, Слона-то я и не приметил ;)
Спасибо за наработку, очень полезная.
Правда в Модулях ведут себя не понятно. Не выводятся блоки описанные в модулях + может вывести 2 блока вместо одного….
Привет, не подскажешь как c помошью твоего плагина и хелпера подтянуть в default модуль блок из другого модуля. Например у меня есть блоки (новости, опрос и тд) - они экшены default модуля! И есть модуль memebers и в адном из его экшенов как раз какой то блок который доступен после залогинивания…. Если я захочу будучи залогиненым посмотреть страницы default модуля то блоки новости, опрос и тд будут на месте, а некий блок пропадет!!! почему он доступен только в модуле memebers? как это все можно обыграть?
спасибо за внимание!
Рекомендую обратить внимание на этот код - это хелпер вида action:
$this->action(’action’, ‘controller’, ‘module’);
Это вызовет экшен action в контроллере controller модуля module, и выведет результат в вид. Прочтите статью еще раз внимательно - там есть всё, что вы хотите.
Хочу сказать, что моё решение, возможно, не подойдет под все ваши нужды. Этот код просто пример, как можно поступить. Он не претендует на законченное решение, а лишь дает основу, при помощи которой вы можете сделать собственное решение.
Спасибо!