View Source

<ac:macro ac:name="toc"><ac:parameter ac:name="maxLevel">2</ac:parameter></ac:macro>
<p>This document serves as several proposals combined, and consists of the<br />
following:</p>

<ul>
<li>An overview of various issues and requirements</li>
<li>Requirements (as bullet points)</li>
<li>Proposed Interfaces</li>
<li>Use Cases</li>
</ul>


<p>The focus of this proposal is on the following:</p>

<ul>
<li>Basic interfaces for the ZF2 MVC</li>
<li>How these interfaces may interact</li>
</ul>


<p>It is <strong>not</strong> intended to discuss the following:</p>

<ul>
<li><em>Where</em> code will exist within the filesystem in projects</li>
<li>Final MVC <em>implementations</em>. We will create separate proposals for the default implementations once this proposal is accepted.</li>
</ul>


<p>In this proposal, we are specifically keeping the following themes from the ZF2 requirements in mind:</p>

<ul>
<li>General goals:
<ul>
<li>Easing the learning curve</li>
<li>Improving baseline performance</li>
<li>Make extending the framework (trivially) simple</li>
</ul>
</li>
<li>Developer goals:
<ul>
<li>Programming by contract</li>
<li>Simplification</li>
<li>Favoring of the explicit</li>
</ul>
</li>
</ul>


<p>The primary takeaways from this proposal (or set of proposals) should be that the ZF2 MVC:</p>

<ul>
<li><strong>must</strong> be built with both simplicity and extensibility in mind.</li>
<li><strong>should</strong> provide the basic building blocks for creating custom MVC layers; other systems utilizing those building blocks <strong>should</strong> retain compatibility with these systems and shipped implementations.</li>
<li><strong>will</strong> provide a simple, standard MVC solution (though details on that will come in later proposals).</li>
</ul>


<h2>Overview</h2>

<p>ZF's MVC has grown organically. While the basic, shipped MVC works for many, there are number of limitations that cause issues for those wanting to deviate from the standard workflow.</p>

<h3>Page Controllers</h3>

<p>In some cases, page controllers (vs. a front controller with many action controllers) are desired. In this paradigm, a separate end-point would be created for a particular controller (or multiple endpoints for multiple controllers), and it would then handle all requests to that end-point. (Page controllers were especially common pre-frameworks.)</p>

<p>Currently, ZF1 is not terribly friendly in this regard. While action controllers <em>can</em> be instantiated on their own, there are a number of soft dependencies on the front controller &ndash; e.g. redirects and URL generation typically require pulling the router from the front controller, layout rendering assumes a front controller plugin, etc. While many of these obstacles may be overcome, page controllers should be much easier to create than they currently are.</p>

<h3>No Action Controller Interface</h3>

<p>If you wish to attach a controller to the MVC, it <strong>must</strong> extend <code>Zend_Controller_Action</code>, as that's the type tested against, and provides the only blueprint or abstract implementation of a controller in the framework. This is problematic on many levels:</p>

<ul>
<li>If you do not want the various features of <code>Zend_Controller_Action</code> &ndash; action helper integration, <code>_forward()</code>, <code>_redirect()</code>, front controller parameter injection, etc. &ndash; there's no way to disable that functionality, as it's baked in. You cannot simply substitute an alternate implementation.</li>
<li>If you want to alter how action dispatching works, you must fight both the dispatcher and the action controller.
<ul>
<li>The dispatcher sends an &quot;action&quot; argument to the action controller's <code>dispatch()</code> method &ndash; but it's actually a method name; you have to pull the actual action name from the request object. This means, again, fighting the framework. This smells of poor interface definition.</li>
<li>A number of developers and organizations have requested the ability to pass arguments to action controllers. This is actually possible &ndash; but, again, not easily achievable due to the structure of the framework. In this case, the abstract controller is too limiting for those extending the framework, and smells of restrictive interface definition.</li>
<li>If you want to use a different convention than <code>&lt;action&gt;Action()</code> for dispatchable methods, again, you must pull the action name from the request object. This makes it difficult to mix-in existing service objects and/or controllers from other MVC frameworks.</li>
</ul>
</li>
</ul>


<p>Basically, we need some sort of action controller interface that allows for both the default, defined use case, as well as alternate implementations to exist side-by-side.</p>

<h3>Poorly Composed Responsibilities</h3>

<p>Continuing on the subject of <code>Zend_Controller_Action</code>, the class currently has too many responsibilities: </p>

<ul>
<li>View composition, initialization, and rendering</li>
<li>Redirection</li>
<li>Forwarding additional controllers</li>
<li>Helper broker (this is actually not terrible, as it composes the helper broker; however, access to the broker is not ideal)</li>
<li>Composition of application parameters</li>
<li>Hook management and lifecycle</li>
<li>Action dispatch</li>
</ul>


<p>The base action controller interface should only define the minimum requirements for executing an action controller, and everything else should be either:</p>

<ul>
<li>Composed in (either via controller or setter injection)</li>
<li>Part of application logic</li>
</ul>


<p>At its basic level, an action controller is a Strategy or Command pattern &ndash; the front controller executes the action controller and returns a response. As such, anything outside that scope is likely suspect in terms of design.</p>

<h3>Services</h3>

