Zend Framework

Zend_Controller_Action lacks view dependency injection

Details

  • Type: Improvement Improvement
  • Status: Resolved Resolved
  • Priority: Minor 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;
    }

}

Activity

Hide
Sebastian Krebs added a comment -

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

Show
Sebastian Krebs added a comment - 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
Hide
Shaun Rowe added a comment -

I would be very keen to see this improvement brought to the core. I also have in place a similar extension to Zend_Controller_Action

Show
Shaun Rowe added a comment - I would be very keen to see this improvement brought to the core. I also have in place a similar extension to Zend_Controller_Action
Hide
michael depetrillo added a comment -

This is a good idea but I don't think you need all those parameters.

If Zend_Controller_Action could simply look for a param named 'view' in the front controller then subclassing the action controller would not be necessary.

Developers should be able to set the view script/helper/filter path in the bootstrap or via a plugin.

Show
michael depetrillo added a comment - This is a good idea but I don't think you need all those parameters. If Zend_Controller_Action could simply look for a param named 'view' in the front controller then subclassing the action controller would not be necessary. Developers should be able to set the view script/helper/filter path in the bootstrap or via a plugin.
Hide
Ralph Schindler added a comment -

if you set a hard coded path in your bootstrap, then you leave out the possibilities (and best practices IMO) of being able to house your modules view scripts within your modules directory. Since there could be many different paths to your modules, you can't set a hard coded path at bootstrap time, you only know the skeleton of the path RELATIVE to the dispatched module/controller.. and the script name is relative to the action dispatched. So if I have deployed an application with the Conventional/or Classical Modular Directory Structure (http://framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s+Directory+Layout), then your path to your view scripts will not be completely known until the actions initView() and getScriptName() methods are called.

Make sense?
-ralph

Show
Ralph Schindler added a comment - if you set a hard coded path in your bootstrap, then you leave out the possibilities (and best practices IMO) of being able to house your modules view scripts within your modules directory. Since there could be many different paths to your modules, you can't set a hard coded path at bootstrap time, you only know the skeleton of the path RELATIVE to the dispatched module/controller.. and the script name is relative to the action dispatched. So if I have deployed an application with the Conventional/or Classical Modular Directory Structure (http://framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s+Directory+Layout), then your path to your view scripts will not be completely known until the actions initView() and getScriptName() methods are called. Make sense? -ralph
Hide
Matthew Weier O'Phinney added a comment -

I've spoken briefly with Ralph about this off-list, but I'm bringing it back here. I'm worried about adding code such as he suggests as it introduces a lot of coupling between the components. While I realize that both Zend_Controller_Action and Zend_View are components of MVC and should have some coupling, I'm worried that introducing this type of functionality explicitly in the action controller will lead to maintenance headaches later should, for instance, the design of Zend_View change.

It appears to me that the following goals are desired:

  • Globally available view object for all controllers and actions
  • No need to explicitly instantiate view object within controllers
  • Class prefixes for view helpers and filters based on current module; ability to autospecify these
  • Ability to set default view rendering options for all controllers
  • Ability to autorender (i.e., no explicit call to render() required

As it turns out, we already have a way to accomplish all of the above: via an action controller helper.

If you set an action controller helper from your bootstrap, it will be globally available to any action controller. Additionally, action controller helpers tie in to preDispatch() and postDispatch(), allowing us to set options in the view object automatically prior to executing a controller's action method, and also perform actions after the action method is done – such as rendering. Additionally, we have access to the request object, allowing us to determine the current module, and thus the path to view scripts and a class prefix to use with helpers and filters.

I'm attaching a proof of concept below. The usage is pretty nice:

// in your bootstrap (needed so helper is present at preDispatch()):
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_View());

// and a controller class, foo module:
class Foo_BarController extends Zend_Controller_Action
{
    // renders bar/index.phtml by default; no action required
    public function indexAction()
    {
    }

