Scratching the Surface of Zend Framework 2

Exclusive offer: get 50% off this eBook here
Zend Framework 2 Application Development

Zend Framework 2 Application Development — Save 50%

Explore the Zend Framework 2 and create your own superb social network with this book and ebook

$26.99    $13.50
by Christopher Valles | October 2013 | RAW Open Source PHP Web Development

In this article by Christopher Valles, the author of Zend Framework 2 Application Development, discusses the basic elements of Zend Framework 2. You will get an in-depth look at the components involved on a simple request/response operation and this knowledge will be used and expanded on throughout the article. After that we will review how the welcome page is loaded.

By the end of this article, you will know the lifecycle of a request and how to interact with the different components of the framework to produce some output.

Bootstrap your app

There are two ways to bootstrap your ZF2 app. The default is less flexible but handles the entire configuration, and the manual is really flexible but you have to take care of everything.

The goal of the bootstrap is to provide to the application, Zend\Mvc\Application, with all the components and dependencies needed to successfully handle a request. A Zend Framework 2 application relies on the following six components:

  • Configuration array
  • ServiceManager instance
  • EventManager instance
  • ModuleManager instance
  • Request object
  • Response object

As these are the pillars of a ZF2 application, we will take a look at how these components are configured to bootstrap the app.

To begin with, we will see how the components interact from a high perspective and then we will jump into details of how each one works. When a new request arrives to our application, ZF2 needs to set up the environment to be able to fulfill it. This process implies reading configuration files and creating the required objects and services; attach them to the events that are going to be used and finally create the request object based on the request data.

Once we have the request object, ZF2 will tell the router to do his job and will inspect the request object to determine who is responsible for processing the data.

Once a controller and action has been identified as the one in charge of the request, ZF2 dispatches it and gives the controller/action the control of the program in order to execute the code that will interpret the request and will do something with it. This can be from accepting an uploaded image to showing a sign-up form and also changing data on an external database.

When the controller processes the data, sometimes a view object is generated to encapsulate the data that we should send to the client who made the request, and a response object is created.

After we have a response object, ZF2 sends it to the browser and the request ends.

Now that we have seen a very simple overview of the lifecycle of a request we will jump into the details of how each object works, the options available and some examples of each one.

Configuration array

Let's dissect the first component of the list by taking a look at the index.php file:

chdir(dirname(__DIR__)); // Setup autoloading require 'init_autoloader.php'; // Run the application! Zend\Mvc\Application::init(require 'config/application.config.php')->run();

As you can see we only do three things. The first thing is we change the current folder for the convenience of making everything relative to the root folder. Then we require the autoloader file; we will examine this file later. Finally, we initialize a Zend\Mvc\Application object by passing a configuration file and only then does the run method get called.

The configuration file looks like the following code snippet:

return array( 'modules' => array( 'Application', ), 'module_listener_options' => array( 'config_glob_paths' => array( 'config/autoload/{,*.}{global,local}.php', ), 'module_paths' => array( './module', './vendor', ), ), );

This file will return an array containing the configuration options for the application. Two options are used: modules and module_listener_options. As ZF2 uses a module organization approach, we should add the modules that we want to use on the application here. The second option we are using is passed as configuration to the ModuleManager object. The config_glob_path array is used when scanning the folders in search of config files and the module_paths array is used to tell ModuleManager a set of paths where the module resides.

ZF2 uses a module approach to organize files. A module can contain almost anything, simple PHP files, view scripts, images, CSS, JavaScript, and so on. This approach will allow us to build reusable blocks of functionality and we will adhere to this while developing our project.

PSR-0 and autoloaders

Before continuing with the key components, let's take a closer look at the init_autoloader.php file used in the index.php file. As is stated on the first block comment, this file is more complicated than it's supposed to be. This is because ZF2 will try to set up different loading mechanisms and configurations.

if (file_exists('vendor/autoload.php')) { $loader = include 'vendor/autoload.php'; }

The first thing is to check if there is an autoload.php file inside the vendor folder; if it's found, we will load it. This is because the user might be using composer, in which case composer will provide a PSR-0 class loader. Also, this will register the namespaces defined by composer on the loader.