<p>ZF1 created a number of server classes: AMF, JSON, XML-RPC, SOAP (we won't talk about REST). These all follow the PHP SoapServer API:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$server = new Server;
$server->setClass($classOrObject, $optionalNamespace); // optionally multiple times
$server->addFunction($functionName, $optionalNamespace); // optionally multiple times
$response = $server->handle();
echo $response;
]]></ac:plain-text-body></ac:macro>

<p>This API is simple and effective. We've discovered one or two limitations &ndash; it would be better to always require a request object to `handle()`, for instance, but overall, it works.</p>

<p>There are some cases, however, where the approach is limited:</p>

<ul>
<li>Request/Response objects in the services are limited in functionality; if you need to set or get additional headers, you're on your own.</li>
<li>Authentication and Authorization are items often automated in the MVC &ndash; which leads to writing extra code for service endpoints in order to make use of these.</li>
<li>No hooks. Having the ability to tie into things like pre/post-handle would be useful for automating things such as authentication/authorization. Currently, while you <em>can</em> program around these, there's no easily re-usable, pluggable way of doing so.</li>
<li>Inability to configure; setting classes/objects/functions, request and response classes, etc. all must be done manually, instead of via options and/or configuration classes.</li>
</ul>


<p>These often lead to developers wrapping services within the ZF MVC layer. This, however, leads to additional issues:</p>

<ul>
<li>Each server is basically an MVC in its own right. This leads to duplication of objects (request, response), duplication of logic (dispatching controller + dispatching service), and degradation of performance (Often 5-20x better performance is had by using a standalone service).</li>
<li>No ties/hooks into MVC; you end up needing to disable things like layouts, views, etc. manually.</li>
</ul>


<p>Making services capable of being configurable, first-class citizens of the MVC would simplify service creation and exposure within applications.</p>

<h3>Dispatching</h3>

<p><code>Zend_Controller_Dispatcher_Standard</code> is, frankly, an atrocity. While a dispatcher interface is defined, the front controller, the dispatcher itself, and several other classes all utilize methods that are not part of the interface. This makes it incredibly difficult to drop in alternate dispatch mechanisms.</p>

<p>Why would you want to do that?</p>

<ul>
<li>Micro-MVCs, or performance-optimized MVCs. Often, a business need may require performance optimizations that the default ZF MVC is simply incapable of providing.</li>
<li>Alternate controller strategies. If you don't want to follow the convention of <code>&lt;name&gt;Controller::&lt;name&gt;Action()</code>, you have to replace both the dispatcher and action controller strategies &ndash; and modifying the dispatcher is difficult.
<ul>
<li>One use case for changing the strategy is RESTful controllers. It doesn't make much sense to have separate actions for each request method &ndash; a switch statement and an interface for resources could accomplish this much more quickly.</li>
<li>If you replace the dispatcher mechanism, this means you cannot re-use action controllers between projects, as one may assume the standard dispatcher, the other a custom dispatcher.</li>
</ul>
</li>
</ul>


<p>In order to be re-usable, the dispatcher interface <strong>must</strong> contain only the methods necessary for dispatching, and a way to configure itself.</p>

<h3>Too Many Hook Points</h3>

<p>Right now, we have three layers of hooks within the ZF MVC (from outside -&gt; in): </p>

<ul>
<li>Front Controller plugins</li>
<li>Action Helper hooks</li>
<li>Action Controller hooks</li>
</ul>


<p>One common issue on the ZF mailing lists is when and where to register automated processes. There are four answers:</p>

<ul>
<li>Front Controller plugins, if they don't <em>need</em> to be aware of the action controller, and need to be run on every request.</li>
<li>Action Helper hooks, if they <em>may</em> need to be aware of the action controller, and likely need to be run on every request.</li>
<li>A base action controller extension class, in the init() or pre/postDispatch() hook methods, if action controller awareness <em>must</em> be present, and you want to selectively apply hooks to all or some controllers.</li>
<li>A specific action controller's hook methods, for ultimate specificity.</li>
</ul>


<p>This presents a number of problems:</p>

<ul>
<li>Education. Developers must learn the entire life cycle of the standard MVC in order to make good use of it.</li>
<li>Different mechanisms have different specificity. This can be a good thing, but it can also lead to edge cases quite quickly.</li>
<li>Each registers with the system in a different way.</li>
</ul>


<p>Hooks:</p>

<ul>
<li><strong>must</strong> be standardized:</li>
<li><strong>must</strong> allow opt-in behavior. One controller may require hooks, while another might not. An example: service end-points likely may not need view and layout automation, while normal controllers might.</li>
<li><strong>must</strong> be implemented in a way that they may be composed in. In other words, neither front nor action controllers should require methods for registering hooks, but rather utilize a collaborator to do so. This allows easy mocking, the ability to setup hooks via extension classes, or runtime configurability.</li>
</ul>


<h3>Usage outside ZF applications</h3>

<p>Related to page controllers, it's often useful to integrate Zend Framework within other systems, such as WordPress, Drupal, Joomla, TikiWiki, etc. These systems often provide hook systems that allow you to execute code on-demand. As many ZF applications may utilize a full variety of MVC features, including views, request and response headers, etc., embedding and wrapping the ZF MVC within hooks can be an expedient way to accomplish this task. However, this tends to:</p>

<ul>
<li>introduce negative performance impact</li>
<li>introduce complicated configuration and setup</li>
</ul>


<p>Wrapping ZF MVC functionality <strong>should</strong> be easy to accomplish.</p>

