topics: php , vagrant , zend framework 2 (post)

A Bare Bones Zend Framework 2 Skeleton Application From Scratch

Overview

In this article I describe how to write your own minimal ZF2 Skeleton Application from scratch. Errors are intentionally introduced and rectified as a demonstration of requirements and functionality, hopefully providing insight into the structure and content of the bare-bones skeleton application. Whilst other introductory modules are available, zf2-base is intended to introduce the core components of Zend Framework 2 in a straight forward, incrementally documented manor and provide a simple basis on which to integrate further functionality.

Getting started

For environmental consistency, I highly recommend using Vagrant. Vagrant is a tool to build and run development platforms useable across a wide variety of hardware and operating systems. Vagrant configures and executes a virtual machine in which a development environment functions. There are a multitude of advantages to this, but the ones of most utility here are the ease by which a development environment can be created, the inherent consistency of the development environment configuration and the shared folder feature, allowing the use of your favourite editor/IDE to modify the application sources locally.

If you do not have Vagrant installed already, please follow the instructions here

Please see https://github.com/richardjennings/vagrant-lemp for the Vagrant configuration used in this tutorial. The Vagrant LEMP repository contains a Vagrant configuration which utilises Chef to configure and build the virtual machines configuration.

To get this Vagrant LEMP environment setup and running:

cd myprojectdir
git clone https://github.com/richardjennings/vagrant-lemp.git –recursive .
vagrant up

After the virtual machine image has been imported (Ubuntu 12.04.2 LTS (Precise Pangolin) 32bit), chef should provision the virtual machine, installing Nginx and PHP-FPM amongst others. In the vagrant configuration file (Vagrantfile), an ip address is specified:

config.vm.network :private_network, ip: “192.168.33.10”

Once Vagrant is running, you should find that Nginx responds to http://192.168.33.10 with an “It Works” page.

You can find the “It Works” page content in /shared/public/index.html

Try changing the content in /shared/pulbic/index.html and visit http://192.168.33.10 again. You should see the content change in line with your modifications.

To stop the Vagrant Virtual Machine:

vagrant halt

File structure

To get started, we need to create the default recommended file structure for a ZF2 Application and a very simple module. These folders and files should be created in the Vagrant shared/ folder.

.
..
config/
	application.config.php
	autoload/
		global.php
module/
    Application/
        Module.php
public/
    index.php
vendor/
composer.json

composer.json

{
    "name": "richardjennings/zf2-base",
    "description": "Bare-bones ZF2 Skeleton Application",
    "license": "MIT",
    "keywords": [
        "zf2"
    ],
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": ">=2.2.0"
    }
}

Composer is a PHP application that trys to resolve and install dependencies defined in a config file, composer.json. In composer.json we are defining via JSON in the “require” block the requirement for a PHP version equal or greater than 5.3.3. We also define “zendframework/zendframework” as a dependency with the requirement that the version number is equal or greater than 2.2.0.

When the command

composer install

is executed in the root directory, composer will download a copy of Zend Framework 2 and put it in:

vendor/zendframework/zendframework

If you do not have composer installed already, please checkout the installation instructions. Please ensure that “composer install” completed successfully. The contents of Zend Framework should now be in /vendor/zendframework.

It is possible to specify git repositories as dependencies, though in this basic example we are using the central composer package archive Packagist. Zend Framework specifies via its own composer.json file several properties that are evaluted by composer. The option specified of most importance to our Skeleton Applications initialisation is:

"autoload": {
    "psr-0": {
       "Zend\\": "library/",
       "ZendTest\\": "tests/"
   }
}

This autoload configuration instructs Composer to generate an autoload implementation for the namespaces Zend and ZendTest automatically. The autoloading code generated can be found in the vendor/composer folder.

/vendor/autoload.php provides the configured autoloading if required or included.

more information about Composer

public/index.php

ZF2 utilises the Front Controller Pattern, with convention expecting the front controller to be named index.php located in a folder named public. The folder named public is expected to be the webserver root directory, with other folders and files such as composer.json not accessible via url.

The purpose of the ZF2 front controller is usually to initialize and then run the Application. This can be achieved as follows:

<?php
//change directory such that all paths are relative to the application root., /public
chdir(dirname(__DIR__));

//include the autoloader generated by composer
require('vendor/autoload.php');

//initialize the Application with Application config
$application = Zend\Mvc\Application::init(require 'config/application.config.php');

//run the application, responding to a type of Request with a type of Response
$application->run();

Note that the array of configuration returned by config/application.config.php is provided as an argument to the Zend\Mvc\Application::init static method.

config/application.config.php

<?php
/**
 * This config file defines which modules the application uses, how the Module 
 * Manager can find modules, how the rest of the configuration for the 
 * application can be found and is the config used to initialize the 
 * MVC Application object and its dependencies. Further options are available for more
 * advanced functionality such as Service Manager configuration, but are left out for clarity.
 */
return array(
    
    /**
     * List the modules the module manager will load.
     *
     * Modules are named via Namespace. Here we are requiring
     * a module with namespace Application.
     */
    'modules' => array(
        'Application',
    ),

    /**
     * Provide configuration to the Module Manager (code responsible
     * for loading modules)
     *
     * If there are Namespaces that have not already been autoloaded then the
     * Module Manager will need to know where abouts on the file system to look for them. 
     */
    'module_listener_options' => array(
        /**
         * Module Paths for module discovery
         * These path values depend on how the front controller has initialized this 
         * environment. Typically we have changed into the root directory.
         * This allows the module paths to be specified relatively.Here we are specifying 
         * that modules live in a folder called module and a folder called vendor in the 
         * root folder (up one directory level from public)
         */
        'module_paths' => array(
            './module',
            './vendor',
        ),

        /**
         * Next we define how the configuration for the rest of the Application should 
         * be generated and retrieved.
         *
         * ZF2 does not use Application Environment to alternate between configurations
         * by default. Instead it is recomended that a global configuration file is 
         * utilized for all common configuration, and a local configuration file is used
         * for implementation specific configuration (that is normally not wanted in Version
         * Control)
         *
         * Globbing essentially means apply pattern matching to a directory structure.
         * Here we define the pattern used to retrieve our config files, which will be merged
         * into a single configuration.
         */
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
    ),
);

Initially our bare bones application is not going to need any configuration. As mentioned in the application.config.php file annotated above, config files have been configured to be loaded from config/autoload/. Despite our module not requiring any config, create the global config file and return an empty array. We can add any required configuration later.

config/autoload/global.php

<?php
return array();

The Application Module

Finally we need to create the module that corresponds with the application.config.php module configuration. Zend\ModuleManager\Listener\ModuleResolverListener (the bit of code that looks for modules), requires a class named Module to exist. For our module to be valid and loaded by the Module Manager, we must create this class.

module/Application/Module.php

<?php
namespace Application;

class Module
{
}

Test it out

If you are using Vagrant and the Vagrantfile suggested, check out http://192.168.33.10/

You should get an uncaught exception:

Fatal error: Uncaught exception ‘Zend\View\Exception\RuntimeException’ with message ‘Zend\View\Renderer\PhpRenderer::render: Unable to render template “error”; resolver could not resolve to a file’ This is telling us that there was an error, the Application tried to render an Error template, couldn’t find one and that is also an error. We need to create an Error template and configure the View Manager to use it.

View Manager

The View Manager is a ZF2 MVC component and is responsible for managing the View layer. To add an error template, we need to add the error template configuration to the View Managers configuration. As the error template is going to be in our application module, lets add some configuration to the application module config file to map an “error” template to a file.

module/Application/config/module.config.php

<?php
return array(
    //View manager configuration can be specified via the 'view_manager' key
    'view_manager' => array(
        //the template path stack is a record of all the places to look for templates
        'template_path_stack' => array(
            //here we specify templates can be found in the module/Application/views folder.
            __DIR__.'/../views/',
        ),
    ),
);

For ultimate bare bones simplicity what we have defined in the application module config above is a single place in which all our templates can be found module/Application/views, rather than a mapping between a template name and a specific file.

Module config

The Module Manager does not know where our module config file is or even if there is one. The Manager checks if a modules Module class has a getConfig() method. If it does, the Module Manager will call getConfig() and merge the module config with the application and autoloaded config. The Module Manager uses other mechanisms to determine information about modules, including checking for the presence of additional methods and checking if the Module object implements certain interfaces.

module/Application/Module.php

<?php
namespace Application;

class Module
{
    /**
     * Implementing a getConfig() method allows the Module Manager to
     * ask for the modules conifg. When asked, include and return 
     * module.config.php 
     */
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }
}

Create an empty view, name it error and use the file extension phtml

module/Application/views/error.phtml

More Errors

Unable to render template “layout/layout”; resolver could not resolve to a file’ The View Manager is trying to render a layout. This time we need to create a folder called layout, and add an empty view file called layout.phtml into it

module/Application/views/layout/layout.phtml

404

You should now find that a request to http://192.168.33.10/ returns your browsers default 404 page.