Details
-
Type:
Improvement
-
Status:
Resolved
-
Priority:
Minor
-
Resolution: Fixed
-
Affects Version/s: 0.9.2
-
Fix Version/s: 1.0.0 RC1
-
Component/s: Zend_Controller
-
Labels:None
Description
I would consider ZF feature complete when I can use ZF and build and entire basic application with commonplace methods, without having once to extend a single class. IMO, to extend is to add value that is too specific for the 80/20.
One of the last pieces of that puzzle for me, in the case of Zend_Controller_Action, is to not have to extend Zend_Controller_Action to be able to inject a specific view object. To me its important to have a single view object since now view helpers can optionally share a variable space within that view.
To accomplish this level of dependency injection, I suggest that the current initView() / render() / getScriptName() undergo some mutation.
Initially, i think the following invoke parameters should be allowed for view configuration:
view
viewSetBasePath
viewAddScriptPath
viewAddHelperPath
viewAddHelperPrefix
viewAddFilterPath
viewScriptName
Additionally, I would like to add the following proof-of-concept (Xend_Controller_Action) that runtime view object configuration is not only possible but adds much value to Zend_Controller_Action. It would allow the following simple usage (as well as others in init())
// in bootstrap file $controller->setControllerDirectory('../application/user/default/controllers') ->setParam('viewSetBasePath', ':moduleDir/views/') ->setParam('viewScriptName', ':action-name.phtml') ->setParam('view', $view);
<?php /** Zend_Controller_Exception */ require_once 'Zend/Controller/Action.php'; abstract class Xend_Controller_Action extends Zend_Controller_Action { /** * View object * @var Zend_View_Interface */ public $view = null; /** * View script base path * @see {render()} * @var string */ public $viewBasePath = ':moduleDir/views/'; /** * View script file name * @see {render()} * @var string */ public $viewScriptName = ':controller_name/:action_name.phtml'; /** * Render a view * * Renders a view. By default, views are found in the view script path as * <controller>/<action>.phtml. You may change the script suffix by * resetting {@link $viewSuffix}. You may omit the controller directory * prefix by specifying boolean true for $noController. * * By default, the rendered contents are appended to the response. You may * specify the named body content segment to set by specifying a $name. * * @see Zend_Controller_Response_Abstract::appendBody() * @param string|null $action Defaults to action registered in request object * @param string|null $name Response object named path segment to use; defaults to null * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script * @return void */ public function render($action = null, $name = null, $noController = false) { $view = $this->initView(); $script = $this->getViewScript($action); $this->getResponse()->appendBody( $view->render($script), $name ); } /** * Initialize View object * * Initializes {@link $view} if not otherwise a Zend_View_Interface. * * If {@link $view} is not otherwise set, instantiates a new Zend_View * object, using the 'views' subdirectory at the same level as the * controller directory for the current module as the base directory. * It uses this to set the following: * - script path = views/scripts/ * - helper path = views/helpers/ * - filter path = views/filters/ * * @return Zend_View_Interface * @throws Zend_Controller_Exception if base view directory does not exist */ public function initView() { $viewConfigure = $this->getInvokeArg('viewConfigure'); $viewSetBasePath = $this->getInvokeArg('viewSetBasePath'); require_once 'Zend/View/Interface.php'; if (isset($this->view) && ($this->view instanceof Zend_View_Interface)) { return $this->view; } elseif ( ($invokeView = $this->getInvokeArg('view')) instanceof Zend_View_Interface) { $this->view = $invokeView; } else { require_once 'Zend/View.php'; $this->view = new Zend_View(); $viewConfigure = true; } if ($viewConfigure || (count(array_intersect(array('viewSetBasePath', 'viewAddScriptPath', 'viewAddHelperPath', 'viewAddHelperPrefix', 'viewAddFilterPath', 'viewScriptName'), array_keys($this->getInvokeArgs()))) > 0) ) { $this->viewConfigure(); } return $this->view; } /** * Construct view script path * * Used by render() to determine the path to the view script. * * @param string $action Defaults to action registered in request object * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script * @return string * @throws Zend_Controller_Exception with bad $action */ public function getViewScript($action = null, $noController = false) { $request = $this->getRequest(); if (null === $action) { $action = $request->getActionName(); } elseif (!is_string($action)) { throw new Zend_Controller_Exception('Invalid action specifier for view render'); } $viewScriptName = $this->getInvokeArg('viewScriptName'); if ($viewScriptName === null) { $viewScriptName = $this->viewScriptName; } $script = $this->_translateViewPath($viewScriptName); return $script; } public function viewConfigure() { $available_directives = array('viewSetBasePath', 'viewAddScriptPath', 'viewAddHelperPath', 'viewAddHelperPrefix', 'viewAddFilterPath', 'viewScriptName'); $executable_directives = array_intersect($available_directives, array_keys($this->getInvokeArgs())); if (count($executable_directives) == 0) { $executable_directives = array('viewSetBasePath'); } foreach ($executable_directives as $execute_directive) { switch ($execute_directive) { case 'viewSetBasePath': $basePath = ($this->getInvokeArg('viewSetBasePath')) ? $this->getInvokeArg('viewSetBasePath') : $this->viewBasePath; $this->view->setBasePath($this->_translateViewPath($basePath)); break; case 'viewAddScriptPath': case 'viewAddFilterPath': $command = str_replace('view', '', $execute_directive); $this->view->$command($this->_translateViewPath($this->getInvokeArg($execute_directive))); break; case 'viewAddHelperPath': $this->addHelperPath($this->getInvokeArg($execute_directive), $this->getInvokeArg('viewAddHelperPrefix')); break; } } } private function _translateViewPath($path) { $available_replacements = array('moduleDir', 'controllerDir', 'controllerName', 'controller_name', 'actionName', 'action_name'); // gather some info from outside. $request = $this->_request; $wordDelim = $this->getFrontController()->getDispatcher()->getWordDelimiter(); $controllerDirs = $this->getFrontController()->getControllerDirectory(); $controllerDir = (array_key_exists($request->getModuleName(), $controllerDirs)) ? $controllerDirs[$request->getModuleName()] : $controllerDirs['default']; $moduleDir = dirname($controllerDir); // eval'ed for performance - on demand $replacements = array( ':controllerDir' => '$rpath = $controllerDir;', ':moduleDir' => '$rpath = $moduleDir;', ':moduleName' => '$rpath = str_replace(" ", "", ucwords(strreplace($wordDelim, " ", strtolower($request->getModuleName()))));', ':module_name' => '$rpath = str_replace(" ", "_", str_replace($wordDelim, " ", strtolower($request->getModuleName())));', ':module-name' => '$rpath = str_replace(" ", "-", str_replace($wordDelim, " ", strtolower($request->getModuleName())));', ':controllerName' => '$rpath = str_replace(" ", "", ucwords(str_replace($wordDelim, " ", strtolower($request->getControllerName()))));', ':controller_name' => '$rpath = str_replace(" ", "_", str_replace($wordDelim, " ", strtolower($request->getControllerName())));', ':controller-name' => '$rpath = str_replace(" ", "-", str_replace($wordDelim, " ", strtolower($request->getControllerName())));', ':actionName' => '$rpath = str_replace(" ", "", ucwords(str_replace($wordDelim, " ", strtolower($request->getActionName()))));', ':action_name' => '$rpath = str_replace(" ", "_", str_replace($wordDelim, " ", strtolower($request->getActionName())));', ':action-name' => '$rpath = str_replace(" ", "-", str_replace($wordDelim, " ", strtolower($request->getActionName())));' ); foreach ($replacements as $replacement_key => $replacement_value) { if (preg_match('/'.$replacement_key.'/', $path)) { eval($replacement_value); $path = preg_replace('/'.$replacement_key.'/', $rpath, $path); } } return $path; } }
Especially for specify an own viewScriptName i would prefer such a behaviour, because i use naming for Views like its used by Controllers (Index/IndexView.phtml). Now i have to overwrite the hole initView-Method