<h3>Alternate View Strategies</h3>

<p>Currently, ZF only offers a single view strategy, based on using PHP as a template language. Alternate view strategies may be utilized by implementing <code>Zend_View_Interface</code>, but, as with the dispatcher, many methods assume and call more functionality than the view interface defines.</p>

<p>Additionally, some strategies are not easily supported:</p>

<ul>
<li>&quot;Code-Behind&quot;. In this paradigm, individual template pages are called by the front controller, and the templates &quot;wire in&quot; code that will handle specific events, usually through tags:
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<% Page CodeFile="Application/Controller/Service.php"
inherits="Application\Controller\Service" %>
]]></ac:plain-text-body></ac:macro>
This would load the appropriate file, inherit code, and execute it. Currently, this sort of strategy is impossible without introducing an entirely new MVC layer.</li>
<li>Hierarchical MVC. HMVC's primary strength is that it allows for creation of complex content by executing many different MVC triads. As an example, a call to one controller may initiate calls to two additional controllers, each of which may also execute additional controllers; the results will then be aggregated and passed to a view. This is different than &quot;forwarding&quot; in the current system, as the entire request/response cycle of a given controller happens <em>within</em> the body of a single action. This allows manipulating the response to determine what is relevant to the current request.</li>
<li>Strict segregation of templates from development. The default MVC keeps all view scripts under the same tree as the given module. This means that it's more difficult to segregate access easily for designers. Often, it would be easier to put all view scripts for the entire application under a single directory, and give designers access only to that directory. While possible with the view renderer, it's non-trivial.
<ul>
<li>Additionally, using PHP as the templating language is not optimal for many designers; the ability to utilize a standard template engine would often be easier for purposes of training.</li>
</ul>
</li>
<li>Transform views are not supported currently. Transform views are an alternative to Two Step Views, and often simpler for designers to utilize.</li>
</ul>


<h3>Configuration and Injection of Resources</h3>

<p>How do resources make their way into controllers, views, etc.? This question was largely answered in ZF1 with the introduction of <code>Zend_Application</code>. However, this integration is incomplete.</p>

<ul>
<li>The bootstrap is injected into the front controller singleton</li>
<li>The front controller injects the bootstrap into controllers</li>
<li>All other classes retrieve the bootstrap from the front controller singleton</li>
</ul>


<p>Two solutions present themselves:</p>

<ul>
<li>A dependency injection container</li>
<li>A service locator</li>
</ul>


<p>Either solution is possible with the current implementation of <code>Zend_Application_Bootstrap</code>; it does not specify what type of &quot;container&quot; is used internally.</p>

<p>The question is fundamentally this, however: how should controllers, models, and views get their dependencies/resources? Should we expect the front controller/dispatcher to inject them, or to pass a service locator to them so that they may pull them themselves?</p>

<p>Alternately, should the ZF MVC interfaces care?</p>

<h3>Performance</h3>

<p>ZF's MVC performance has (mostly) steadily declined since the 1.0 release (with the exception of the 1.7 release, when a performance audit was performed). In large part, this is due to additional functionality:</p>

<ul>
<li>Adding layouts added another view rendering phase to the typical request</li>
<li>Adding Zend_Application and the bootstrap lifecycle</li>
</ul>


<p>In performance profiling, the ZF team has discovered that the primary source of the bottlenecks is in the !PluginLoader implementation, which (a) has many stat calls, (b) doesn't cache between instances, and (c) differs between components. We have done a large amount of work during the Autoloading &amp; Plugin Loading milestone to correct this situation, but are looking for additional areas we can improve performance in the MVC layer.</p>

<h3>Modules</h3>

<p>The current approach to modules in ZF is very much &quot;tacked on&quot;. Due to how they work, it's quite difficult to simply &quot;drop in&quot; third-party code and expect it to work. While this is somewhat better since the introduction of Zend_Application, the requirement to execute a bootstrap for each module that requires autoloading leads to additional performance degradation.</p>

<p>Modules <strong>should</strong> be considered &quot;first-class citizens&quot; of Zend Framework.</p>

<h2>Requirements</h2>