    // render bar/form.phtml instead of bar/baz.phtml
    public function bazAction()
    {
        $this->_helper->view('form');
    }

    // don't render anything
    public function hideAction()
    {
        $this->_helper->view->noRender();
    }

    // renders bar/populate.phtml with variable 'foo' set to 'bar'
    public function populateAction()
    {
        $this->view->foo = 'bar';
    }
}

// in one of your view scripts:
<?php $this->foo(); // call Foo_Bar_View_Helper_Foo::foo() ?>

As you can see, it simplifies code tremendously in the controllers, and offers quite a lot of flexibility. You can also at any time set a customized view object into it, or pull out the view object and modify it (clear variables, set new paths, etc.).

The advantage to this is that it keeps the action controller code very specific to its primary task; it simply acts as the glue between a controller and a view. Additionally, it's completely optional: if you don't want to use it, you don't have to; if your code requires a different approach to views, dont' use it.

Thoughts?

<?php
class Zend_Controller_Action_Helper_View extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var Zend_View_Interface
     */
    public $view;

    /**
     * Front controller instance
     * @var Zend_Controller_Front
     */
    protected $_frontController;

    /**
     * Whether or not to use a controller name as a subdirectory when rendering
     * @var boolean
     */
    protected $_noController    = false;

    /**
     * Whether or not to autorender postDispatch
     * @var boolean
     */
    protected $_noRender        = false;

    /**
     * Which named segment of the response to utilize
     * @var string
     */
    protected $_responseSegment = null;

    /**
     * Which action view script to render
     * @var string
     */
    protected $_scriptAction    = null;

    /**
     * View script suffix
     * @var string
     */
    protected $_viewSuffix      = 'phtml';

    public function setView(Zend_View_Interface $view)
    {
        $this->view = $view;
    }

    /**
     * Retrieve front controller instance
     * 
     * @return Zend_Controller_Front
     */
    public function getFrontController()
    {
        if (null === $this->_frontController) {
            $this->_frontController = Zend_Controller_Front::getInstance();
        }

        return $this->_frontController;
    }

    protected function _generateDefaultPrefix()
    {
        if (null === $this->_actionController) {
            return 'Zend_View_Helper';
        }

        $class = get_class($this->_actionController);

        if (!strstr($class, '_')) {
            $prefix = 'Zend_View_Helper';
        } else {
            $prefix = substr($class, 0, strpos($class, '_')) . '_View_Helper';
        }

        return $prefix;
    }

    /**
     * Retrieve base path based on location of 
     * 
     * @return void
     */
    protected function _getBasePath()
    {
        if (null === $this->_actionController) {
            return '.' . DIRECTORY_SEPARATOR . 'views';
        }

        $front      = $this->getFrontController();
        $modulePath = $front->getControllerDirectory($this->getRequest()->getModuleName());
        if (null === $modulePath) {
            throw new Zend_Controller_Action_Exception('Cannot determine view base path: invalid module "' . $module . '"in request');

        }
        $path = realpath($modulePath . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views');
        return $path;
    }

    /**
     * Initialize the view object
     *
     * $options may contain the following keys:
     * - noController - flag indicating whether or not to look for view scripts in subdirectories named after the controller
     * - noRender - flag indicating whether or not to autorender postDispatch()
     * - responseSegment - which named response segment to render a view script to
     * - scriptAction - what action script to render
     * - viewSuffix - what view script filename suffix to use
     * 
     * @param  string $path 
     * @param  string $prefix 
     * @param  array $options 
     * @return void
     */
    public function initView($path = null, $prefix = null, array $options = array())
    {
        if (null === $this->view) {
            $this->setView(new Zend_View());
        }

        if (null === $path) {
            $path = $this->_getBasePath();
        }

        if (null === $prefix) {
            $prefix = $this->_generateDefaultPrefix();
        }

        $this->view->addBasePath($path, $prefix);

        $this->_noRender        = false;
        $this->_responseSegment = null;
        $this->_scriptAction    = null;

        foreach ($options as $key => $value)
        {
            switch ($key) {
                case 'noController':
                case 'noRender':
                    $property = '_' . $key;
                    $this->{$property} = ($value) ? true : false;
                    break;
                case 'responseSegment':
                case 'scriptAction':
                case 'viewSuffix':
                    $property = '_' . $key;
                    $this->{$property} = (string) $value;
                    break;
                default:
                    break;
            }
        }

        if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
            $this->_actionController->view       = $this->view;
            $this->_actionController->viewSuffix = $this->_viewSuffix;
        }
    }

    /**
     * preDispatch - initialize view
     * 
     * @return void
     */
    public function preDispatch()
    {
        $this->initView();
    }

    /**
     * Set the noRender flag (i.e., whether or not to autorender)
     * 
     * @param  boolean $flag 
     * @return Wopnet_Controller_Helper_View
     */
    public function setNoRender($flag = true)
    {
        $this->_noRender = ($flag) ? true : false;
        return $this;
    }

    /**
     * Retrieve noRender flag value
     * 
     * @return boolean
     */
    public function getNoRender()
    {
        return $this->_noRender;
    }

    /**
     * Set the view script to use
     * 
     * @param  string $name 
     * @return Wopnet_Controller_Helper_View
     */
    public function setScriptAction($name)
    {
        $this->_scriptAction = (string) $name;
        return $this;
    }

    /**
     * Retrieve view script name
     * 
     * @return string
     */
    public function getScriptAction()
    {
        return $this->_scriptAction;
    }

    /**
     * Set the response segment name
     * 
     * @param  string $name 
     * @return Wopnet_Controller_Helper_View
     */
    public function setResponseSegment($name)
    {
        $this->_responseSegment = (string) $name;
        return $this;
    }

    /**
     * Retrieve named response segment name
     * 
     * @return string
     */
    public function getResponseSegment()
    {
        return $this->_responseSegment;
    }

    /**
     * Set the noController flag (i.e., whether or not to render into controller subdirectories)
     * 
     * @param  boolean $flag 
     * @return Wopnet_Controller_Helper_View
     */
    public function setNoController($flag = true)
    {
        $this->_noController = ($flag) ? true : false;
        return $this;
    }

    /**
     * Retrieve noController flag value
     * 
     * @return boolean
     */
    public function getNoController()
    {
        return $this->_noController;
    }

    /**
     * Set options for rendering a view script
     * 
     * @param  string $action View script to render
     * @param  string $name Response named segment to render to
     * @param  boolean $noController Whether or not to render within a subdirectory named after the controller
     * @return Wopnet_Controller_Helper_View
     */
    public function setRender($action = null, $name = null, $noController = false)
    {
        $this->_scriptAction    = (string) $action;
        $this->_responseSegment = (string) $name;
        $this->_noController    = ($noController) ? true : false;

        return $this;
    }

    /**
     * postDispatch - auto render a view
     *
     * Only autorenders if: 
     * - _noRender is false
     * - action controller is present
     * - request has not been re-dispatched (i.e., _forward() has not been called)
     * 
     * @return void
     */
    public function postDispatch()
    {
        if (!$this->_noRender 
            && (null !== $this->_actionController)
            && $this->getRequest()->isDispatched())
        {
            $this->_actionController->render($this->_scriptAction, $this->_responseSegment, $this->_noController);
        }
    }

    /**
     * Use this helper as a method; proxies to setRender()
     * 
     * @param  string $action 
     * @param  string $name 
     * @param  boolean $noController 
     * @return void
     */
    public function direct($action = null, $name = null, $noController = false)
    {
        $this->setRender($action, $name, $noController);
    }
}
Show
Matthew Weier O'Phinney added a comment - I've spoken briefly with Ralph about this off-list, but I'm bringing it back here. I'm worried about adding code such as he suggests as it introduces a lot of coupling between the components. While I realize that both Zend_Controller_Action and Zend_View are components of MVC and should have some coupling, I'm worried that introducing this type of functionality explicitly in the action controller will lead to maintenance headaches later should, for instance, the design of Zend_View change. It appears to me that the following goals are desired:
  • Globally available view object for all controllers and actions
  • No need to explicitly instantiate view object within controllers
  • Class prefixes for view helpers and filters based on current module; ability to autospecify these
  • Ability to set default view rendering options for all controllers
  • Ability to autorender (i.e., no explicit call to render() required
As it turns out, we already have a way to accomplish all of the above: via an action controller helper. If you set an action controller helper from your bootstrap, it will be globally available to any action controller. Additionally, action controller helpers tie in to preDispatch() and postDispatch(), allowing us to set options in the view object automatically prior to executing a controller's action method, and also perform actions after the action method is done – such as rendering. Additionally, we have access to the request object, allowing us to determine the current module, and thus the path to view scripts and a class prefix to use with helpers and filters. I'm attaching a proof of concept below. The usage is pretty nice:
// in your bootstrap (needed so helper is present at preDispatch()):
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_View());

