Skip to end of metadata
Go to start of metadata

<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 "bootstrapping" 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 "exit" (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 – 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 "superglobal" 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 – 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 – 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>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Nov 12, 2009

    <p>The Phly_PubSub seems to be similar to the <a href="http://framework.zend.com/wiki/pages/viewpage.action?pageId=41398">Zend_Message</a> proposal (which holds the record for being under "Pending Recommendation" <ac:emoticon ac:name="wink" />). Perhaps you could combine your Phly_PubSub proposal with the Zend_Message proposal?</p>

    1. Nov 12, 2009

      <p>It is similar, though the approach differs in some significant ways (doesn't have the concept of a "message"; it simply passes objects on to the observer; has the ability to filter until a condition is met; etc.) Regardless, let's collaborate.</p>

  2. Nov 14, 2009

    <p>Three things I would like to see (all three borrowed from ASP.NET MVC:<br />
    1) Abstract away the construction of controllers to a controller factory interface. One very good use case for this would be the ability to construct controllers using a DI Container.<br />
    2) Abstract away the invocation of an ActionController action into an action invoker interface. In ASP.NET MVC the Action invoker interface does all the dirty work of calling the action. The default implementation calls the action method and puts the parameters (gathered from route data, form data and query data) into parameters on the action method. On each parameter a IModelBinder is called to convert it to a .NET type (also something I'd like).<br />
    3) Action methods should return an ActionResult, instead of just modifying the response. There are many salient features that could stem from that...not sure how it would work the "dispatch loop" idea though (ASP.NET MVC pipeline doesn't run in a loop)</p>

    <p>All three of these make testing controllers a cinch, as well as make cleaner code and thinner controllers.</p>

  3. Nov 20, 2009

    <p>Are there any goals to provide specification or implementation of application bootstrapping via CLI? Currently, everyone seems to have their own way of doing it. If there's a standard or documented way of bootstrapping the application from CLI, it opens gates for new component proposals. </p>

    1. Dec 19, 2009

      <p>I second this. Routing for CLI is hakish. It would be great to have something standard, utilizing getopt.</p>

      <p>Another thing I would like to propose is the abstraction of action controllers to a more generic execution container. This would allow the flexibility to use Command Objects, allowing more opportunity for reduced duplication. As purely a container it would not have methods such as dispatch(), run() and _forward(), which arguably should be in the dispatcher subpackage (although the latter method could stay and forward to to the dispatcher, to accommodate Tell, Don't Ask).</p>

      <p>I also second Avi's 3). Not only does it make testing of action controllers cleaner (the current test super type is, pardon me for saying, a monstrosity), it also opens up the possibility for extending the dispatcher and implementing a configured dispatch map. Basically I am just saying lets open it up as much as is sensible without losing the defensiveness which I hold so dear in ZF.</p>

  4. Jan 26, 2010

    <p>Hi,</p>

    <p>I was thinking would it make sense to be able to setup the FC to act as a super-lightweight endpoint for API services such as JSON, XML-RPC, Soap etc</p>

    <p>Currently I skip the MVC totally when serving API service, it would be cool if the FC had a "switch" that would put it into endpoint mode which does not register any unrequired objects?</p>

  5. Sep 28, 2010

    <p>Since Dependency Injection is a requirement for all components of ZF 2.0, I'd like to discuss here how we plan to inject collaborators (from a Service Layer for example) into controllers.</p>
    <ul class="alternate">
    <li>Constructor injection? Who is responsible for creating the controller objects?</li>
    <li>Setter injection?</li>
    </ul>

    1. Sep 28, 2010

      <p>This is still in the air. I'm working on a prototype currently that uses a strict registry container that is passed to collaborators (it follows an interface that defines methods for retrieving the router, dispatcher, request and response objects, and a few others). Collaborators then retrieve their dependencies from this container. However, this feels wierd, in the same way that the Symfony 2 DI container feels wierd – in the MVC of this latter, I see calls to "$this->container->get('somedependency')", not unlike the calls I'm making for "$this->getRegistry()->getSomeDependency()", and I'm left wondering if this is really a better situation or yet another solution that's going to be abused.</p>

      1. Sep 28, 2010

        <p>I looked at Symfony 2 code on Github and it is indeed weird. Actually, the container used this way is not an example of Dependency Injection but a Service Locator.</p>

        <p>There are two solutions to the controllers problems:<br />
        1. Leave them as they are, with empty constructor; preach the "fat model - thin controller" mantra, and use a Service Locator like Symfony Dependency Injection <strong>in</strong> them to get their dependencies. This is actually what we do in ZF 1, with controllers actions long ~10 lines at maximum.<br />
        2. Prescribe that controllers must ask for their dependencies, either with a custom constructor or via setters. The framework can then pass the necessary request-scoped collaborators instead of calling a raw new $controller() like Symfony and ZF do now (the means are various: configuration, inferring from setter names, inferring from setter signatures via reflection...).</p>

        <p>I discussed 2) some time ago: <a class="external-link" href="http://giorgiosironi.blogspot.com/2009/11/how-to-eliminate-singletons-part-2.html">http://giorgiosironi.blogspot.com/2009/11/how-to-eliminate-singletons-part-2.html</a><br />
        With this solution, we'll have real Dependency Injection and controller would only depend on their collaborators and not on Symfony DI or on any other part of the framework. Theoretically they would not even have to extend an abstract class, since an interface will suffice (maybe I'm going too far here).<br />
        However, this would result in <strong>real</strong> unit testing for controllers, since they would be instantiable in isolation without setting up a container. Moreover, there would be an Api for each controller that defines its dependencies - either a constructor or some setters - so that when one writes an additional unit test it is immediately clear what mocks are needed.</p>

        1. Sep 28, 2010

          <p>The place where DI is most prevalent nowadays is in the .NET world. So why don't we see what is doing in frameworks like ASP.NET MVC, or fubumvc (fubumvc might be to radical, but it was built with DI as a first class citizen)</p>

          1. Sep 28, 2010

            <p>Apart from breaking up actions into single classes, it seems that fubumvc does exactly this:
            <a class="external-link" href="http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/04/28/dependency-injection-in-asp-net-mvc-contextual-controller-injection.aspx">http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/04/28/dependency-injection-in-asp-net-mvc-contextual-controller-injection.aspx</a></p>

            <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
            public class HomeController : Controller

            {

                private readonly IFooService _fooService;

                public HomeController(IFooService fooService)

               

            Unknown macro: {         _fooService = fooService;     }

                ...

            }
            ]]></ac:plain-text-body></ac:macro>

      2. Dec 17, 2010

        <p>Any progress on this at this point? It's crucial for big modular architectures (CMS'es especially) that this gets done right.</p>

  6. Sep 28, 2010

    <p>How does the performance of the EventManager (PubSub) hold up as the number of subscribers increases? Can there be more than one PubSub instance (one FC Provider, one plugin Provider, one Form provider, etc) or is there just one global Provider? If just a single Provider, could that end up being an unexpected bottleneck since each component has (up to this point) been tested in isolation. </p>

    <p>What about event namespace collisions? Could it become a potential developer headache?</p>

    <p>This is by no means a criticism, but I'm seeing the PubSub component being proposed/used in several places with in ZF2 proposals. I definitely like the flexibility and potential it offers, but are we going out of our way to use it (like a new shiny toy)?</p>

    1. Sep 28, 2010

      <p>SignalSlot (the new name for PubSub) is very fast – but you shouldn't do endless chains, either. The more you add, the slower things will be, as there will be more and more calls in the graph.</p>

      <p>It's actually written primarily to be a per-instance mechanism – so, yes, a provider per component, or per instance of a component. Global should be used sparingly. As such:</p>

      <ul>
      <li>Namespace collisions will be rare, if they ever occur</li>
      <li>It offers a nice opt-in flex-point for components – i.e., if a flex-point is needed in one or more locations, drop in a signal slot, add accessors to set/get the signal slot, and document the signals available (as well as their arguments).</li>
      </ul>

      <p>I recommend you look at Zend\SignalSlot in the ZF2 repository for more details. Short answer, though, is that the concerns you raise have been addressed.</p>

  7. Sep 28, 2010

    <p>Two things on your MVC demo. One, it has local references (to your home directory). Which is fine since I'm not actually trying to run it but if others wanted to fork and offer suggestions, they would have to make modifications. I realize it is a prototype so not a major concern, just wanted to make sure you were aware.</p>

    <p>The real observation concerns the additional code in the Bootstrap. Could some of that get moved into a later cycle of the FrontController's events? For example, what if the view and/or layout isn't needed (ex. ajax request). If we could avoid initializing it until actually confirmed to be needed then we could further reduce some overhead.</p>

    <p>Come to think of it, isn't that is a somewhat similar concept to per-module bootstrapping and dependency injection/containers (only bootstrap what is needed for the request).</p>

  8. Dec 20, 2010

    <p>1st suggestion:<br />
    I think it would be great to enable the multiple "module" layout as default (not like yet).</p>

    <p>I hope that the most projects use more than the default module and split the code into logical modules.</p>

    <p>2nd suggestion:<br />
    The models and forms are defined as follow:<br />
    System_Model_Address<br />
    System_Form_Address<br />
    but the controller doesn't follow this:<br />
    System_AddressController becomes -> System_Controller_Adress</p>

  9. Jan 27, 2012

    <p>ActionController currently implements Dispatchable interface with:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[public function dispatch(Request $request, Response $response = null)]]></ac:plain-text-body></ac:macro>

    <p>the dispatch() method works with no MvcEvent provided (it creates new MvcEvent), but this is pointless. In execute() the newly created event will return empty value from $e->getRouteMatch(). You should either throw an exception when dispatch() is called with no event attached or allow to attach RouteMatch to the controller (for example setRouteMatch()), then try to getRouteMatch() if event does not provide one. This way the component will be easier to use even if one doesn't want to use Zend\Mvc\Application and doesn't setup MvcEvent prior to use of ActionController.</p>