<ul>
<li>Basics
<ul>
<li>The basic building blocks of the MVC <strong>must</strong> include standard Request and Response interfaces.
<ul>
<li>The default implementation <strong>must</strong> provide HTTP-aware implementations of the Request and Response interfaces.</li>
</ul>
</li>
<li>The basic responsibility of the MVC and Server classes <strong>must</strong> be to convert a Request object into a Response object.</li>
<li>The primary goals are to:
<ul>
<li>Create interfaces and loosely coupled objects for creating MVC implementations; and</li>
<li>Create one or more default implementations.</li>
</ul>
</li>
</ul>
</li>
<li>Request object
<ul>
<li><strong>Must</strong> handle both request <em>metadata</em> and optionally <em>content</em>.</li>
<li><strong>Must</strong> provide an HTTP-aware Request implementation:
<ul>
<li>Aware of Request-Method</li>
<li>Aware of Superglobals ($_GET, $_POST, $_SERVER, $_ENV, $_FILES, cookies, sessions, etc.)</li>
<li>Aware of HTTP request headers</li>
<li><strong>Should</strong> be aware enough of Content-Type and Request-Method to be able to auto-populate $_PUT and $_POST from the raw request body for any format supported by Zend\Serializer.</li>
</ul>
</li>
</ul>
</li>
<li>Response object
<ul>
<li><strong>Must</strong> handle both response content an optionally <em>metadata</em>.</li>
<li><strong>Must</strong> provide an HTTP-aware Response implementation:
<ul>
<li><strong>Must</strong> be capable of aggregating and emitting HTTP response headers and status codes.</li>
<li><strong>Should</strong> be capable of manipulating Cache-Control headers</li>
<li><strong>Should</strong> be capable of aggregating and emitting cookies</li>
<li><strong>Might</strong> be &quot;View-aware&quot;, such that getting a View Model or View Renderer as the content would auto-render the view when the response is emitted.</li>
</ul>
</li>
</ul>
</li>
<li>Headers
<ul>
<li><strong>Must</strong> provide interfaces and implementations for representing invidivual HTTP headers as well as collections of headers</li>
</ul>
</li>
<li>Cookies
<ul>
<li><strong>Should</strong> provide interfaces and implementations for representing cookies and collections of cookies</li>
</ul>
</li>
<li>Parameters
<ul>
<li><strong>Must</strong> provide interfaces and implementations for representing metadata objects (including the superglobals).</li>
<li>The Parameters interface <strong>must</strong> allow for array access of metadata.</li>
</ul>
</li>
<li>Page Controllers:
<ul>
<li><strong>Must</strong> be able to create page controllers with ZF.</li>
<li><strong>Must</strong> be able to utilize page controllers outside the front controller paradigm.</li>
<li>URL generation <strong>should</strong> be de-coupled from the front controller</li>
<li>Controller &quot;redirection&quot; and/or &quot;forwarding&quot; <strong>must</strong> be de-coupled from the front controller</li>
<li><strong>Must</strong> be able to configure services/resources and inject easily, preferably via the constructor.</li>
<li>Ideally, Action Controllers <strong>should</strong> be re-usable as standalone Page controllers.</li>
</ul>
</li>
<li>Action Controllers
<ul>
<li>ZF2 <strong>must</strong> offer a simple, strong interface for action controllers.
<ul>
<li>The action controller interface <strong>must not</strong> require front controller interaction.</li>
<li>Action controller dependencies <strong>must</strong> be injectable, either through explicit setters or the constructor; the interface should not dictate anything that would prevent this.</li>
<li>The action controller interface <strong>must</strong> specify a single method for dispatching itself.</li>
</ul>
</li>
<li>Action controllers <strong>must</strong> allow the flexibility to re-define the conventions for resolving actions.
<ul>
<li>ZF2 <strong>must</strong> offer a default implementation with the recommended convention</li>
</ul>
</li>
<li>Action controllers <strong>must</strong> allow the flexibility to determine whether or not action methods will expect arguments.</li>
<li>Action controllers <strong>should</strong> <strong>optionally</strong> allow lifecycle management, preferably via signal slots and/or action helpers.
<ul>
<li>The default implementation <strong>should</strong> allow configuring lifecycle management both statically and via constructor injection.</li>
</ul>
</li>
</ul>
</li>
<li>Server classes
<ul>
<li><strong>Must</strong> implement the same basic interface as action controllers, in order to allow them to be composed as part of an MVC application.</li>
<li><strong>Must</strong> utilize the same base Request and Response interfaces, and <strong>should</strong> decorate these in order to allow de/serialization of the protocol format.</li>
<li><strong>Should</strong> provide lifecycle hooks, likely in the form of !SignalSlots.
<ul>
<li>Hooks <strong>should</strong> be injectable/configurable</li>
<li>Hooks <strong>should</strong> allow static configuration as well as per-instance.</li>
<li>Hooks for servers <strong>should not</strong> utilize the same signals as action controllers, to help prevent things such as View/Layout injection.</li>
</ul>
</li>
</ul>
</li>
<li>Router
<ul>
<li>The basic &quot;Route&quot; interface <strong>must</strong>:
<ul>
<li>Provide the ability to match a Request object to metadata tokens.</li>
<li>Provide the ability to assemble a URL/Request based on metadata.</li>
</ul>
</li>
<li>A new &quot;RouteStack&quot; interface <strong>should</strong> be developed, allowing the ability to provide a stack of routes to test for matches.</li>
<li>Routing <strong>could</strong> write matched metadata to the Request object metadata.
<ul>
<li>This is under debate. However, doing so would simplify most workflows for the MVC. The main question is whether this should overwrite such metadata, or whether the RouteStack implementation would return a &quot;RouteResult&quot; object that it would then register as a single metadatum of the result.</li>
</ul>
</li>
</ul>
</li>
<li>Dispatcher
<ul>
<li><strong>Must</strong> follow the same basic interface as action controllers and server implementations.</li>
<li>The dispatcher <strong>must</strong> be responsible for mapping a controller name to a class that will handle it, and <strong>optionally</strong> injecting configuration.</li>
<li>The dispatcher <strong>may</strong> be omitted, depending on the MVC created.</li>
<li><strong>Should</strong> return a response as soon as it has one.</li>
</ul>
</li>
<li>Front Controller
<ul>
<li><strong>Must not</strong> implement a singleton pattern.</li>
<li><strong>Should</strong> be responsible simply for:
<ul>
<li>Router management</li>
<li>Dispatcher management</li>
<li>Dispatching the request</li>
</ul>
</li>
<li>Application Configuration <strong>should</strong> be injected into the child objects.</li>
<li><strong>Should</strong> provide lifecycle hooks around the router and dispatcher.</li>
<li><strong>Should</strong> return a response as soon as it has one.</li>
</ul>
</li>
<li>Lifecycle hooks
<ul>
<li>All areas where hooks are used <strong>must</strong> use the same mechanism</li>
<li>In most cases, static configuration as well as per-instance configuration <strong>should</strong> be supported.</li>
</ul>
</li>
<li>Application configuration
<ul>
<li>Application-wide (or commonly used) resources <strong>must</strong> be configurable</li>
<li>Dispatchable objects (controllers, dispatchers, servers, etc.) <strong>should</strong> accept application configuration, and &quot;wire themselves&quot; based on that configuration.</li>
<li>Configuration management <strong>must</strong> handle dependencies.</li>
<li>Configuration management <strong>must</strong> lazy load when requested (i.e., it should not preload all resources)</li>
</ul>
</li>
<li>Performance
<ul>
<li>Performance of the MVC layer must be as fast as possible, while still supporting required features such as layouts, lifecycle management, and application configuration.</li>
</ul>
</li>
<li>Modules
<ul>
<li>Design of any shipped MVC implementations <strong>must</strong> encourage module re-use, and <strong>must</strong> support modules as basic MVC infrastructure.</li>
</ul>
</li>
</ul>