// and a controller class, foo module:
class Foo_BarController extends Zend_Controller_Action
{
    // renders bar/index.phtml by default; no action required
    public function indexAction()
    {
    }

    // render bar/form.phtml instead of bar/baz.phtml
    public function bazAction()
    {
        $this->_helper->view('form');
    }

    // don't render anything
    public function hideAction()
    {
        $this->_helper->view->noRender();
    }

    // renders bar/populate.phtml with variable 'foo' set to 'bar'
    public function populateAction()
    {
        $this->view->foo = 'bar';
    }
}

// in one of your view scripts:
<?php $this->foo(); // call Foo_Bar_View_Helper_Foo::foo() ?>
As you can see, it simplifies code tremendously in the controllers, and offers quite a lot of flexibility. You can also at any time set a customized view object into it, or pull out the view object and modify it (clear variables, set new paths, etc.). The advantage to this is that it keeps the action controller code very specific to its primary task; it simply acts as the glue between a controller and a view. Additionally, it's completely optional: if you don't want to use it, you don't have to; if your code requires a different approach to views, dont' use it. Thoughts?
<?php
class Zend_Controller_Action_Helper_View extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var Zend_View_Interface
     */
    public $view;

    /**
     * Front controller instance
     * @var Zend_Controller_Front
     */
    protected $_frontController;

    /**
     * Whether or not to use a controller name as a subdirectory when rendering
     * @var boolean
     */
    protected $_noController    = false;

    /**
     * Whether or not to autorender postDispatch
     * @var boolean
     */
    protected $_noRender        = false;

    /**
     * Which named segment of the response to utilize
     * @var string
     */
    protected $_responseSegment = null;

    /**
     * Which action view script to render
     * @var string
     */
    protected $_scriptAction    = null;

    /**
     * View script suffix
     * @var string
     */
    protected $_viewSuffix      = 'phtml';

    public function setView(Zend_View_Interface $view)
    {
        $this->view = $view;
    }

    /**
     * Retrieve front controller instance
     * 
     * @return Zend_Controller_Front
     */
    public function getFrontController()
    {
        if (null === $this->_frontController) {
            $this->_frontController = Zend_Controller_Front::getInstance();
        }

        return $this->_frontController;
    }

    protected function _generateDefaultPrefix()
    {
        if (null === $this->_actionController) {
            return 'Zend_View_Helper';
        }

        $class = get_class($this->_actionController);

        if (!strstr($class, '_')) {
            $prefix = 'Zend_View_Helper';
        } else {
            $prefix = substr($class, 0, strpos($class, '_')) . '_View_Helper';
        }

        return $prefix;
    }

    /**
     * Retrieve base path based on location of 
     * 
     * @return void
     */
    protected function _getBasePath()
    {
        if (null === $this->_actionController) {
            return '.' . DIRECTORY_SEPARATOR . 'views';
        }

        $front      = $this->getFrontController();
        $modulePath = $front->getControllerDirectory($this->getRequest()->getModuleName());
        if (null === $modulePath) {
            throw new Zend_Controller_Action_Exception('Cannot determine view base path: invalid module "' . $module . '"in request');

        }
        $path = realpath($modulePath . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views');
        return $path;
    }

    /**
     * Initialize the view object
     *
     * $options may contain the following keys:
     * - noController - flag indicating whether or not to look for view scripts in subdirectories named after the controller
     * - noRender - flag indicating whether or not to autorender postDispatch()
     * - responseSegment - which named response segment to render a view script to
     * - scriptAction - what action script to render
     * - viewSuffix - what view script filename suffix to use
     * 
     * @param  string $path 
     * @param  string $prefix 
     * @param  array $options 
     * @return void
     */
    public function initView($path = null, $prefix = null, array $options = array())
    {
        if (null === $this->view) {
            $this->setView(new Zend_View());
        }

        if (null === $path) {
            $path = $this->_getBasePath();
        }

        if (null === $prefix) {
            $prefix = $this->_generateDefaultPrefix();
        }

        $this->view->addBasePath($path, $prefix);

        $this->_noRender        = false;
        $this->_responseSegment = null;
        $this->_scriptAction    = null;

        foreach ($options as $key => $value)
        {
            switch ($key) {
                case 'noController':
                case 'noRender':
                    $property = '_' . $key;
                    $this->{$property} = ($value) ? true : false;
                    break;
                case 'responseSegment':
                case 'scriptAction':
                case 'viewSuffix':
                    $property = '_' . $key;
                    $this->{$property} = (string) $value;
                    break;
                default:
                    break;
            }
        }

        if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
            $this->_actionController->view       = $this->view;
            $this->_actionController->viewSuffix = $this->_viewSuffix;
        }
    }

    /**
     * preDispatch - initialize view
     * 
     * @return void
     */
    public function preDispatch()
    {
        $this->initView();
    }

    /**
     * Set the noRender flag (i.e., whether or not to autorender)
     * 
     * @param  boolean $flag 
     * @return Wopnet_Controller_Helper_View
     */
    public function setNoRender($flag = true)
    {
        $this->_noRender = ($flag) ? true : false;
        return $this;
    }

    /**
     * Retrieve noRender flag value
     * 
     * @return boolean
     */
    public function getNoRender()
    {
        return $this->_noRender;
    }

    /**
     * Set the view script to use
     * 
     * @param  string $name 
     * @return Wopnet_Controller_Helper_View
     */
    public function setScriptAction($name)
    {
        $this->_scriptAction = (string) $name;
        return $this;
    }

    /**
     * Retrieve view script name
     * 
     * @return string
     */
    public function getScriptAction()
    {
        return $this->_scriptAction;
    }

    /**
     * Set the response segment name
     * 
     * @param  string $name 
     * @return Wopnet_Controller_Helper_View
     */
    public function setResponseSegment($name)
    {
        $this->_responseSegment = (string) $name;
        return $this;
    }

    /**
     * Retrieve named response segment name
     * 
     * @return string
     */
    public function getResponseSegment()
    {
        return $this->_responseSegment;
    }

    /**
     * Set the noController flag (i.e., whether or not to render into controller subdirectories)
     * 
     * @param  boolean $flag 
     * @return Wopnet_Controller_Helper_View
     */
    public function setNoController($flag = true)
    {
        $this->_noController = ($flag) ? true : false;
        return $this;
    }

    /**
     * Retrieve noController flag value
     * 
     * @return boolean
     */
    public function getNoController()
    {
        return $this->_noController;
    }

    /**
     * Set options for rendering a view script
     * 
     * @param  string $action View script to render
     * @param  string $name Response named segment to render to
     * @param  boolean $noController Whether or not to render within a subdirectory named after the controller
     * @return Wopnet_Controller_Helper_View
     */
    public function setRender($action = null, $name = null, $noController = false)
    {
        $this->_scriptAction    = (string) $action;
        $this->_responseSegment = (string) $name;
        $this->_noController    = ($noController) ? true : false;

        return $this;
    }

    /**
     * postDispatch - auto render a view
     *
     * Only autorenders if: 
     * - _noRender is false
     * - action controller is present
     * - request has not been re-dispatched (i.e., _forward() has not been called)
     * 
     * @return void
     */
    public function postDispatch()
    {
        if (!$this->_noRender 
            && (null !== $this->_actionController)
            && $this->getRequest()->isDispatched())
        {
            $this->_actionController->render($this->_scriptAction, $this->_responseSegment, $this->_noController);
        }
    }

    /**
     * Use this helper as a method; proxies to setRender()
     * 
     * @param  string $action 
     * @param  string $name 
     * @param  boolean $noController 
     * @return void
     */
    public function direct($action = null, $name = null, $noController = false)
    {
        $this->setRender($action, $name, $noController);
    }
}
Hide
Matthew Weier O'Phinney added a comment -