PSR-0 is an autoloading standard proposed by the PHP Framework Interop Group (http://www.php-fig.org/) that describes the mandatory requirements for autoloader interoperability between frameworks. Zend Framework 2 is one of the projects that adheres to it.

if (getenv('ZF2_PATH')) { $zf2Path = getenv('ZF2_PATH'); } elseif (get_cfg_var('zf2_path')) { $zf2Path = get_cfg_var('zf2_path'); } elseif (is_dir('vendor/ZF2/library')) { $zf2Path = 'vendor/ZF2/library'; }

In the next section we will try to get the path of the ZF2 files from different sources. We will first try to get it from the environment, if not, we'll try from a directive value in the php.ini file. Finally, if the previous methods fail the code, we will try to check whether a specific folder exists inside the vendor folder.

if ($zf2Path) { if (isset($loader)) { $loader->add('Zend', $zf2Path); } else { include $zf2Path . '/Zend/Loader/AutoloaderFactory.php'; Zend\Loader\AutoloaderFactory::factory(array( 'Zend\Loader\StandardAutoloader' => array( 'autoregister_zf' => true ) )); } }

Finally, if the framework is found by any of these methods, based on the existence of the composer autoloader, the code will just add the Zend namespace or will instantiate an internal autoloader, Zend\Loader\Autoloader, and use it as a default.

As you can see, there are multiple ways to set up the autoloading mechanism on ZF2 and at the end what matters is which one you prefer, as all of them in essence will behave the same.

ServiceManager

After all this execution of code, we arrive at the last section of the index.php file where we actually instantiate the Zend\Mvc\Application object.

As we said, there are two methods of creating an instance of Zend\Mvc\Application. In the default approach, we call the static method init of the class by passing an optional configuration as the first parameter. This method will take care of instantiating a new ServiceManager object, storing the configuration inside, loading the modules specified in the configuration, and getting a configured Zend\Mvc\Application.

ServiceManager is a service/object locator that implements the Service Locator design pattern; its responsibility is to retrieve other objects.

$serviceManager = new ServiceManager( new Service\ServiceManagerConfig($smConfig) ); $serviceManager->setService('ApplicationConfig', $configuration); $serviceManager->get('ModuleManager')->loadModules(); return $serviceManager->get('Application')->bootstrap();

As you can see, the init method calls the bootstrap() method of the Zend\Mvc\Application instance.

Service Locator is a design pattern used in software development to encapsulate the process of obtaining other objects. The concept is based on a central repository that stores the objects and also knows how to create them if required.

EventManager

This component is designed to provide multiple functionalities. It can be used to implement simple observer patterns, and also can be used to do aspect-oriented design or even create event-driven architectures.

The basic operations you can do over these components is attaching and detaching listeners to named events, trigger events, and interrupting the execution of listeners when an event is fired.

Let's see a couple of examples on how to attach to an event and how to fire them:

//Registering an event listener $events = new EventManager(); $events->attach(array('EVENT_NAME'), $callback); //Triggering an event $events->trigger('EVENT_NAME', $this, $params);

Inside the bootstrap method of Zend\Mvc\Application, we are registering the events of RouteListener, DispatchListener, and ViewManager. After that, the code is instantiating a new custom event called MvcEvent that will be used as the target when firing events. Finally, this piece of code will fire the bootstrap event.

ModuleManager

Zend Framework 2 introduces a completely redesigned ModuleManager. This new module has been built with simplicity, flexibility, and reuse in mind. These modules can hold everything from PHP to images, CSS, library code, views, and so on.

The responsibility of this component in the bootstrap process of an app is loading the available modules specified by the config file. This is accomplished by the following code line located in the init method of Zend\Mvc\Application:

$serviceManager->get('ModuleManager')->loadModules();

This line, when executed, will retrieve the list of modules located at the config file and will load each module.

Each module has to contain a file called Module.php with the initialization of the components of the module if needed. This will allow the module manager to retrieve the configuration of the module. Let's see the usual content of this file:

namespace MyModule; class Module { public function getAutoloaderConfig() { return array( 'Zend\Loader\ClassMapAutoloader' => array( __DIR__ . '/autoload_classmap.php', ), 'Zend\Loader\StandardAutoloader' => array( 'namespaces' => array( __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, ), ), ); } public function getConfig() { return include __DIR__ . '/config/module.config.php'; } }

As you can see we are defining a method called getAutoloaderConfig() that provides the configuration for the autoloader to ModuleManager. The last method getConfig() is used to provide the configuration of the module to ModuleManager; for example, this will contain the routes handled by the module.

Request object

This object encapsulates all data related to a request and allows the developer to interact with the different parts of a request. This object is used in the constructor of Zend\Mvc\Application and also is set inside MvcEvent to be able to retrieve when some events are fired.

Response object

This object encapsulates all the parts of an HTTP response and provides the developer with a fluent interface to set all the response data. This object is used in the same way as the request object. Basically it is instantiated on the constructor and added to MvcEvent to be able to interact with it across all the events and classes.

The request object

As we said, the request object will encapsulate all the data related to a request and provide the developer with a fluent API to access the data. Let's take a look at the details of the request object in order to understand how to use it and what it can offer to us:

use Zend\Http\Request; $string = "GET /foo HTTP/1.1\r\n\r\nSome Content"; $request = Request::fromString($string); $request->getMethod(); $request->getUri(); $request->getUriString(); $request->getVersion(); $request->getContent();

This example comes directly from the documentation and shows how a request object can be created from a string, and then access some data related with the request using the methods provided. So, every time we need to know something related to the request, we will access this object to get the data we need.

If we check the code on Zend\Http\PhpEnvironment\Request.php, the first thing we can notice is that the data is populated on the constructor using the superglobal arrays. All this data is processed and then populated inside the object to be able to expose it in a standard way using methods.

To manipulate the URI of the request you can get/set the data with three methods, two getters and one setter. The only difference with the getters is that one returns a plain string and the other returns an HttpUri object.

  • getUri() and getUriString()
  • setUri()

To retrieve the data passed in the request, there are a few specialized methods depending on the data you want to get:

  • getQuery()
  • getPost()
  • getFiles()
  • getHeader() and getHeaders()

About the request method, the object has a general way to know the method used, returning a string or nine specialized functions that will test specific methods based on the RFC 2616, which defines the standard methods for an HTTP request.

  • getMethod()
  • isOptions()
  • isGet()
  • isHead()
  • isPost()
  • isPut()
  • isDelete()
  • isTrace()
  • isConnect()
  • isPatch()

Finally, two more methods are available in this object that will test special requests such as AJAX and requests made by a flash object.

  • isXmlHttpRequest()
  • isFlashRequest()

Notice that the data stored on the superglobal arrays when populated on the object are converted from an Array to a Parameters object.

The Parameters object lives in the Stdlib section of ZF2, a folder where common objects can be found and used across the framework. In this case, the Parameters class is an extension of ArrayObject and implements ParametersInterface that will bring ArrayAccess, Countable, Serializable, and Traversable functionality to the parameters stored inside the object. The goal with this object is to provide a common interface to access data stored in the superglobal arrays. This expands the ways you can interact with the data in an object-oriented approach.

Zend Framework 2 Application Development Explore the Zend Framework 2 and create your own superb social network with this book and ebook
Published: October 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

The router

This component of the framework has been rewritten from scratch and it's really powerful and flexible, allowing you to create a lot of combination of routes. The main functionality of the router is matching a request to a given controller/action. It also assembles URLs based on the defined routes. This process is accomplished by inspecting the URI and attempting to match it with a given route that has some constraints, or by generating the URL based on the parameters defined on the routes.

The general flow of this component is as follows:

  • A request arrives and the framework creates a request object
  • Then the route parses the URI to identify the segments
  • After that the router iterates through the list of routes previously ordered
  • On each iteration, the segments of the URI are checked to see if it matches the current route
  • When a route matches the URI, the request is dispatched to the specific controller and action is configured on the route

We have two parts on the routing system: the router itself that holds the logic to test URIs against routes and generate URLs, and the route that will hold the data of how to match a specific request. The route can also hold default data, extra parameters, and so on.

'wall' => array( 'type' => 'Zend\Mvc\Router\Http\Segment', 'options' => array( 'route' => '/api/wall[/:id]', 'constraints' => array( 'id' => '\w+' ), 'defaults' => array( 'controller' => 'Wall\Controller\Index' ), ), )

This is an example of a route. As you can see we are specifying a parameter called id and we are adding constraints on this id.

The router in ZF2 can be used not only for HTTP requests but also for CLI applications. In our case, we will focus our attention on the website-related router but the essence of the router will be the same for the CLI apps.

Zend Framework 2 provides two different routers: SimpleRouteStack and TreeRouteStack, and eight possible route types to choose from: Hostname, Literal, Method, Part, Regex, Scheme, Segment, and Query. Here is a detailed view of all of them.

SimpleRouteStack

This is a basic router that gets all the configured routes and loops through them in a LIFO order until a match is found. The routes have priority and as the router is looping them in a LIFO order, the routes that are more likely to be matched (the ones that match more often) should be registered last, and the one that matches less should be registered first. Also, an easy way to set up the priority of the routes is using the third parameter of the AddRoute() method.

$router->addRoute('foo', $route, 10);

That's an example of how you can specify priorities on routes; just pass a number and the routes will be ordered by those values.

TreeRouteStack

This router is a little bit more complex than SimpleRouteStack because this allows you to create a tree of routes and will use a binary-tree algorithm to match the routes. The basic configuration of a TreeRouteStack route will consist of the following:

  • A base route that will be the root of the tree and all the following routes will extend this one.
  • An optional configured instance of RoutePluginManager to be able to lazy-load routes.
  • Another optional parameter will be may_terminate that tells the router that no other route will follow this one.
  • Finally, a child_routes array that can also be optional. If we specify routes here, they will extend the main one; also, a child route can be another TreeRouteStack.

You can add routes one by one on both routers by using the AddRoute() method or in batches using AddRoutes().

$route = Part::factory(array( 'route' => array( 'type' => 'literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'Application\Controller\IndexController', 'action' => 'index' ) ), ), 'child_routes' => array( 'forum' => array( 'type' => 'literal', 'options' => array( 'route' => 'forum', 'defaults' => array( 'controller' => 'Application\Controller\ForumController', 'action' => 'index' ) ) ) ) ));

As you can see, the first route we defined is the home page – the root of the domain. Then we defined the route for the forum extending from the base one.

Hostname

$route = Hostname::factory(array( 'route' => ':subdomain.domain.tld[/:type]', 'constraints' => array( 'subdomain' => 'fw\d{2}' ), 'defaults' => array( 'type' => 'json', ), ));

This is a usage example of how to configure a Hostname route. In the route parameter, we specify the hostname to match in the URI. Also, you can set up parameters, such as subdomain in this case.

You can set up constraints and default values on the route; in this particular example, we are specifying a regular expression that will limit the matching of the route to all the subdomains starting with the letters fw and followed by two digits. Sometimes you will need to pass values to the controllers based on the URL or define some default values for parameters on the URL. In this example we are setting the default value of type to the json string.

Literal

This route will match a URI path literally as specified by the route parameter. As usual, the default data will be the parameters you want returned on a match.

$route = Literal::factory(array( 'route' => '/foo', 'defaults' => array( 'controller' => 'Application\Controller\IndexController', 'action' => 'foo' ), ));

Method

This type of route will match if the HTTP method used on the request matches the one configured in the route using the verb parameter. We can specify multiple methods on the same route if we separate them with comma.

$route = Method::factory(array( 'verb' => 'post,put', 'defaults' => array( 'controller' => 'Application\Controller\IndexController', 'action' => 'form-submit' ), ));

Part

The Part route type not only extends TreeRouteStack but also implements RouteInterface. It means that this route can hold a tree of possible routes based on the URI segments. Let's explore the idea of a forum application. We want to show the home of the website when you go to the root of the domain, and then we want to display the forum if you go to domain.com/forum. We can accomplish that using the Part route type and we will need to configure it as shown in the following code snippet:

$route = Part::factory(array( 'route' => array( 'type' => 'literal', 'options' => array( 'route' => '/', 'defaults' => array( 'controller' => 'Application\Controller\IndexController', 'action' => 'index' ) ), ), 'route_plugins' => $routePlugins, 'may_terminate' => true, 'child_routes' => array( 'forum' => array( 'type' => 'literal', 'options' => array( 'route' => 'forum', 'defaults' => array( 'controller' => 'Application\Controller\ForumController', 'action' => 'index' ) ) ) ) ));

As you can see in the previous code example, we are defining a child_routes parameter with more routes inside, thus creating a tree of routes. Of course, the routes added as child will extend the parent route and therefore the matching URI path. Now with this in place if we get a request on domain.com/forum, ZF2 will inspect the request object and will match the URI against the routes we defined. Finally, as the forum route will match, the request will be dispatched to ForumController.

Regex

A Regex route will be based on a regular expression test match of the URI. You need to specify regex in the regex parameter and it can be any valid regular expression.

When working with this route we have to keep two things in mind. The first one is to use named captures on regex for any value we want to return as parameter on a match, the second is that we need to specify a value for the spec parameter to map each regex capture to the specific parameter. This will be used by the URL helper when building URLs based on the routes. For instance:

$route = Regex::factory(array( 'regex' => '/blog/(?<id>[a-zA-Z0-9_-]+)(\.(?<format>(json|html|xml|rss)))?', 'defaults' => array( 'controller' => 'Application\Controller\BlogController', 'action' => 'view', 'format' => 'html', ), 'spec' => '/blog/%id%.%format%', ));

In the previous example, you will notice that regex is using named captures enclosed in <> and we are specifying the format of the URL in the spec parameter, identifying each parameter with a text between percentage symbols. This will help the router to put the value of the parameters in the correct place in the URL while assembling it.

Scheme

The Scheme route type will match a URI based only on the URI scheme. This route type is similar to the Literal route because the match has to be exact and will return the default parameters on a successful match.

$route = Scheme::factory(array( 'scheme' => 'https', 'defaults' => array( 'https' => true, ), ));

As you can see in the previous code example, if the route matches, we will return a parameter called https with a true value.

Segment

$route = Segment::factory(array( 'route' => '/:controller[/:action]', 'constraints' => array( 'controller' => '[a-zA-Z][a-zA-Z0-9_-]+', 'action' => '[a-zA-Z][a-zA-Z0-9_-]+', ), 'defaults' => array( 'controller' => 'Application\Controller\IndexController', 'action' => 'index', ), ));

This is an example of how to use the Segment route that will allow us to match against any segment of the URI path. In the route parameter, the segments are defined using a specific notation; this is a colon followed by alphanumeric characters. The segments on a URI can be optional and that will be denoted by enclosing the segment in brackets.

Each segment must have constraints associated with it; the constraint will be a regular expression defining the requirements for a positive match.

Wildcard

This type of route allows you to get all the segments of a URI specifying the separator between keys and values. Consider the following code example to get a better understanding:

'route_name' => array( 'type' => 'Zend\Mvc\Router\Http\Wildcard', 'options' => array( 'key_value_delimiter' => '/', 'param_delimiter' => '/', ) )

That's how the route looks when configured, and it will work like this. If you have a URI such as /id/1/name/john, this route will return the two variables id and name. The first one will contain the number 1 and the second will contain the string john.

Query

This is the last type of route offered by the route system of Zend Framework 2. This will allow us to capture and define parameters on a query string. An important point is that it's designed to be used as a child route.

$route = Part::factory(array( 'route' => array( 'type' => 'literal', 'options' => array( 'route' => 'page', 'defaults' => array( ), ), ), 'may_terminate' => true, 'route_plugins' => $routePlugins, 'child_routes' => array( 'query' => array( 'type' => 'Query', 'options' => array( 'defaults' => array( 'foo' => 'bar' ) ) ), ), ));

In this example we can see how the route has been defined as a child of a Literal one. Then we can use this to generate a URL with a query string. For example:

$this->url( 'page/query', array( 'name'=>'my-test-page', 'format' => 'rss', 'limit' => 10, ) );

In the previous code example, we should specify /query in order to activate the child route while assembling the URL and be able to append the parameters to the query string of the URL generated.

DispatchListener

One of the big changes on this new version of the framework is that everything is built around events. That's why the dispatchers in just an event listener waiting for the EVENT_DISPATCH event.

When this event is triggered, it will be captured by DispatchListener and this will lead to a few things. Let us see the following code snippet step-by-step to fully understand the dispatch flow:

$routeMatch = $e->getRouteMatch(); $controllerName = $routeMatch->getParam('controller', 'not-found'); $application = $e->getApplication(); $events = $application->getEventManager(); $controllerLoader = $application->getServiceManager()->get('ControllerLoader');

First the listener will retrieve information from the event itself, such as controller, route matched, and the application, and will try to load the controller. Notice that we are passing a second parameter to the $routeMatch->getParam() method; if for any reason the parameter does not exists on the route, this one will be the returned value by default.

If everything is successful, it will execute the method dispatch of the controller. Let's see the actual code that accomplishes that.

try { $return = $controller->dispatch($request, $response); } catch (\Exception $ex) { $e->setError($application::ERROR_EXCEPTION) ->setController($controllerName) ->setControllerClass(get_class($controller)) ->setParam('exception', $ex); $results = $events->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $e); $return = $results->last(); if (! $return) { $return = $e->getResult(); } } return $this->complete($return, $e);

The dispatch responsibility is shared between DispatchListener and the controller; actually the dispatch itself is located in the AbstractActionController class. This method has the logic to examine the RouteMatch object, get the action to be called, and figure out if it exists and is callable. If it is, it will take care of calling it and returning the results.

$action = $routeMatch->getParam('action', 'not-found'); $method = static::getMethodFromAction($action); if (!method_exists($this, $method)) { $method = 'notFoundAction'; } $actionResponse = $this->$method(); $e->setResult($actionResponse); return $actionResponse;

Summary

Good job! Right now you should have a clear picture about how a request is handled in ZF2, how the components are involved in the process, and how they are used to complete the request. These components are the basis of ZF2, so it's really important that you understand how to use them and how to interact with the data using these components.

We discovered how to bootstrap the application and the options we have to customize it. We have also learned how the request object holds the data and how to access them. The article also showed how the router will use the request data and the set of routes we configure to determine which controller and action should be executed. Also, we saw how the dispatch process is executed and how the actual controller/action gets called. In the meantime we explored the different solutions and helpers ZF2 provides for some common problems and how they help us to solve them. We finally saw the last step of the request, the response.

Now with all this knowledge, we have a solid base on which to start developing all the components of our social network step-by-step. We will be developing the API side using AbstractRestfulController as a base to build a web service and the AbstractActionController class for the web client. We are going to be developing the two sides at the same time to be able to see the progress in the browser and be able to interact with the forms and the different features we develop.

Resources for Article:


Further resources on this subject:


Zend Framework 2 Application Development Explore the Zend Framework 2 and create your own superb social network with this book and ebook
Published: October 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Christopher Valles

Christopher Valles is a Software Engineer from Barcelona, Spain, currently based in London, UK. He started developing when he was seven using a Vtech kid laptop that was strangely shipped with a simple version of the BASIC programming language. Since then, he has explored more than 16 different programming languages ranging from Assembler to PHP, Python, and GO.

Chris also stepped into the sysadmin role and has been managing systems since he started working in this industry. He has taken care of servers right from simple webservers to infrastructures on the Cloud and internal Mac infrastructures. He is an Apple Certified Support Professional and Apple Certified Technical Coordinator.

His desire to learn and experiment has driven him to explore other fields, such as machine learning and robotics. He currently owns close to five robots and has built more than 20 over the past years. If you don't find him on the computer, he is probably spending time in the kitchen cooking delicious recipes.

The sectors where Chris has worked ranges from adult content websites and payment processors to social networks and the gaming industry. Presently, he's working as a Software Engineer at Hailo Networks, Ltd.

Books From Packt


Expert PHP 5 Tools
Expert PHP 5 Tools

Zend Framework 2 Cookbook
Zend Framework 2 Cookbook

 Zend Framework 1.8 Web Application Development
Zend Framework 1.8 Web Application Development

Zend Framework 2.0 by Example: Beginner’s Guide
Zend Framework 2.0 by Example: Beginner’s Guide

 Yii 1.1 Application Development Cookbook
Yii 1.1 Application Development Cookbook

Web Application Development with Yii and PHP
Web Application Development with Yii and PHP

CouchDB and PHP Web Development Beginner's Guide
CouchDB and PHP Web Development Beginner's Guide

PHP Application Development with NetBeans: Beginner's Guide
PHP Application Development with NetBeans: Beginner's Guide


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
7
K
W
r
R
N
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software