<h2>Proposed Interfaces</h2>

<p>In examining the requirements, a standard interface for page/action controllers, dispatchers, service providers, and front controllers emerged, essentially a Command pattern:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
interface Dispatchable
{
/**
* Dispatch a request
*
* Typically, if something dispatchable returns a Response object, that
* Response will be used. Systems could be built, however, that introspect
* the return value in order to determine what type of Response to build.
* On the other end of the spectrum, we could pass a Response object, and
* expect the code to interact with and populate it.
*
* @return mixed
*/
public function dispatch(Request $request, Response $response = null);
}
]]></ac:plain-text-body></ac:macro>

<p>Basically, by making each of these components &quot;dispatchable&quot;, we get a number of advantages:</p>

<ul>
<li>Controllers may be used either standalone, or within a larger application context (cronjobs, message handlers that consume controllers, etc.). This provides the ability to create standalone page controllers,</li>
<li>Server classes may be configured and attached to a front controller. In this case, they can become first-class citizens of the MVC, instead of something tacked on or wrapped.</li>
<li>A front controller implementation need only offer the ability to broker controllers to dispatch. This leads to the ability to slipstream custom implementations that are tuned for specific needs.</li>
</ul>


<p>To this end, we propose the following interfaces:</p>

<ul>
<li>Message interface (metadata and body content)</li>
<li>Request interface (extends Message)</li>
<li>Response interface (extends Message, provides a way to emit itself)</li>
<li>Dispatchable interface (takes a Request object, and optimally returns a Response object)</li>
<li>Route interface (takes a request, and decomposes it into a Result)</li>
</ul>


<p>The basic use case then becomes:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$dispatchable = new DispatchableObject();
$response = $dispatchable->dispatch(new Request, Response $response = null);
$response->emit();
]]></ac:plain-text-body></ac:macro>

<h3>Interfaces</h3>

<p>Interface definitions have been written and committed to the following repository:</p>

<ul>
<li><a class="external-link" href="https://github.com/weierophinney/fig-library">https://github.com/weierophinney/fig-library</a></li>
</ul>


<p>with concrete implementations in the &quot;Zend&quot; tree of the &quot;feature/zf-implementation&quot; branch. Below are the basic definitions.</p>

<ul>
<li>Message
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
interface Message
{
public function setMetadata($spec, $value = null);
public function getMetadata($key = null);
public function setContent($content);
public function getContent();
}
]]></ac:plain-text-body></ac:macro></li>
<li>Request
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
interface Request extends Message { }
]]></ac:plain-text-body></ac:macro></li>
<li>Response
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
interface Response extends Message
{
public function emit();
}
]]></ac:plain-text-body></ac:macro></li>
<li>Dispatchable
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
interface Dispatchable
{
/**
* Ideally, returns a Response object.
*/
public function dispatch(Request $request);
}
]]></ac:plain-text-body></ac:macro></li>
</ul>


<p>Additionally, HTTP-aware versions of the Request and Response interfaces are proposed:</p>

<ul>
<li>HttpRequest
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

use Fig\Request;

interface HttpRequest extends Request
{
/* accessors for various superglobals */
public function query($name = null, $default = null);
public function post($name = null, $default = null);
public function cookie($name = null, $default = null);
public function file($name = null);
public function server($name = null, $default = null);
public function env($name = null, $default = null);
public function headers($name = null);

/* URI decomposition */
public function getRequestUri(); // Return a URI instance

/* script name */
public function getScriptName();

/* Capabilities
*
* These may be pushed into an additional interface.
*/
public function getMethod();
public function getETags();
public function getPreferredLanguage(array $locales = null);
public function getLanguages();
public function getCharsets();
public function getAcceptableContentTypes();
public function isNoCache();
public function isFlashRequest();
public function isSecure();
public function isXmlHttpRequest();

/* potential method tests */
public function isDelete();
public function isGet();
public function isHead();
public function isOptions();
public function isPost();
public function isPut();
}
]]></ac:plain-text-body></ac:macro></li>
<li>ModifiableHttpRequest
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

