Issues

ZF-1545: Allow setting of arbitrary Request/Response objects on ViewRenderer

Description

At present ViewRenderer maintains a singular relationship with a specific Zend_Controller_Action instance, which it relies upon for the initial capture of Request/Response objects. It then uses the Request object for all subsequent ViewScriptSpec formatting. Likewise it always uses the same Response object for capturing output. The problem is that this makes dispatching additional Actions (e.g. nested) which require the ViewRenderer extremely difficult. It can be worked around to some extent within a Controller class action method - outside this it's not possible. This limits reuse of controllers since the extra View management logic needs to be tacked on later when/if needed.

Adding setRequest()/setResponse() methods would help alleviate this problem, and allow programmers to temporarily override the initial objects with custom ones to enable more flexible rendering options.

As an aside, this problem is the same one afflicting the Widget View Helper example on the issue tracker. The Widget cloned Request object is never utilised by the ViewRenderer (once the request is set it's not updated from the controller) so it keeps trying to render the exact same template over and over.

Comments

Assign to Matthew.

While I see this as a fringe case, adding the accessors is fairly trivial. My understanding is this:

  • If no request/response set, grab from action controller (always; don't cache)
  • If set, use the one set (always)

Is this what you intend? The issue I see is that if you chain several actions, but set the request in the first, how can you be sure that it's updated properly? If you grab always from the action controller, you can be assured that it's been updated during each cycle of the dispatch loop. My main worry is that this will introduce a behaviour that's hard to detect and debug. Can you provide a concrete use case so I can better understand how you envision this working, as well as the rationale for needing the functionality?

It is a fringe issue. The suggested setters were for one specific use case (will add below).

The root of the problem really is that HelperBroker keeps a static record of Helpers. So a nested Request never has an opportunity to use a fresh HelperBroker instance, and just ends up inheriting helpers instantiated for the parent request. Not suggesting this change.

In any case, my use case is the Widget View Helper. It's still the only available method of dispatching a controller from within an unrelated class (e.g. Zend_View) and receiving back output. With the ViewRenderer now entering circulation it needed some updating to temporarily reset during a Widget dispatch so it could render a View specific for that Request. A non-cached action controller system would also work, but it seems the more complex solution since you'd still need to reinstate the original objects for the parent action controller being nested into.

Less vague mumbling from me; more code...


/**
 * Helper for returning the output from dispatching a new Request to the
 * Controller using the provided Action name, and optional Controller/Module
 * names and other parameters.
 * 
 * @package    Zend_View
 * @subpackage Helpers
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_View_Helper_Controller {

    /**
     * Dispatch a request via the Controller and fetch the resulting rendered
     * View to return
     *
     * @param string $action
     * @param string $controller
     * @param string $module
     * @param array $params
     * @returns string
     */
    public function controller($action, $controller = null, $module = null, array $params = null)
    {
        $front = Zend_Controller_Front::getInstance();
        if (!$front->getParam('noViewRenderer') && Zend_Controller_Action_HelperBroker::hasHelper('viewRenderer')) {
            $enableViewRendererReset = true;
        }
        $front = Zend_Controller_Front::getInstance();
        $request = clone $front->getRequest();
        $request->setActionName($action);
        if (!is_null($controller)) {
            $request->setControllerName($controller);
        }
        if (!is_null($module) && !is_null($controller)) {
            $request->setModuleName($module);
        }
        if (!is_null($params)) {
            $request->setParams($params);
        }
        $response = new Zend_Controller_Response_Http();
        if (isset($enableViewRendererReset)) {
            $viewRenderer = Zend_Controller_Action_HelperBroker::getExistingHelper('viewRenderer');
            $__originalRequest = $viewRenderer->getRequest();
            $__originalResponse = $viewRenderer->getResponse();
            $viewRenderer->setRequest($request);
            $viewRenderer->setResponse($response);
        }
        $front->getDispatcher()->dispatch($request, $response);
        // reset original objects back on ViewRenderer
        if (isset($enableViewRendererReset)) {
            $viewRenderer->setRequest($__originalRequest);
            $viewRenderer->setResponse($__originalResponse);
        }
        return $response->getBody();
    }

}

Fix version after 1.0.1.

First, please pardon me if I am misunderstanding some things; I don't mean to muddy the waters. :)

It appears that this issue will be resolved by approval of the {{Zend_View_Helper_Controller}} portion of the Zend_View Enhanced proposal; is this correct?

Sorry it is not clear to me, but to which class is it proposed that we add {{setRequest()}} and {{setResponse()}} methods, or would this be superseded by the above so as to avoid unnecessary complexity from potential pitfalls?

This was fixed some time ago with another bug. Helpers now fetch the request and response objects directly from the currently registered action controller, or the front controller if no action controller is registered. Thus, you can pass in request/response objects to an action controller, and these will be used directly by the ViewRenderer.