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. [source lang="php"] <?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 :)
Pingback: Unit Testing with the Zend Framework with Zend_Test and PHPUnit | free Zend Framework screencasts - Zendcasts
Pingback: » Have you made the jump to Zend Framework 1.8?
Pingback: PHP Podcasts » Unit Testing with the Zend Framework with Zend_Test and PHPUnit
Pingback: Zend Application: Guía para empezar
Pingback: Federico Cargnelutti (fedecarg) 's status on Sunday, 18-Oct-09 22:59:47 UTC - Identi.ca
Pingback: blog.nielslange.de » Test Driven Development (TTD) with the Zend Framework