use Fig\Parameters;

interface ModifiableHttpRequest extends HttpRequest
{
/* mutators for various superglobals */
public function setQuery(Parameters $query);
public function setPost(Parameters $post);
public function setCookies(Parameters $cookies);
public function setFiles(Parameters $files); // Maybe separate component for Files?
public function setServer(Parameters $server);
public function setEnv(Parameters $env);
public function setHeaders(HttpRequestHeaders $headers);
public function setRawBody($string);

/* Capabilities
*
* These may be pushed into an additional interface.
*/
public function setMethod($method);

/* creational capabilities */
public static function create(Uri $uri, $method = 'get' /** .. more args */);
}
]]></ac:plain-text-body></ac:macro></li>
<li>Uri
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Zend\Uri;

class Uri
{
public function getScheme();
public function getHost();
public function getPort();
public function getPath();
public function getQueryString();
public function getBaseUrl();

public function setScheme($scheme);
public function setHost($host);
public function setPort($port);
public function setPath($path);
public function setQueryString($stringOrArray);
public function setBaseUrl(Uri $uri);

public function isValid();
public function __toString();
}
]]></ac:plain-text-body></ac:macro></li>
<li>HttpResponse
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

use Fig\Response;

interface HttpResponse extends Response
{
public function __construct($content = '', $status = 200, $headers = null);

public function sendHeaders();
public function sendContent();
// public function emit(); // send both headers and content; defined in Fig\Response

/* mutators and accessors */
public function getHeaders();
public function setHeaders(HttpResponseHeaders $headers);
}
]]></ac:plain-text-body></ac:macro></li>
<li>HttpHeaders
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

use Iterator,
ArrayAccess,
Countable;

/*
* In most cases, extend SplQueue, and then override where necessary
*/
interface HttpHeaders extends Iterator, ArrayAccess, Countable
{
/*
* General mutators and accessors
*
* These are items that are technically part of the response headers, but
* not individual headers themselves.
*/
public function getProtocolVersion(); // HTTP 1.0, 1.1
public function setProtocolVersion($version);

/**
* Adding headers
*
* Also: requires overriding push, unshift to ensure values are of correct
* type.
*
* Typically, $header will be of type HttpHeader, but this allows addHeader()
* to operate as a factory. Suggestion is to allow HttpHeader objects, arrays,
* or all 3 arguments.
*
* @param string|array|HttpHeader $header
* @param null|string $content
* @param bool $replace
* @return HttpHeaders
*/
public function addHeader($header, $content = null, $replace = false);

/**
* Allow adding multiple headers at once
*
* Implementation can vary -- could be key/value pairs, array of HttpHeader
* objects, array of arrays, etc -- or combination thereof.
*
* @param mixed $headers
* @return HttpHeaders
*/
public function addHeaders($headers);

/*
* Retrieve named header; returns either false or a queue of headers.
* has() tests for headers
*/
public function get($type);
public function has($type);

/**
* Representation of headers as string
*/
public function __toString();

/**
* Populate object and headers from string
*
* Accepts text representing all headers, and splits it into either a
* status or request line and all provided headers.
*
* @param string $string
* @return HttpHeaders
*/
public function fromString($string);
}
]]></ac:plain-text-body></ac:macro></li>
<li>HttpHeader
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

interface HttpHeader
{
public function __construct($header, $value = null, $replace = false);

/* mutators */
public function setType($type);
public function setValue($value);
public function replace($flag = null); // also acts as mutator

/* accessors */
public function getType();
public function getValue();

/* behaviors */
public function send();
public function __toString();
}
]]></ac:plain-text-body></ac:macro></li>
<li>HttpRequestHeaders
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

interface HttpRequestHeaders extends HttpHeaders
{
/* Request-Line-related accessors/mutators */
public function getMethod(); // GET, POST, PUT, etc.
public function getUri();
public function setMethod($method); // GET, POST, PUT, etc.
public function setUri($uri);
public function renderRequestLine(); // render the "request line" portion of the header
}
]]></ac:plain-text-body></ac:macro></li>
<li>HttpResponseHeaders
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

/**
* Represents the HTTP Response headers
*
* Describes a collection of response headers. As such, it also ensures a status
* code and optional status message.
*/
interface HttpResponseHeaders extends HttpHeaders
{
public function getStatus(); // Returns HttpStatus instance

/* Sending headers, and testing sent status */
public function send(); // actually send headers
public function sent(); // return boolean headers sent status

/* Specialized response header(s) */
public function setRedirect($url, $code = 302);

/* Methods occurring below here need to be discussed */

/* Potential specialized mutators * /
public function expire();
public function setClientTtl($seconds);
public function setEtag($etag = null, $weak = false);
public function setExpires($date = null);
public function setLastModified($date = null);
public function setMaxAge($value);
public function setNotModified();
public function setPrivate($value);
public function setSharedMaxAge($value);
public function setTtl($seconds);
public function setVary($headers, $replace = true);

/* Potential specialized conditionals * /
public function hasVary(); // Vary header present? (should )
public function isCacheable();
public function isFresh();
public function isNotModified(HttpRequest $request);
public function isValidateable();
public function mustRevalidate();

/* Potential specialized accessors * /
public function getAge() ;
public function getEtag();
public function getExpires();
public function getLastModified();
public function getMaxAge();
public function getTtl();
public function getVary();
*/
}
]]></ac:plain-text-body></ac:macro></li>
<li>HttpStatus
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig\Http;

