Set up a Zend Framework application using Zend_Application (including PHPUnit setup)

Today I spent some time setting up a new Zend Framework application using ZF 1.8.0 Beta 1 and the new component Zend_Application. Using that component, all bootstrapping is done by Zend_Application_Bootstrap and so-called resource plugins. Such resource plugins are responsible for initialization of single components such as front controller, database or the view. This way, the whole bootstrapping is nicely modularized and keeps some headache away. In addition, the majority of settings can be set in the application config file. As I ran into some issues, I’d like to note the required steps and hope that it’s useful so someone. I won’t explain the files in detail, you can find enough information about components and parameters on the manual and the quickstart, but you should get to a working setup with just copy&pasting the code here ;)

Basically a big part of this setup can be done using the new Zend_Tool CLI, however I had some problems setting it up, so I’ve done it manually (however most of the code is the one generated by Zend_Tool).

At first, let’s create the directory structure:

mkdir -p public/css
mkdir -p public/img
mkdir -p public/js
mkdir -p application/configs
mkdir -p application/controllers
mkdir -p application/layouts/scripts
mkdir -p application/models
mkdir -p application/views/helpers
mkdir -p application/views/scripts/index
mkdir -p application/views/scripts/error
mkdir -p data/session
mkdir -p bin
mkdir -p library/App
mkdir -p tests/application
mkdir -p tests/application/controllers

Download the ZF release and drop the Zend directory into library. Now we’re ready to go.

Public files

public/.htaccess

SetEnv APPLICATION_ENV development

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

public/index.php just sets needed path and environment constants and lets Zend_Application do the rest. As I mentioned before, most of the settings are set in the application config file.

<?php
define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
define('APPLICATION_PATH', BASE_PATH . '/application');

// Include path
set_include_path(
    BASE_PATH . '/library'
    . PATH_SEPARATOR . get_include_path()
);

// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV',
              (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
                                         : 'production'));

// Zend_Application
require_once 'Zend/Application.php';

$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);

$application->bootstrap();
$application->run();

Config file

application/configs/application.ini: In my setup I got 3 databases for development, testing and production, so the DB definitions differ between environments.

[production]

# Debug output
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0

# Include path
includePaths.library = APPLICATION_PATH "/../library"

# Bootstrap
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"

# Front Controller
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.frontController.env = APPLICATION_ENV

# Layout
resources.layout.layout = "layout"
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"

# Views
resources.view.encoding = "UTF-8"
resources.view.basePath = APPLICATION_PATH "/views/"

# Database
resources.db.adapter = "pdo_mysql"
resources.db.params.host = "localhost"
resources.db.params.username = "myproject"
resources.db.params.password = "myproject"
resources.db.params.dbname = "myproject_production"
resources.db.isDefaultTableAdapter = true

# Session
resources.session.save_path = APPLICATION_PATH "/../data/session"
resources.session.remember_me_seconds = 864000

[testing : production]

# Debug output
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

# Database
resources.db.params.dbname = "myproject_testing"

[development : production]

# Debug output
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

# Database
resources.db.params.dbname = "myproject_development"

Bootstrap

application/Bootstrap.php: as defined in the config file, the application uses this file for the bootstrap process. I let Zend_Application handle all necessary initialization except the view. This was mainly because I wanted to set the view doctype to XHTML.

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initView()
    {
        // Initialize view
        $view = new Zend_View();
        $view->doctype('XHTML1_STRICT');
        $view->headTitle('My Project');
        $view->env = APPLICATION_ENV;

        // Add it to the ViewRenderer
        $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
            'ViewRenderer'
        );
        $viewRenderer->setView($view);

        // Return it, so that it can be stored by the bootstrap
        return $view;
    }
}

Done. That was the whole bootstrapping process. Your application is now ready to use ZF’s MVC implementation + Database. So let’s continue to add Application specific elements.

Add a layout

Let’s add a basic layout in application/layouts/scripts/layout.phtml:

<?php echo $this->doctype() ?>
<html>
<head>
    <?php echo $this->headTitle() ?>
    <?php echo $this->headLink() ?>
    <?php echo $this->headStyle() ?>
    <?php echo $this->headScript() ?>
</head>
<body>
    <?php echo $this->layout()->content ?>
</body>
</html>
[/php]

