Problem
When using standard Zend Framework's routing — default scheme of building URI/module/controller/action/parameter 1/value 1/…/…../ is used. We can change a method of URI recognition with a help of routers and create any possible routes, but one issue still remains — only one unique controller and one action with a list of parameters will be assigned to one route. According to this only one action executes for one web-page and this action must build the entire web-page. This approach is clear for developer and follows MVC paradigm, but not very suitable for creating CMS (content management system).
Tree structure of a site's web-pages and some functional modules, which can be placed on different web-pages of a site, in different places, that are defined by design templates of web-pages underlies in a heart of most CMS. And while building a resulting code of a web-page some modules are called; as a result for each call — parts of a web-page are created (menu, newslines, informers, blocks of content), after that each part are placed on a page according to design template of a web-page. Such structure are very suitable for site constructing with a help of administration interface. According to MVC paradigm it is logically to create controller with some actions for each functional module. Such controllers implement some module's functionality in different situations. On the other hand, standard router of Zend Framework does not allow using a few controllers for one page creation.
Let's find ways to resolve this problem. In a standard loop of dispatching a routing process executes. On this stage, key parameters are formed in a query object: current language (lang), module, controller, action and parameters for current action. After that dispatching process starts. This process implemented as a simple loop. Body of this loop attempts to create an instance of a class, which name is formed dynamically according to module and controller names. Then attempt to execute a method which name is formed according to action name is carried out. After that dispatcher checks for a flag of dispatching completion. This flag controls iterations of a loop. We can drop this flag in controller and set new query values, thereby we can pass management to another controller, creating chains of controllers in such a way. Each controller returns result to resulting object, which is represented as an array of named segments. By the end of dispatching process, on a basis of a resulting object the end web-page are formed.
In that way, router on a basis of URI has to define the hole list of controllers and actions, that are required for a web-page building, set the name of first controller from the chain of controllers and pass it to dispatching loop. Then each controller after execution has to check a list of controllers and actions; and if there are unexecuted actions — drop the flag of dispatching completion and set new values of query object. To resolve this task we can use standard plugin ZendController ActionStack of Zend Framework. ActionStack allows stack management and works as a plugin of postDispatch.
So, to resolve titled task, calling different actions of different controllers according to URI, we have to create our own router, which will define a list of required controllers and actions, and then with a help of ActionStack plugin add them to query stack. We will use ActionStack in auxiliary controller; our router will pass management to it. To implement this we will add one more router to a standard bootstrap-file. After that combination of standard and our systems of routing are possible.
$sys = new Zend_Controller_Router_Route_Pages( $db, array('controller' => 'sys', 'action' => 'route' ); )
$router->addRoute('sys', $sys); Creating our own router.
We will create router on a basis of Zend_Controller_Router_Route_Interface(). Main method of it match() — checks the received URI for coincidence with a web-page address in DB; if matches found — a list of controllers and actions will be retrieved. /** * Compares reported by user path with already defined address of a web-page.
* If matches found - array with previously defined constants are retrieved.
* @param string $path URI path, reported by user for comparison
* @return array|false Array of defined values or false if no matches found
*/ public function match($path) {
if( false === ($page = $this->_getPage($path)) ) return false;
/* Looking for path coincidence.
If matches found - defines array and retrieves list of controllers and actions */
$values = array(); values[$this->_moduleKey] = $this->_defaults[$this->_moduleKey];
$values[$this->_controllerKey] = $this->_defaults[$this->_controllerKey];
$values[$this->_actionKey] = $this->_defaults[$this->_actionKey];
$values[$this->_pageIdKey] = $page['id'];
$values[$this->_pageControllersKey] = $this->_getPageControllers($values[$this->_pageIdKey]);
//retrieves list of controllers and actions $values[$this->_pageLayoutKey] = $page['layout_name'];
$values[$this->_pageAreasCntKey] = $page['areas_cnt'];
return $values; }
Let's define auxiliary controller with routeAction() as main one.
class SysController extends Zend_Controller_Action
{
public function indexAction() { $this->routeAction();
}
/* * Retrieves a list of controllers from query object and assigns them to ActionStack for execution. */
public function routeAction() {
// turn of render (display) for this action
$this->_helper->viewRenderer->setNoRender();
// retrieves query object $request = $this->getRequest(); // retrieves array, which was created by router
$controllers = $request->getParam('page_controllers');
// for each controller
foreach((array)$controllers as $cont) {
//sets it for execution
$this->_helper->ActionStack($cont['action'], $cont['name'], null, $cont); }
/* * sets web-page template and passes to template a list of controllers (for display needs inside template) */
$this->_helper->layout->setLayout($request->getParam('page_layout'));
$this->_helper->layout->__set('controllers', $request->getParam('page_controllers'));
}
}
EggmenGroup LLC © 2010