interface HttpStatus
{
public function getStatusCode(); // 200, 301, etc.
public function getStatusMessage();
public function setStatusCode($code, $text = null);

/* Testing header status */
public function isRedirect(); // 3XX status and/or Location header?
public function isClientError(); // 4XX status?
public function isEmpty(); // 201, 204, or 304 status?
public function isForbidden(); // 403 status?
public function isInformational(); // 1XX status?
public function isInvalid(); // <100 or >= 600 status?
public function isNotFound(); // 404 status?
public function isOk(); // 200 status?
public function isServerError(); // 5XX status?
public function isSuccessful(); // 2XX status?
}
]]></ac:plain-text-body></ac:macro></li>
<li>Parameters
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
namespace Fig;

use ArrayAccess,
Countable,
Serializable,
Traversable;

/*
* Basically, an ArrayObject. You could simply define something like:
* class QueryParams extends ArrayObject implements Parameters {}
* and have 90% of the functionality
*/
interface Parameters extends ArrayAccess, Countable, Serializable, Traversable
{
public function __construct(array $values = null);

/* Allow deserialization from standard array */
public function fromArray(array $values);

/* Allow deserialization from raw body; e.g., for PUT requests */
public function fromString($string);

/* Allow serialization back to standard array */
public function toArray();

/* Allow serialization to query format; e.g., for PUT or POST requests */
public function toString();
}
]]></ac:plain-text-body></ac:macro></li>
</ul>


<p>Routing is an aspect of the MVC that would be affected by these interfaces. The following interfaces would be used within the routing subcomponent:</p>

<ul>
<li>Route
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
interface Route
{
public function setRequest(Request $request);
public function match(Request $request);
public function assemble($name, array $params = array(), array $options = array());
}
]]></ac:plain-text-body></ac:macro></li>
<li>RouteStack
<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
/**
* An implementation would likely extend SplStack and/or Iterator + ArrayAccess
*/
interface RouteStack extends Route
{
public function setRequest(Request $request);
public function addRoute(Route $route);
public function addRoutes($routes);
public function getCurrentRoute();
}
]]></ac:plain-text-body></ac:macro></li>
</ul>


<h2>Use Cases</h2>

<p>The following are examples covering a variety of use cases. </p>

<p>We are discussing currently what exact form <code>$options</code> will take. It <em>will</em> be a service locator of some sort; we are split on whether it will also be a dependency injection container (even if we don't, we will likely make the definition flexible enough to allow using one). For now, focus on the use cases surrounding the interfaces.</p>

<h3>Page Controller</h3>

<p>One often requested feature is the ability to use action controllers as standalone page controllers. As the <code>Dispatchable</code> interface simply requires that a <code>Request</code> object be passed, this becomes a trivial use case.</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$request = new Request();
$request->setMetadata('action', 'foo');

$controller = new SomePageController($options);
$response = $controller->dispatch($request);
$response->emit();
]]></ac:plain-text-body></ac:macro>

<h3>Front Controller</h3>

<p>The Zend Framework 1.0 front controller is actually relatively simple. It's <code>dispatch()</code> method currently simply routes and then dispatches the request. That paradigm could be used again to provide a simple ZF1 emulation layer:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$controller = new FrontController($options);
$response = $controller->dispatch(new Request);
$reponse->emit();
]]></ac:plain-text-body></ac:macro>

<p>In the above, the assumption is that <code>$options</code> would contain dependencies &ndash; such as a dispatcher, router, etc &ndash; and that these would be configured as well. The dispatcher would look like this:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$dispatcher = new Dispatcher($options);
return $dispatcher->dispatch($request);
]]></ac:plain-text-body></ac:macro>

<p>In other words, it, too, would implement the <code>Dispatchable</code> interface, and be responsible for determining what <code>Dispatchable</code> objects attached to it should be invoked.</p>

<p>This leaves great flexibility of implementation &ndash; anything <code>Dispatchable</code> can be used as an action controller.</p>

<p>Internally, plugins would be swapped out in favor of <code>SignalSlots</code>, giving some additional flexibility of implementation, as well as simplifying the front controller implementation.</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$controller->signals()->connect('router.start', new RouteInitializer())
->connect('router.stop', new ModuleInitializer())
->connect('dispatch.start', new LayoutSwitcher());
]]></ac:plain-text-body></ac:macro>

<p>Basically, the front controller would simply become a Facade for managing the workflow of other objects.</p>

<h3>Server Classes</h3>

<p>ZF currently provides a number of Server classes: <code>Zend_Json_Server</code>, <code>Zend_XmlRpc_Server</code>, <code>Zend_Amf_Server</code>, etc. These would all follow the same interface:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$server = new Server($options);
$response = $server->dispatch($request);
$response->emit();
]]></ac:plain-text-body></ac:macro>

<p>These would likely use either extensions to the base <code>Request</code>/<code>Response</code> classes provided, or <em>decorate</em> them in order to provide de/serialization of the protocol for which they are responsible. (Decoration offers the most flexibility for differing use cases.)</p>

<p>Because they implement <code>Dispatchable</code>, they could be mixed in as just another dispatchable within a Front Controller implementation, or executed in standalone endpoints:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
$server = new SoapServer($options);
$server->setClass($someClass, $namespace);