Ralph and I have discussed this offline, and are breaking it into two phases.

  • ViewRenderer action helper (as proposed above) to tackle view injection
  • ability to specify view base path and view script path spec via router like paths

The first has been committed to the incubator. The second will be worked on separately, and may be incorporated in the ViewRenderer at a later date.

Show
Matthew Weier O'Phinney added a comment - Ralph and I have discussed this offline, and are breaking it into two phases.
  • ViewRenderer action helper (as proposed above) to tackle view injection
  • ability to specify view base path and view script path spec via router like paths
The first has been committed to the incubator. The second will be worked on separately, and may be incorporated in the ViewRenderer at a later date.
Hide
Matthew Weier O'Phinney added a comment -

I have added a new method to Zend_Controller_Action, renderScript(), which takes a specific script path and name, as well as a response segment; this will allow helpers such as the ViewRenderer to implement their own logic for determining the script name and then rendering it. At this point, the infrastructure is now in place to allow injection of the sort Ralph outlines.

I have received the go-ahead to commit the ViewRenderer to core, and will do so this week.

Show
Matthew Weier O'Phinney added a comment - I have added a new method to Zend_Controller_Action, renderScript(), which takes a specific script path and name, as well as a response segment; this will allow helpers such as the ViewRenderer to implement their own logic for determining the script name and then rendering it. At this point, the infrastructure is now in place to allow injection of the sort Ralph outlines. I have received the go-ahead to commit the ViewRenderer to core, and will do so this week.
Hide
Matthew Weier O'Phinney added a comment -

