- Decoupled and DI-driven (
- Event-driven (
- HTTP handlers (
- An Event Manager is an object that aggregates listeners for one or more named events, and which triggers events.
- A Listener is a callback that can react to an event.
- An Event is an action.
1. Model – Doctrine
Doctrine can be integrated into Zend Framework 2 as a “module” which provides all the libraries and configuration in a self-contained bundle. Installing these Doctrine modules is pretty straightforward. We’ll use Composer to install them into the project directory, and then tweak a couple configuration files.
For this example, we need the DoctrineModule, which provides common code for different Doctrine providers, and theDoctrineORMModule, a “provider” module which includes all the Doctrine2 libraries for the ORM. (For those using MongoDB, there’s also a separate ODM provider module.) See the README file for the DoctrineORMModule for further details about the installation process.
Instalation and Configuration
//Instalation php composer.phar require doctrine/doctrine-orm-module:0.7.* //Enabling the modules config/application.config.php return array( 'modules' => array( 'Application', 'DoctrineModule', 'DoctrineORMModule', 'MyModule', ), // [...] ); //Configure the connection config/autoload/doctrine.local.php return array( 'doctrine' => array( 'connection' => array( 'orm_default' => array( 'driverClass' =>'Doctrine\DBAL\Driver\PDOMySql\Driver', 'params' => array( 'host' => 'localhost', 'port' => '3306', 'user' => 'username', 'password' => 'password', 'dbname' => 'database', ))))); //Configure mappings module/MyModule/config/module.config.php namespace MyModule; return array( 'doctrine' => array( 'driver' => array( __NAMESPACE__ . '_driver' => array( 'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver', 'cache' => 'array', 'paths' => array(__DIR__ . '/../src/' . __NAMESPACE__ . '/Entity') ), 'orm_default' => array( 'drivers' => array( __NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver' ))))), // [...] ./vendor/bin/doctrine-module orm:validate-schema ./vendor/bin/doctrine-module orm:schema-tool:create ./vendor/bin/doctrine-module orm:generate-entities 'path' --generate-annotations=true
2. Controller and Routing
Implementing each of the above interfaces is a lesson in redundancy; you won’t often want to do it. As such, we’ve developed two abstract, base controllers you can extend to get started.
The first is Zend\Mvc\Controller\AbstractActionController. This controller implements each of the above interfaces, and uses the following assumptions:
- An “action” parameter is expected in the RouteMatch object composed in the attached MvcEvent. If none is found, anotFoundAction() is invoked.
- The “action” parameter is converted to a camelCased format and appended with the word “Action” to create a method name. As examples: “foo” maps to “fooAction”, “foo-bar” or “foo.bar” or “foo_bar” to “fooBarAction”. The controller then checks to see if that method exists. If not, the notFoundAction() method is invoked; otherwise, the discovered method is called.
- The results of executing the given action method are injected into the MvcEvent‘s “result” property (via setResult(), and accessible via getResult()).
Essentially, a route mapping to an AbstractActionController needs to return both “controller” and “action” keys in its matches.
The second abstract controller ZF2 provides is Zend\Mvc\Controller\AbstractRestfulController. This controller provides a native RESTful implementation that simply maps HTTP request methods to controller methods, using the following matrix:
- GET maps to either get() or getList(), depending on whether or not an “id” parameter is found in the route matches. If one is, it is passed as an argument to get(); if not, getList() is invoked. In the former case, you should provide a representation of the given entity with that identification; in the latter, you should provide a list of entities.
- POST maps to create(). That method expects a $data argument, usually the $_POST superglobal array. The data should be used to create a new entity, and the response should typically be an HTTP 201 response with the Location header indicating the URI of the newly created entity and the response body providing the representation.
- PUT maps to update(), and requires that an “id” parameter exists in the route matches; that value is passed as an argument to the method. It should attempt to update the given entity, and, if successful, return either a 200 or 202 response status, as well as the representation of the entity.
- DELETE maps to delete(), and requires that an “id” parameter exists in the route matches; that value is passed as an argument to the method. It should attempt to delete the given entity, and, if successful, return either a 200 or 204 response status.
Additionally, you can map “action” methods to the AbstractRestfulController, just as you would in theAbstractActionController; these methods will be suffixed with “Action”, differentiating them from the RESTful methods listed above. This allows you to perform such actions as providing forms used to submit to the various RESTful methods, or to add RPC methods to your RESTful API.
Routing is the act of matching a request to a given controller. Typically, routing will examine the request URI, and attempt to match the URI path segment against provided constraints. If the constraints match, a set of “matches” are returned, one of which should be the controller name to execute. Routing can utilize other portions of the request URI or environment as well – for example, the host or scheme, query parameters, headers, request method, and more.
Two routers are provided, the
TreeRouteStack. Each works with the above interface, but utilize slightly different options and execution paths. By default, the
Zend\Mvc uses the
TreeRouteStack as the router.
This router simply takes individual routes that provide their full matching logic in one go, and loops through them in LIFO order until a match is found. As such, routes that will match most often should be registered last, and least common routes first. Additionally, you will need to ensure that routes that potentially overlap are registered such that the most specific match will match first (i.e., register later). Alternatively, you can set priorities by giving the priority as third parameter to the
addRoute()method, specifying the priority in the route specifications or setting the priority property within a route instance before adding it to the route stack.
Zend\Mvc\Router\Http\TreeRouteStack provides the ability to register trees of routes, and will use a B-tree algorithm to match routes. As such, you register a single route with many children.
TreeRouteStack will consist of the following configuration:
- A base “route”, which describes the base match needed, the root of the tree.
- An optional “route_plugins”, which is a configured
Zend\Mvc\Router\RoutePluginManagerthat can lazy-load routes.
- The option “may_terminate”, which hints to the router that no other segments will follow it.
- An optional “child_routes” array, which contains additional routes that stem from the base “route” (i.e., build from it). Each child route can itself be a
TreeRouteStackif desired; in fact, the
Partroute works exactly this way.
When a route matches against a
TreeRouteStack, the matched parameters from each segment of the tree will be returned.
TreeRouteStack can be your sole route for your application, or describe particular path segments of the application.
An example of a
TreeRouteStack is provided in the documentation of the
Zend\View provides the “View” layer of Zend Framework 2’s MVC system. It is a multi-tiered system allowing a variety of mechanisms for extension, substitution, and more.
The components of the view layer are as follows:
- Variables Containers hold variables and callbacks that you wish to represent in the view. Often-times, a Variables Container will also provide mechanisms for context-specific escaping of variables and more.
- View Models hold Variables Containers, specify the template to use (if any), and optionally provide rendering options (more on that below). View Models may be nested in order to represent complex structures.
- Renderers take View Models and provide a representation of them to return. Zend Framework 2 ships with three renderers by default: aPhpRenderer which utilizes PHP templates in order to generate markup, a JsonRenderer, and a FeedRenderer for generating RSS and Atom feeds.
- Resolvers utilizes Resolver Strategies to resolve a template name to a resource a Renderer may consume. As an example, a Resolver may take the name “blog/entry” and resolve it to a PHP view script.
- The View consists of strategies that map the current Request to a Renderer, and strategies for injecting the result of rendering to the Response.
- Rendering Strategies listen to the Zend\View\ViewEvent::EVENT_RENDERER event of the View and decide which Renderer should be selected based on the Request or other criteria.
- Response Strategies are used to inject the Response object with the results of rendering. That may also include taking actions such as setting Content-Type headers.
Additionally, Zend Framework 2 provides integration with the MVC via a number of event listeners in the Zend\Mvc\View namespace.
Creating and Registering Alternate Rendering and Response Strategies
Zend\View\View does very little. Its workflow is essentially to martial a ViewEvent, and then trigger two events, “renderer” and “response”. You can attach “strategies” to these events, using the methods addRenderingStrategy() and addResponseStrategy(), respectively. A Rendering Strategy investigates the Request object (or any other criteria) in order to select a Renderer (or fail to select one). A Response Strategy determines how to populate the Response based on the result of rendering.
Zend Framework 2 ships with three Rendering and Response Strategies that you can use within your application.
- Zend\View\Strategy\PhpRendererStrategy. This strategy is a “catch-all” in that it will always return theZend\View\Renderer\PhpRenderer and populate the Response body with the results of rendering.
- Zend\View\Strategy\JsonStrategy. This strategy inspects the Accept HTTP header, if present, and determines if the client has indicated it accepts an “application/json” response. If so, it will return the Zend\View\Renderer\JsonRenderer, and populate the Response body with the JSON value returned, as well as set a Content-Type header with a value of “application/json”.
- Zend\View\Strategy\FeedStrategy. This strategy inspects the Accept HTTP header, if present, and determines if the client has indicated it accepts either an “application/rss+xml” or “application/atom+xml” response. If so, it will return theZend\View\Renderer\FeedRenderer, setting the feed type to either “rss” or “atom”, based on what was matched. Its Response strategy will populate the Response body with the generated feed, as well as set a Content-Type header with the appropriate value based on feed type.
By default, only the PhpRendererStrategy is registered, meaning you will need to register the other Strategies yourself if you want to use them. Additionally, it means that you will likely want to register these at higher priority to ensure they match before thePhpRendererStrategy. As an example, let’s register the JsonStrategy:
The view layer of Zend Framework 2 incorporates and utilizes a custom Zend\EventManager\Event implementation –Zend\View\ViewEvent. This event is created during Zend\View\View::getEvent() and is passed directly to all the events that method triggers.
The ViewEvent adds accessors and mutators for the following:
- Model object, typically representing the layout view model.
- Renderer object.
- Request object.
- Response object.
- Result object.
The methods it defines are:
- setModel(Model $model)
Zend Framework comes with an initial set of helper classes. In particular, there are helpers for creating route-based URLs and HTML lists, as well as declaring variables. Additionally, there are a rich set of helpers for providing values for, and rendering, the various HTML <head>tags, such as HeadTitle, HeadLink, and HeadScript. The currently shipped helpers include:
- HTML Object Plugins
4. Service Manager
By default, Zend Framework utilizes Zend\ServiceManager within the MVC layer and in various other components. As such, in most cases you’ll be providing services, invokable classes, aliases, and factories either via configuration or via your module classes.
By default, the module manager listener Zend\ModuleManager\Listener\ServiceListener will do the following:
- For modules implementing Zend\ModuleManager\Feature\ServiceProviderInterface, or the getServiceConfig() method, it will call that method and merge the retrieved configuration.
- After all modules have been processed, it will grab the configuration from the registeredZend\ModuleManager\Listener\ConfigListener, and merge any configuration under the service_manager key.
- Finally, it will use the merged configuration to configure the ServiceManager instance.
In most cases, you won’t interact with the ServiceManager, other than to providing services to it; your application will typically rely on the configuration of the ServiceManager to ensure that services are configured correctly with their dependencies. When creating factories, however, you may want to interact with the ServiceManager to retrieve other services to inject as dependencies. Additionally, there are some cases where you may want to receive the ServiceManager to lazy-retrieve dependencies; as such, you may want to implement ServiceLocatorAwareInterface and know more details about the API of the ServiceManager.
Types of services
- abstract_factories, which should be an array of abstract factory class names.
- aliases, which should be an associative array of alias name/target name pairs (where the target name may also be an alias).
- factories, an array of service name/factory class name pairs. The factories should be either classes implementingZend\ServiceManager\FactoryInterface or invokable classes. If you are using PHP configuration files, you may provide any PHP callable as the factory.
- invokables, an array of service name/class name pairs. The class name should be class that may be directly instantiated without any constructor arguments.
- services, an array of service name/object pairs. Clearly, this will only work with PHP configuration.
- shared, an array of service name/boolean pairs, indicating whether or not a service should be shared. By default, theServiceManager assumes all services are shared, but you may specify a boolean false value here to indicate a new instance should be returned.
- An array (or Traversable object) of configuration compatible with Zend\ServiceManager\Config. (Basically, it should have the keys for configuration as discussed in the previous section.
- A string providing the name of a class implementing Zend\ServiceManager\ConfigInterface.
- An instance of either Zend\ServiceManager\Config, or an object implementing Zend\ServiceManager\ConfigInterface.
As noted previously, this configuration will be merged with the configuration returned from other modules as well as configuration files, prior to being passed to the ServiceManager; this allows overriding configuration from modules easily.