<h4>Add Controllers and views</h4>
<kbd>application/controllers/IndexController.php</kbd>: nothing special here.

<?php class IndexController extends Zend_Controller_Action { public function indexAction() { } }

And the view for index/index in application/views/scripts/index/index.phtml

<h1>Welcome to the Zend Framework!</h1>

application/controllers/ErrorController.php: is called in case of an error (e.g. controller does not exist). Depending on your environment it shows the exception and stack trace.

<?php
class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:

                // 404 error -- controller or action not found
                $this->getResponse()->setHttpResponseCode(404);
                $this->view->message = 'Page not found';
                break;
            default:
                // application error 
                $this->getResponse()->setHttpResponseCode(500);
                $this->view->message = 'Application error';
                break;
        }

        $this->view->exception = $errors->exception;
        $this->view->request   = $errors->request;
    }
}

And the belonging view in application/views/scripts/error/error.phtml

<h1>An error occurred</h1> 
<h3><?php echo $this->message ?></h3> 

<? if ('development' == $this->env): ?> 

    <h4>Exception information:</h4> 
    <p> 
      <strong>Message:</strong> <?php echo $this->exception->getMessage(); ?> 
    </p> 

    <h4>Stack trace:</h4> 
    <pre><?php echo $this->exception->getTraceAsString() ?></pre> 

    <h4>Request Parameters:</h4> 
    <? var_dump($this->request->getParams()); ?>

<? endif; ?>

Your application should work now. Try browsing to / and to /foo. You should end up seeing your index/index and error/error actions.

Add unit testing

Another point which took me a little longer to figure out was how to add unit testing with PHPUnit. I installed it via PEAR and got version 3.3.16.

Add config and helper files

A central config file for PHPUnit located at tests/phpunit.xml. The bootstrap attribute tells PHPUnit to execute that file before running any tests.

<phpunit bootstrap="./TestHelper.php" colors="true">
    <testsuite name="MyProject">
        <directory>./</directory>
    </testsuite>
</phpunit>

A test helper file which sets include paths located at tests/TestHelper.php:

<?php
define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
define('APPLICATION_PATH', BASE_PATH . '/application');

// Include path
set_include_path(
    '.'
    . PATH_SEPARATOR . BASE_PATH . '/library'
    . PATH_SEPARATOR . get_include_path()
);

// Define application environment
define('APPLICATION_ENV', 'testing');

require_once 'ControllerTestCase.php';

A base class for controller tests which sets up the needed environment located at tests/ControllerTestCase.php:

<?php
require_once 'Zend/Application.php';
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';

abstract class ControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase
{
    public $application;

    public function setUp()
    {
        $this->application = new Zend_Application(
            APPLICATION_ENV,
            APPLICATION_PATH . '/configs/application.ini'
        );

        $this->bootstrap = array($this, 'appBootstrap');
        parent::setUp();
    }

    public function appBootstrap()
    {
        $this->application->bootstrap();
    }
}

Add unit tests

Let’s add a simple unit test for the index controller at tests/application/controllers/IndexControllerTest.php.

<?php
class IndexControllerTest extends ControllerTestCase
{
    public function testIndexAction() {
        $this->dispatch('/');
        $this->assertController('index');
        $this->assertAction('index');
    }

    public function testErrorURL() {
        $this->dispatch('foo');
        $this->assertController('error');
        $this->assertAction('error');
    }
}

Test!

Now you should be able to run the tests. Change to the tests directory and run phpunit.

cd tests
phpunit --configuration phpunit.xml --verbose

This should run 2 tests and complete successfully.

Be lazy

No, just kidding ;) But I am, when it comes to typing. So I wrote a little bash script to run the tests without having to type the whole phpunit command. I dropped it under bin/run_tests.

#!/bin/bash
TESTS_DIR=$(dirname $0)/../tests/

cd $TESTS_DIR
phpunit --configuration phpunit.xml --verbose
echo ""
echo ""

Don’t forget to make the script executable:

chmod +x bin/run_tests

Note: As Matthew stated, the script is not necessary if your config file is called phpunit.xml. So just chdir to the tests directory and run phpunit if you don’t have any parameters you’d like to set on the command line.

Be even more lazy!

Download a zip archive of the project or grab your copy at GitHub :)

Sources