I've promoted the component to core, and contacted Ralph to see if he wants to implement the script path customizations now or at a later date.

Show
Matthew Weier O'Phinney added a comment - I've promoted the component to core, and contacted Ralph to see if he wants to implement the script path customizations now or at a later date.
Hide
Matthew Weier O'Phinney added a comment -

I have implemented the initial functionality to allow script path customizations to core, but it currently breaks tests; I should have changes ready sometime Thursday, 24 May 2007.

Show
Matthew Weier O'Phinney added a comment - I have implemented the initial functionality to allow script path customizations to core, but it currently breaks tests; I should have changes ready sometime Thursday, 24 May 2007.
Hide
Matthew Weier O'Phinney added a comment -

Zend_Controller_Action_Helper_ViewRenderer is in core and supports the following:

  • Initialize view object in action controller automatically
  • Render view automatically when action ends
  • Allow injecting custom path specifications to use when setting view base path and view script paths

This helper will be on by default with the 1.0.0RC1 release.

Show
Matthew Weier O'Phinney added a comment - Zend_Controller_Action_Helper_ViewRenderer is in core and supports the following:
  • Initialize view object in action controller automatically
  • Render view automatically when action ends
  • Allow injecting custom path specifications to use when setting view base path and view script paths
This helper will be on by default with the 1.0.0RC1 release.

People

Vote (2)
Watch (2)

Dates

  • Created:
    Updated:
    Resolved: