View Source

<h1>Zend_Controller 2.0 RoadMap</h1>

<ac:macro ac:name="toc-zone"><ac:parameter ac:name="type">list</ac:parameter><ac:parameter ac:name="minLevel">2</ac:parameter><ac:parameter ac:name="maxLevel">3</ac:parameter><ac:parameter ac:name="location">top</ac:parameter><ac:rich-text-body>

<h2>Front Controller improvements</h2>

<h3>Reference implementation</h3>
<p>A proof-of-concept has been developed and is available at:</p>
<ul>
<li><a class="external-link" href="http://github.com/weierophinney/phly/tree/mvcfsm/Phly_Mvc/">http://github.com/weierophinney/phly/tree/mvcfsm/Phly_Mvc/</a></li>
</ul>


<h3>Goals</h3>
<p>The goals of the rewrite are to make a component that is:</p>

<ul>
<li>Lightweight</li>
<li>Flexible</li>
<li>Easy to extend</li>
<li>Easy to create and use custom implementations</li>
</ul>


<p>In evaluating architectures, two principal ideas rose as potential solutions:</p>

<ul>
<li>Finite State Machine (FSM)</li>
<li>Event-driven model</li>
</ul>


<p>The reasons an FSM were considered were several. First, the front controller defines several distinct states already: routing, dispatching, and emitting the response; errors are also a distinct state, though currently muddled into the rest of the architecture. <em>(You could potentially add &quot;bootstrapping&quot; to that list.)</em> However, in current ZF iterations, there's no easy way to transition between or skip states; e.g., if an exception is detected, an entire iteration of the dispatch loop is executed; if a redirect is made, you have to either complete the remainder of the dispatch loop or call &quot;exit&quot; (a horrible hack).</p>

<p>A Finite State Machine largely solves these problems. If you need to go to another state, you simply <code>goto it</code>. That said, there are some limitations to <code>goto</code>: it only works with labels defined in the current scope; you cannot call goto from an invoked method, for instance. One way to mitigate this is to keep track of state in an object.</p>

<p>At this point, an event driven model makes sense. In this case, a token, the event, is passed to all invoked methods. This allows those methods to alter the event state such that when the process returns to the invoking method, the state may be checked and control dispatched to the appropriate state accordingly.</p>

<p>To make the concept more powerful, if each state allows for one or more topics to which listeners may subscribe, complex chains of actions may be created easily. Using <a href="http://github.com/weierophinney/phly/tree/mvcfsm/Phly_PubSub/">Phly_PubSub</a> (to be proposed to ZF as well), this also allows us to check the event state after each subscriber completes to determine if we need to transition to a new state &ndash; allowing the subscribers to help determine execution flow.</p>

<p>Each subscriber is passed one argument and one argument only: the <code>Event</code>. As such, the event now becomes a repository/service locator. In the proposed MVC implementation, it holds the request and response objects, the pubsub provider (so additional subscribers may be registered at any time), and the state; it can potentially hold anything, however, as it extends <code>ArrayObject</code>.</p>

<p>Basic operation is something like this:</p>
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
// where $stateChanged is a closure checking the state of the Event
routing:
$pubsub->publishUntil($stateChanged, 'mvc.routing.pre', $e);
$pubsub->publishUntil($stateChanged, 'mvc.routing', $e);
$pubsub->publishUntil($stateChanged, 'mvc.routing.post', $e);
$e->setState('dispatching');

dispatching:
$pubsub->publishUntil($stateChanged, 'mvc.dispatching.pre', $e);
$pubsub->publishUntil($stateChanged, 'mvc.dispatching', $e);
$pubsub->publishUntil($stateChanged, 'mvc.dispatching.post', $e);
$e->setState('response');

response:
$pubsub->publishUntil($stateChanged, 'mvc.response.pre', $e);
$pubsub->publishUntil($stateChanged, 'mvc.response', $e);
$pubsub->publishUntil($stateChanged, 'mvc.response.post', $e);
return;

error:
$pubsub->publishUntil($stateChanged, 'mvc.error', $e);
]]></ac:plain-text-body></ac:macro>

<p>The actual workings are slightly more complex, but the ideas hold.</p>

<p>The ramifications of this architecture are intriguing:</p>

<ul>
<li>There's no need to use <em>any</em> ZF classes with the front controller (other than the event, event manager (pubsub), and front controller); you can potentially create your own structure and callbacks to use.</li>
</ul>


<ul>
<li>If interfaces are provided, they simply provide a hook into the default use case we choose to provide.</li>
</ul>


<ul>
<li>Developers could potentially override the FrontController and provide their own application states, providing for custom workflows.</li>
</ul>


<h3>Additional Components</h3>
<p>The proposed MVC does aim to provide a default work flow for the front controller. This work flow will include:</p>

<ul>
<li>Request object<br />
This object will simply provide access to the current environment, much as <code>Zend_Controller_Request</code> does already. The primary differences will be that injection into the various &quot;superglobal&quot; accessors will be built in. </li>
<li>Response object<br />
This object will be a container for response-related items, including headers, values related to rendering, and potentially cookies. The primary interface method will be <code>sendOutput()</code>, which will be used to serialize the various items into output. Serialization will primarily be done via <code>Renderers</code>.</li>
<li>Renderer object<br />
A <code>Renderer</code> will take response values and determine what to do with them. The default use case will be to use <code>Zend_View</code> + <code>Zend_Layout</code> to produce content.</li>
<li>Router object<br />
Pulls the response from the event, and then decomposes that into key/value pairs to inject in the request.</li>
<li>Dispatcher object<br />
Determines how to dispatch the request. The default use case will be to determine the module, controller, and action from the request object, and then simply instantiate and invoke the appropriate class; potentially, however, you could use any logic you like, including simply providing closures.</li>
<li>Action controllers<br />
These will work basically the same as they do currently, with a few exceptions.<br />
First, the only necessary method will be <code>__invoke()</code>, and it should accept an <code>Event</code> object. From there, how it matches the action to functionality is up to the developer.<br />
The base <code>ActionController</code> class will provide some standard functionality surrounding this, and will attempt to invoke <code>*Action()</code> methods, just as <code>Zend_Controller_Action</code> does currently. Additionally, it will add logic to <code>__call()</code> to automatically delegate to action delegates and action helpers.</li>
<li>ErrorHandler object<br />
Receives errors and updates the event state.</li>
</ul>


<h3>Ramifications</h3>
<ul>
<li>Front controller plugins would no longer need to rely on an abstract; you * would simply subscribe individual plugin classes/instances or closures with the appropriate topic in the pubsub provider,</li>
<li>The action helper broker can be injected into the <code>Event</code>, and the action controller can then pull it from there. This helps eliminate the singleton factory it currently implements.</li>
<li>The view object becomes subservient to the renderer, which is subservient to the response. This will require potentially more scaffolding during bootstrap and/or when determining what renderer to utilize. standard mvc.response.pre plugins could alleviate the situation.</li>
<li>Even though a default imlementation will be provided, the possibility exists for multiple implementations &ndash; and for community-led implementations for specific purposes.</li>
<li>Potentially huge performance gains, particularly if you attach pubsub callbacks on-demand based on the application flow.</li>
</ul>


<h3>Poka Yoke filtering</h3>

<p>In an action, <code>_getParam()</code> doesn't provide any filtering of superglobals, which can lead to potential security issues if you do not filter the data within your action controllers.</p>

<p>One suggestion is to allow attaching filters/validators to the request object, and only allowing direct access to superglobal values via <code>getRaw*()</code> methods &ndash; essentially a poka yoke.</p>

<h2>ActionController Changes</h2>

<p>Some proposed changes:</p>

<h3>Re-instate the fallback action (previously, noRouteAction())</h3>

<p>Reinstate the <code>noRouteAction()</code> (or something similar) as an empty method (throwing an exception) in the action controller. <code>__call()</code> would then detect method calls ending in 'Action' and call this method.</p>

<p>By itself, this does not represent a BC break; <code>__call()</code> would simply proxy to this method, which would throw the same exception as currently thrown in <code>__call()</code>. Overriding the method would be similar to overriding <code>__call()</code> in current versions.</p>

<h3>Use overloading to access action helpers</h3>

<p><code>__call()</code> would be modified to determine if a method call is a helper name, and, if so, invoke it. <code>__get()</code> would be added, and would do similarly, only it would return the helper directly. This would make calls to helpers much simpler, and make them mimic the behavior of view helpers more directly - leading to better consistency in the framework in general and MVC layer in particular.</p>

<p>As an example,</p>
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$this->_helper->redirector('index');
]]></ac:plain-text-body></ac:macro>
<p>would simply become:</p>
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$this->redirector('index');
]]></ac:plain-text-body></ac:macro>
<p>As an example showing property access:</p>
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$this->_helper->viewRenderer->setNoRender(true);
]]></ac:plain-text-body></ac:macro>
<p>could become:</p>
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$this->viewRenderer->setNoRender(true);
]]></ac:plain-text-body></ac:macro>
<p>This would be a slight BC break - but only for developers overriding <code>__call()</code>, and then the BC issues would be limited in scope. <code>$_helper</code> would continue to be registered, so all previous calls to helpers would work. When overriding <code>__call()</code>, you simply wouldn't be able to call helpers as controller methods, since the logic to proxy to the helper broker would not be present.</p></ac:rich-text-body></ac:macro>