// Tell the dispatcher that the "soap" controller refers to the $server instance
$mvc->getDispatcher()->register('soap', $server);
]]></ac:plain-text-body></ac:macro>

<h3>Micro or Custom MVC stacks</h3>

<p>When dealing with either small sites, or large sites with specific performance requirements, rolling a custom MVC stack is often useful. The current ZF1 implementation does not make this easy. The proposed interfaces <em>do</em>, however. Consider the following, callback-oriented dispatcher implementation:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
use Zend\Stdlib\Dispatchable,
Zend\Http\Response as HttpResponse,
Fig\Request,
Fig\Response;

class Dispatcher extends Dispatchable
{
protected $controllers;

public function attach($spec, $callback = null)
{
if (is_array($spec) || $spec instanceof \Traversable) {
foreach ($spec as $controller => $callback) {
$this->attach($controller, $callback);
}
return $this;
}

if (!is_scalar($spec)) {
throw new \InvalidArgumentException('Spec must be scalar or traversable');
}

if (!is_callable($callback)) {
throw new \InvalidArgumentException('Callback must be callable');
}

$this->controllers[$spec] = $callback;
return $this;
}

/**
* Dispatch a request
*
* @param Request $request
* @return Response
*/
public function dispatch(Request $request)
{
if (!$controller = $request->getMetadata('controller')) {
return new PageNotFoundResponse( '<h1>Page not found</h1>' );
}

if (!array_key_exists($controller, $this->controllers)) {
return new PageNotFoundResponse('<h1>Page not found</h1>');
}

$handler = $this->controllers[$controller];
$response = $handler($request);

if (is_string($response)) {
return new HttpResponse($response);
}
if (!is_object($response)) {
return new ApplicationErrorResponse('<h1>An error occurred</h1>');
}
if (!$response instanceof Response) {
if (!method_exists($response, '__toString')) {
return new ApplicationErrorResponse('<h1>An error occurred</h1>');
}
return new HttpResponse($response->__toString());
}
return $response;
}
}
]]></ac:plain-text-body></ac:macro>

<p>Don't worry about the various objects referenced; the main thing to understand is that it's using those same MVC building blocks: <code>Request</code>, <code>Response</code>, <code>Dispatchable</code>. In action, it looks like this:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
use Zend\Controller\Router,
Zend\Http\Request;

$request = new Request;

$router = new Router;
/*
* Configure some routes here. We'll assume we've somehow configured routes
* mapping the following controllers:
* - homepage
* - foo
* - rest
* - foobar
*/
$router->route($request);

$dispatcher = new Dispatcher();
$dispatcher
->attach('homepage', function($request) {
// Simply returning a string:
return '<h1>Welcome</h1> <p>Welcometo our site!</p>';
})
->attach('foo', function($request) {
// Simply returning a string:
return '<h1>Foo!</h1>';
})
->attach('rest', function($request) {
// Example of a "REST" service...
switch ($request->getMethod()) {
case 'GET':
if (!$id = $request->query('id', false)) {
// We have a "list operation"...
// Assume we somehow grab the list and create a response
return $response;
}
// We have an ID -- fetch it and return the page
break;
case 'POST':
// Create document and return a response
break;
case 'PUT':
if (!$id = $request->query('id', false)) {
// No ID in the query string means no document!
// Return a failure response
}
// We have an ID -- fetch and update from PUT params, and
// return a response
break;
case 'DELETE':
if (!$id = $request->query('id', false)) {
// No ID in the query string means no document!
// Return a failure response
}
// We have an ID -- delete, and // return a response
break;
default:
return new ApplicationErrorResponse('Unknown Method');
break;
}
})
->attach('foobar', function($request) {
// Curry in controllers to allow them to be lazy-loaded, and to ensure we
// get a response object back (Dispatcher will take care of that).
$controller = new FooBarController();
return $controller->dispatch($request);
});

$response = $dispatcher->dispatch($request);
$response->emit();
]]></ac:plain-text-body></ac:macro>

<p>The same MVC building blocks are combined in this example to produce something that looks quite different from the ZF1 MVC &ndash; yet can pick and choose pieces from the ZF2 MVC and interact with them in a standard way.</p>

<h3>Using Traits instead of inheritance</h3>

<p>Due to the simple nature of the Dispatchable interface, it lends itself well to PHP 5.next's traits features. As an example, a &quot;REST&quot; trait could be mixed in to a class quite easily to create a REST controller.</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
trait RestDispatcher
{
public function dispatch(Request $request, Response $response = null)
{
if (!$request instanceof HttpRequest) {
throw new DomainException('REST expects HTTP requests');
}

$resource = $this->getResource();

switch (strtoupper($request->getMethod())) {
case 'GET':
// ...
break;
case 'POST':
// ...
break;
case 'PUT':
// ...
break;
case 'DELETE':
// ...
break;
case 'HEAD':
// ...
break;
default:
$response->getStatus()->setStatusCode(405, 'Method Not Allowed');
}
return $response;
}
}

// In a controller file:
namespace Blog\Controller;

class Entry implements Dispatchable
{
use RestDispatcher;

public function getResource()
{
return new Blog\Model\EntryResource;
}
}
]]></ac:plain-text-body></ac:macro>

<p>While ZF2 will target PHP 5.3, we can also potentially provide ready-made traits for those using PHP 5.next. This will help simplify development for end users.</p>