Zend Framework on a shared host

8th January 2010

When you deploy a Zend Framework website to a shared host, you usually cannot change the DocumentRoot to point at the public/ folder of the website. As a result the URL to the website is now http://www.example.com/public/. This doesn't look very professional, so we'd like to remove it.

The easiest way, given a ZF project created using Zend_Tool is this:

Create /index.php

<?php 
define('RUNNING_FROM_ROOT'true);
include 'public/index.php';

This uses the index.php already created by Zend_Tool and means that we don't have to change anything if we move to a VPS host where we can set the DocumentRoot directly to public/.

Create /.htaccess


SetEnv APPLICATION_ENV development

RewriteEngine On
RewriteRule .* index.php

We create a .htaccess file that redirects every request to index.php. We want to do this so that no one can try and read application/configs/application.ini. Obviously, set the APPLICATION_ENV to the correct value!

Referencing public facing files

Having created a very aggressive, rewrite rule, what about CSS/JS/image files though?

Fortunately, we already have a .htaccess file in the public/ folder that correctly handles this situation. As Apache will execute the .htaccess files in the deepest directory it finds, any reference to a public facing file within the public/ folder will correctly be served.

You do have to be aware of this when referencing public facing files though and add the /public to the baseUrl yourself.

For example, you may set up your CSS file and other view settings within a Front Controller plugin like this:


class App_Controller_Plugin_View extends Zend_Controller_Plugin_Abstract
{
    public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    {
        $frontController Zend_Controller_Front::getInstance();
        $view $frontController->getParam('bootstrap')->getResource('view'); 

        $view->doctype('XHTML1_STRICT');
            
        $baseUrl $request->getBaseUrl();
        if (defined('RUNNING_FROM_ROOT')) {
            $baseUrl .= '/public'; 
            $frontController->setBaseUrl($baseUrl); 
        }
        $view->headLink()->appendStylesheet($baseUrl '/css/main.css');
        $view->headLink()->appendStylesheet($baseUrl '/css/screen.css''screen');
        $view->headLink()->appendStylesheet($baseUrl '/css/print.css''print');
    }   
}

(This code assumes you have added a resources.view[] = "" to your application.ini)

As we have a constant that tells us if we're running from the root, we can dynamically add the /public into the URL. If we change to a host where the DocumentRoot is set directly to the public/ folder, then we don't need to change our code.

That's it. Your Zend Framework application works nicely with shared hosts.

Accessing your configuration data that's stored in application.ini

27th November 2009

Zend_Application will read the data in your application.ini and make it available from your bootstrap's getOptions() method. It then sets the bootstrap as a parameter in the front controller. Note that the top level keys are all normalised to lowercase too.

You can then retrieve the options in a number of ways.

In the controller you can do this:


    public function someAction()
    {
        $bootstrap $this->getInvokeArg('bootstrap'); 
        $options $bootstrap->getOptions();
    }

Outside of the controller you can do this:


    $bootstrap Zend_Controller_Front::getInstance()->getParam('bootstrap');
    $options $bootstrap->getOptions();

One downside is that $options is a nested array, and not a Zend_Config object. If you prefer to work with a Zend_Config object you need to create it yourself as Zend_Application discards the one it creates. The easiest way to do this is to create a new method in your bootstrap to create the Zend_Config object and store to the registry.


    protected function _initConfig()
    {
        $config = new Zend_Config($this->getOptions());
        Zend_Registry::set('config'$config);
        return $config;
    }

You can then get at your config data wherever you need it. Try not to depend too much on Zend_Registry keys though, as it can make testing harder.

Zend Framework URL Rewriting in IIS6

16th November 2009

I've written before about URL rewriting with IIS7's URL Rewrite module.

IIS6, which ships with Windows Server 2003 does not have this module though and guess which version my client's IT dept run? As usual, they wouldn't install ISAPI_Rewrite or one of the other solutions for me. In the past, I've simply written a new router that creates URLs with normal GET variables, but this is ugly and I wanted better.

One thing IIS6 does let you do is configure a URL to be called upon a 404 error, which then allows you to have "pretty" URLs and be able to route them.

Firstly, I set up the URL handler in the IIS Manager:

Screen shot 2009-11-13 at 07.46.59-1.jpg

This will result in all unrecognised URLs being redirected to index.php. The standard Zend_Controller_Request_Http object will automatically extract the URL and routing works as expected.

However, there are three problems:

  1. The $_POST array is always empty
  2. $_SERVER['REQUEST_METHOD'] is always GET, even for a post request
  3. The first key in $_GET has been mangled by IIS

As Zend Framework wraps up the request into a Request object, this is fairly simple to work around by creating our own Request object.


class App_Controller_Request_Iis404 extends Zend_Controller_Request_Http
{
    /**
     * Constructor
     *
     * If a $uri is passed, the object will attempt to populate itself using
     * that information.
     *
     * @param string|Zend_Uri $uri
     * @return void
     * @throws Zend_Controller_Request_Exception when invalid URI passed
     */
    public function __construct($uri null)
    {
        // As Zend_Controller_Request_Http accesses the superglobals directly, we
        // will have to write into $_GET and $_POST directly

        // The post variables can be accessed from php://input
        $input file_get_contents('php://input');
        if (strlen($input)) {
            $input urldecode($input);
            parse_str($input$_POST);
        }
        
        // fix $_GET
        foreach ($_GET as $key=>$value) {
            if (substr($key04) == '404;') {
                // special key created by IIS - the actual key name is after the ?
                $bits explode('?'$key);
                if (count($bits) > 1) {
                    $_GET[$bits[1]] = $value;
                }
            }
        }
        
        return parent::__construct($uri);
    }
    
    /**
     * Return the method by which the request was made
     *
     * @return string
     */
    public function getMethod()
    {
        if (!empty($_POST)) {
            return 'POST';
        }
        
        return parent::getMethod();
    }
}

We start by reading the php://input stream which on a POST request will hold the POST variables. We can then transfer them to the $_POST array. Similarly, the key in the $_GET array that has been mangled, is easy to detect as it starts with '404;'. We can then find the ? and the part after it is the real key, so we create a new $_GET element for that item. Finally, we override getMethod() and return 'POST' if there are any elements in $_POST.

To use a custom Request object, you need to create an _init method in your Bootstrap:


class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    function _initIis404RequestObject()
    {
        $this->bootstrap('frontController');
        $frontController $this->getResource('frontController');
        $frontController->setRequest($options['frontController']['requestClass']);  
    }

Zend Framework's standard URLs now work nicely with IIS6 on Window Server 2003.

Bootstrapping modules in ZF 1.8 and up

8th July 2009

I've started to play with modules in a Zend Framework 1.8 application as the new autoloader means that all your model directories no long have to be on the include_path for autoloading to work. What I'm specifically interested in is being able to instantiate a model that is within a module from within another module.

Setting it all up isn't that hard, but I couldn't find a concise description, so these are my notes on it.

Start by creating a ZF application using the zf command line tool:

$ zf create project myproject

Don't forget to put a copy of ZF 1.8 into the library directory or ensure that it is on the include_path.

We now need a module:

$ cd myproject
$ zf create module blog

This will create all the relevant directories in myproject/application/modules/blog. We create a simple model within the blog module:

File: myproject/application/modules/blog/models/Info.php

<?php

class Blog_Model_Info
{
    public function getInfo()
    {
        return "This is the info about the Blog module";
    }
}

The naming is important. First we have the module name, then we have the word "Model" then we have the name of the model itself. It is important that this model's name matches the filename too.

We want to use this model within the index action of the Index controller like this:

File: myproject/application/controller/IndexController.php

<?php

class IndexController extends Zend_Controller_Action
{

    public function init()
    {
        /* Initialize action controller here */
    }

    public function indexAction()
    {
        // action body
        $info = new Blog_Model_Info();
        $this->view->blogInfo $info->getInfo();
    }
}

I've included the entire class here; most of it is auto-generated, you just need to add the two new lines under the // action body comment. Having assigned something to the view, we should display it so we can prove it worked:

File: myproject/application/views/scripts/index/index.phtml

<?php echo $this->blogInfo?>

(Note that we replace the pretty ZF welcome page.)

At this point we get an error:
blog_model_module_failure.png

This is because we haven't told the autoloader about our module's model's directory. This is done using Zend_Application's bootstrapping. There are two parts:

Firstly we have to add a line to application.ini enable modules at the end of the [production] section:

File: myproject/application/configs/application.ini

resources.frontController.moduleDirectory APPLICATION_PATH "/modules"
resources.modules[] = ""

Secondly, we need to add a Bootstrap class to our module:

File: myproject/application/modules/blog/Bootstrap.php

<?php

class Blog_Bootstrap extends Zend_Application_Module_Bootstrap
{

}

Again, the naming is important; the class name must be {module name}_Bootstrap and it must extend Zend_Application_Module_Bootstrap. It must be stored in a file called Bootstrap.php within the root of the module.

That's it. If you refresh the page, you'll get the data from the Blog module's Info model within the default module:

blog_model_module_success.png

All in all, it's not difficult at all, but if you don't have those two lines in application.ini and define a module bootstrap class, then it doesn't work.

Metadata from Zend_Db_Table_Abstract

28th May 2009

This post is part of a series about my experiences building a PHP app for Windows Server 2008 and IIS 7 for the European WinPHP Challenge 2009 which is sponsored by iBuildings, Microsoft and Leaseweb.

I finally found some more time to work on SuccesSQL and can now display table structure information:

successql_structure-1.jpg

Zend_Db_Table provides all this information directly which is quite useful, however the intended use-case for Zend_Db_Table is that you extend Zend_Db_Table_Abstract for each table that you want to interact with. Obviously, SuccesSQL doesn't know in advance the names of the tables, so I created a stub class that allowed me to instantiate a Zend_Db_Table dynamically:


class SSQL_Db_Table extends Zend_Db_Table_Abstract
{
}

Then, for any given table, I can instantiate like this:


$table = new SSQL_Db_Table(array('name'=>$tableName));

The metadata about the table is stored in a protected member variable called _metadata. As this is protected, it's not accessible, outside of the class, so I created an accessor method to allow me to get at the data:


class SSQL_Db_Table extends Zend_Db_Table_Abstract
{

    public function getMetadata()
    {
        if (empty($this->_metadata)) {
            $this->_setupMetadata();
        }
        return $this->_metadata;
    }

}

Interestingly, Zend_Db_Table_Abstract lazily loads the _metadata variable and so I had to check if it had been loaded and if not, set it up. The data returned is an multi-dimensional array, with one sub-array per field. A dump looks like this:


array
  'id' => 
    array
      'SCHEMA_NAME' => null
      'TABLE_NAME' => string 'albums' (length=6)
      'COLUMN_NAME' => string 'id' (length=2)
      'COLUMN_POSITION' => int 1
      'DATA_TYPE' => string 'int' (length=3)
      'DEFAULT' => null
      'NULLABLE' => boolean false
      'LENGTH' => int 4
      'SCALE' => int 0
      'PRECISION' => int 10
      'UNSIGNED' => null
      'PRIMARY' => boolean true
      'PRIMARY_POSITION' => int 1
      'IDENTITY' => boolean true
  'artist' => 
    array
      'SCHEMA_NAME' => null
      [etc]

From then on, it's just a case of display formatting.

Zend_Loader's autoloader deprecated in Zend Framework 1.8

30th April 2009

Zend_Loader's autoloader has been deprecated in the upcoming Zend Framework version 1.8 and so you now get a notice if you use it:

Notice: Zend_Loader::Zend_Loader::registerAutoload is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead in /www/zf-tutorial/library/Zend/Loader.php on line 207

Notice: Zend_Loader::Zend_Loader::autoload is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead in /www/zf-tutorial/library/Zend/Loader.php on line 186

Notice: Zend_Loader::Zend_Loader::autoload is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead in /www/zf-tutorial/library/Zend/Loader.php on line 186

(and so on)

This is because you have the lines:


require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

(or similar) somewhere in your bootstrap system.

The easiest solution is to change them to:


require_once 'Zend/Loader/Autoloader.php';
$loader Zend_Loader_Autoloader::getInstance();
$loader->registerNamespace('App_');

Where 'App_' is the name of a directory on your include path that has classes within it that follow the Zend Framework naming convention, so change it as appropriate and add more if you need them.

If you need generic autoloading ability, for instance because your models directory is on your include path and you don't namespace your model classes, then add this:


$loader->setFallbackAutoloader(true);

You should also add:


$loader->suppressNotFoundWarnings(false);

when in development mode as then the new autoloader will actually tell you what the syntax error is rather than showing you a white page :)

Now you should go and read the Zend_Loader_Autoloader documentation to learn how very useful it is!

Update: You should also read Matthew's article on Zend_Loader_Autoloader as it explains this all in detail!

Initial notes on Zend_Application

25th March 2009

Zend_Application is now in the incubator and being actively developed for version 1.8 of Zend Framework. I've had a little play using it with a standard ZF application with no modules and this is what I've worked out so far.

As a result this post rambles a bit; sorry about that.

Zend_Application is intended to make bootstrapping your application easier, presumably with less code. It also has a new autoloader, Zend_Loader_Autoloader, but I haven't worked out why I care about that yet.

Back to bootstrapping. The general use-case is to extend Zend_Application_Bootstrap_Base and put your code in there. There are two ways to do this bit:

  1. Add functions to your Zend_Application_Bootstrap_Base class that start with _init, for example _initConfig().
  2. Use pre-supplied (or write your own) plugin classes. The idea is that these are then easily reusable across multiple bootstrap scenarios such as testing, web service provision, standard website, etc. Presumably they are a bit slower as a result, compared to just using functions within the Zend_Application_Bootstrap_Base sub-class.

Inline initialisation functions

To use Zend_Application's bootstrap with inline functions, we need to set up an index.php that looks something like this:

//File: public/index.php
<?php

define('BASE_PATH'realpath(dirname(__FILE__) . '../'));
define('APPLICATION_PATH'BASE_PATH '/application');

set_include_path(BASE_PATH '/library/incubator'
    PATH_SEPARATOR .BASE_PATH '/library'
    PATH_SEPARATOR get_include_path()
);

// APPLICATION_ENVIROMENT defines which config section is loaded
if(!defined('APPLICATION_ENVIRONMENT')) {
    define('APPLICATION_ENVIRONMENT''development');
}

require_once 'Zend/Application.php';

$application = new Zend_Application(APPLICATION_ENVIRONMENT,
    array(
        'bootstrap'=>array('path'=>APPLICATION_PATH.'/Bootstrap.php'),
        'autoloadernamespaces' => array('Zend''App')
    ));
$application->bootstrap();
$application->run();

All we are doing here is setting some constants and the include path so that we can load the Zend Framework. As Zend_Application is in the incubator, we need that too.

We then instantiate Zend_Application passing in the environment string and an array of options. In our case, we need to tell it where to find the bootstrap class and that our library have Zend_XXX and App_XXX classes. Though, I'm not sure what it does with the autoloader info.

The Bootstrap class looks like this:

//File: application/Bootstrap.php
<?php

class Bootstrap extends Zend_Application_Bootstrap_Base
{
    protected $_config;

    function _initConfig()
    {
        // config
        $this->_config = new Zend_Config_Ini(APPLICATION_PATH 
            '/config/app.ini'APPLICATION_ENVIRONMENT);
        Zend_Registry::set('config'$this->_config);
        Zend_Registry::set('env'APPLICATION_ENVIRONMENT);

        // debugging
        if($this->_config->debug) {
            error_reporting(E_ALL E_STRICT);
            ini_set('display_errors''on');
        }
    }

    function _initDB()
    {
        // Database
        if($this->_config->db) {
            $dbAdapter Zend_Db::factory($this->_config->db);
            Zend_Db_Table_Abstract::setDefaultAdapter($dbAdapter);
            Zend_Registry::set('dbAdapter'$dbAdapter);
        }
    }

    function _initView()
    {
        // view and layout setup
        Zend_Layout::startMvc(APPLICATION_PATH '/views/layouts');
        $view Zend_Layout::getMvcInstance()->getView();
        $view->doctype('XHTML1_STRICT');
        $view->headTitle()->setSeparator(' - ');
    }

    function _initFrontController()
    {
        $frontController Zend_Controller_Front::getInstance();
        $frontController->setControllerDirectory(APPLICATION_PATH .'/controllers');
        $frontController->setParam('env'APPLICATION_ENVIRONMENT);

        // action helpers
        Zend_Controller_Action_HelperBroker::addPath(APPLICATION_PATH .'/controllers/helpers');
    }

    public function run()
    {
        $frontController Zend_Controller_Front::getInstance();
        $frontController->dispatch();
    }
}

In this case, I've grouped my init logic into four separate functions, but I could equally have created one function called _initAll(). The only function that you absolutely must have is run(), which in this case we simply dispatch the front controller. It seems a little odd that run() isn't already defined in Zend_Application_Bootstrap_Base though as it's going to be the same code for everyone.

The order of loading of the _init methods is the same order as they are defined in the class. You can change the order by calling $this->bootstrap('xxx'); in any given _init method to force _initXxx() to be run. The system is clever enough not to call the _init function twice.

Incidentally, the config file I've been using look something like this:


// File: application/config/app.ini
[site]
; All the standard site-specific settings
db.adapter PDO_MYSQL
db.params.dbname "test"
db.params.username "test"
db.params.password "test"
db.params.hostname "test"
debug 1

[productionsite]
debug 0

[staging site]

[development site]

[unittesting]
; specific config for unit testing

I've included this so you can compare it to the one used when we add the Zend_Application configuration information.

Now that we've seen how to do it with all our own code, let's repeat the entire process with Zend_Application's resource plugin system.

Using resource plugins and a config file

Another way to use Zend_Application is to use a config file to configure the bootstrapping process and point Zend_Application at it. By the way, it's not clear if the classes that we plug in to the boostrap process are called resources or plugins at the point, so I'm calling them resource plugins :)

Zend_Application comes with a number of plugins, but it's inevitable that you'll need your own. In our case, to replicate what we've done in the inline example, we're going to need a new view resource which we'll call App_Application_Resource_View as it will extend Zend_Application_Resource_View.

This resource plugin is easy to write. The system will call an init() method and you can put whatever you like there. In our case we are extending an already existing resource plugin, so we call up to the parent's init() and then do our own stuff:

// FILE: library/App/Application/Resource/View.php
<?php

class App_Application_Resource_View extends Zend_Application_Resource_View
{
    public function init()
    {
        parent::init();
        $view $this->getView();
        $view->doctype('XHTML1_STRICT');
        $view->headTitle()->setSeparator(' - ');
        Zend_Layout::startMvc(APPLICATION_PATH '/views/layouts');

    }
}

Presumably, at some point they'll be a Zend_Layout resource plugin supplied by by default, but for now, there isn't so I've started up the layout here too. It would be nice if Zend_Application_Resource_View allowed setting up the view helpers like doctype directly from the config file too.

The index.php file looks very similar to last time, except that we now tell Zend_Application about the config file:

//File: public/index.php
<?php

define('BASE_PATH'realpath(dirname(__FILE__) . '../'));
define('APPLICATION_PATH'BASE_PATH '/application');

set_include_path(BASE_PATH '/library/incubator'
    PATH_SEPARATOR .BASE_PATH '/library'
    PATH_SEPARATOR get_include_path()
);

// APPLICATION_ENVIROMENT defines which config section is loaded
if(!defined('APPLICATION_ENVIRONMENT')) {
    define('APPLICATION_ENVIRONMENT''development');
}

require_once 'Zend/Application.php';

$application = new Zend_Application(APPLICATION_ENVIRONMENT,
    APPLICATION_PATH.'/config/app.ini');
$application->bootstrap();
$application->run();

The Bootstrap class is very simple as most of the work is now done elsewhere:

//File: application/Bootstrap.php
<?php
class Bootstrap extends Zend_Application_Bootstrap_Base
{
    public function run()
    {
        Zend_Registry::set('config'$this->getOptions());
        Zend_Registry::set('env'APPLICATION_ENVIRONMENT);
        $this->frontController->dispatch();
    }
}

Note that Zend_Application doesn't store the environment string or the loaded config file to the registry and so they are unavailable to the rest of the application unless we do it ourselves. The easiest place to do it is in the run() method, but it would be nice if it did it automatically for us.

Obviously, as all our bootstrap code has disappeared, we need to tell Zend_Application what we want to do. This is done in the app.ini config file that we passed in the constructor:


//File: application/config/app.ini
[bootstrap]
; Zend_Application configuration
phpsettings.display_errors 0
phpsettings.error_reporting 8191
includepaths=
autoloadernamespaces.0 "Zend"
autoloadernamespaces.1 "App"
bootstrap.path APPLICATION_PATH"/Bootstrap_Resource.php"
pluginpaths.App_Application_Resource "App/Application/Resource"
resources.frontcontroller.controllerdirectory APPLICATION_PATH "/controllers"
resources.frontcontroller.params.0.env APPLICATION_ENVIRONMENT
resources.view =
resources.db.adapter PDO_MYSQL
resources.db.params.dbname "test"
resources.db.params.username "test"
resources.db.params.password "test"
resources.db.params.hostname "test"

[sitebootstrap]
; All the standard site-specific settings
debug 1

[productionsite]
debug 0

[staging site]

[development site]
phpsettings.display_errors 1

[unittesting]
; specific Zend_Application and other config for unit testing

Note that I'm using the PHP constants that were defined in index.php within this config file. That's part of the standard parse_ini_file() functionality, but not many people seem to take advantage of it.

Essentially, Zend_Application takes an array of options as in the second parameter of the constructor and if you don't supply one, it looks for the specific keys it uses in the config file instead. That means that in principle you don't have to use a config file, you could just pass in an array with the right information. I've not tested it though :)

The app.ini file also shows the disconnect over whether the resource plugins are called resources or plugins. We have to use the key "pluginpath" to define the directory where we store them, but use the key "resources" to configure them. It would be nice if we just called them one thing or the other. Note that we have to have a key in the resources section even if there's no parameters to set as in the case of the view resource plugin.

Note also that one phpsetting that you can't set is date.timezone as the dot is used as a separator in Zend_Config_Ini. Ideally, we'd change the separator to a "|" or something which is possible via the third parameter to Zend_Config_Ini's constructor. We can't do that though as the call to load the app.ini file is buried in Zend_Application and we can't pass any options to it. I'd guess this is by design. The easiest solution is to just call date_timezone_set() directly in your Boostrap's run() method instead.

Summary

Zend_Application has the potential to simplify the bootstrapping of an application, but, at the moment at least, you need to be aware of what it does and more importantly, what it doesn't do for you. I can't see a case where you can use it directly without either writing a few of your own _init methods or few resource plugins.

There's also the new loader which presumably will make something easier too, but that will be a separate post when I understand it and can see a use-case for it.

Finally, this should be obvious, but as the Zend_Application code is still under heavy development don't take this as gospel - by the time it is released, it could be very different!

On models in a Zend Framework application

13th December 2008

Let's talk about writing models that communicate with databases within a Zend Framework application. It's a popular topic at the moment as there's been a few threads recently on the ZF mailing lists about creating models.

The "is a" relationship

When working with models within Zend Framework, the simplest solution is to extend Zend_Db_Table_Abstract and Zend_Db_Table_Row_Abstract along these lines:


class Users extends Zend_Db_Table_Abstract
{
    protected $_name 'users';
    protected $_rowClass 'User';
    
    public function fetchAllInLastNameOrder()
    {
        return $this->fetchAll(null, array('last_name''first_name'));
    }
    
    public function fetchUserById($id)
    {
        $id = (int)$id;
        return $this->_fetchRow('id = '$id);
    }
}

class User extends Zend_Db_Table_Row_Abstract
{
    public function name()
    {
        return $this->first_name ' ' $this->last_name;
    }
}

This works fairly well for models where there is a one to one mapping to the underlying database table. It starts to falls down if a given model needs to know about two or more database tables to do its work.

The knack is to avoid using the functions defined in Zend_Db_Table_Abstract within your models or views as this will lead towards fat controllers that are too aware of the underlying database. This gets quite hard as it's much easier to start putting model logic into the controllers with this system, especially when it comes to choosing if you need to insert or update a record. A sure sign that you have this problem is that your controllers create SQL statements or Zend_Db_Table_Select objects.

To try and avoid these problems, there have been discussions on the ZF mailing lists for a little while now about using a "has a" relationship from the model object to the Zend_Db_Table object. Bill Karwin has long been a proponent of this idea and recently there's been much discussion about it. The question is: how do you implement it in practice without ending up writing lots of code for the sake of "cleanliness"?

I've started a new ZF project recently where I've started playing with these ideas and this is my first attempt at it, so I thought I'd share where I had got to in my thinking.

The "has a" relationship

When implementing this relationship we are trying to hide the fact that we are using Zend_Db_Table from the consumers of the models. The basic idea is that we have a gateway class that holds a protected $_table variable that is an instance of a Zend_Db_Table object. When fetching data, the gateway object will return either a single object or an array of objects of a type which I've not-very-imaginatively named "StandardObject".

This is a picture of the various objects and how they relate to each other:

OnModels.png

To get this working, I first mapped out how I want the objects to work via a unit test and then built the classes behind it. I wouldn't call it TDD though as I didn't write tests for all the internals as I went along. This is the basic API that I want my consumers to use:


$userGateway = new UserGateway();

// insert a row
$data = array();
$data['first_name'] = 'John';
$data['last_name'] = 'Smith';
$row $userGateway->setFromArray($data);

// update a row
$data = array();
$data['id'] = 1;
$data['first_name'] = 'Rob';
$data['last_name'] = 'Allen';
$row $userGateway->setFromArray($data);

// fetch a single user
$row $userGateway->fetchUserById(1);

// fetch all users
$rows $userGateway->fetchAllInLastNameOrder();

// delete a user
$result $userGateway->deleteById(1);

The general idea is that I can write specific functionality into the UserGateway class as I need it. Note also that the row objects returned from the gateway class methods behave a little like ValueObjects only. As they are not attached to the database though, they cannot "save" themselves. (I couldn't find the "correct" design pattern name, which probably means, that my design is wrong!) This means that all interaction with the database is via the gateway class which makes this design similar in some ways to the Table Module design pattern.

The implementation is fairly involved, so get ready for lots of code!

Firstly we need the Gateway class:


class UserGateway extends App_Model_Db_StandardGateway 
{
    protected $_tableName 'UsersTable';
    protected $_rowClass 'UserObject';
    
    public function fetchUserById($id)
    {
        $id = (int)$id;
        return $this->_fetchRow('id = '$id);
    }
    
    public function fetchAllInLastNameOrder()
    {
        $orderBy=array('last_name''first_name');
        return $this->fetchAll($orderBy);        
    }
}

The UserGateway contains the specific methods for this model and has a couple of protected variables that are used to set up the system. We also need a table class, UsersTable that inherits from App_Db_Table_Standard which inherits from Zend_Db_Table_Abstract.


class UsersTable extends App_Db_Table_Standard
{
    protected $_name 'users';
}

Obviously we are going to have many gateway models, so it makes sense that common functionality will be in a parent class, which I've called App_Model_Db_StandardGateway. This is the big class and we'll look at it in chunks:


class App_Model_Db_StandardGateway
{
    /**
     * @var App_Db_Table_Abstract
     */
    protected $_table null;
    protected $_tableName null;
    protected $_rowClass 'App_Model_StandardObject';

    public function __construct()
    {
        if (empty($this->_tableName)) {
            throw new Exception('You must specificy _tableName');
        }
        $this->_table = new $this->_tableName();
    }

Every gateway has a $_table property which is instantiated in the constructor. The name of the table class to instantiate is stored in the $_tableName property which is set in the child class. Finally, we have a $_rowClass property which will be used to hold each row object when the data is retrieved from the database. Note that it defaults to App_Model_StandardObject, but I assume that the child class will override with a specific row class as the UserGateway class does when it specifies that it wants to use the UserObject row class.

The first thing in our API is the ability to create or update database rows. I've decided on a function called setFromArray() as a foundational function to allow this. The base implementation is performs these steps:

  1. Get hold of an instance of the row class
  2. Call the row's setFromArray() method
  3. Call the row's save() method

This is implemented as:


// (part of App_Model_Db_StandardGateway)

    public function setFromArray($data)
    {
        $row $this->_fetchRowOrCreate($data);
        $row->setFromArray($data);
        $row->save();

        return new $this->_rowClass($row->toArray());
    }

The tricky bit is getting the instance of the row class (an instance of Zend_Db_Table_Row) as we need to either create a new one or retrieve it from the database. Creating a new one is easy: $row = $table->createRow(). However, retrieving a row based on the $data array is more complicated. The simplest way I could think of was to retrieve the primary key info from the table and then call find() with the correct fields from $data array. To do this, I had to extend Zend_Db_Table_Abstract to implement a getPrimary() method like this:


class App_Db_Table_Standard extends Zend_Db_Table_Abstract
{
    protected $_rowClass 'App_Db_Table_Row_Standard';
   
    public function getPrimary()
    {
        return $this->_primary;
    }

}

Note that we also define a custom row class, App_Db_Table_Row_Standard which will allow us to perform operations on insert and delete.

Back to the App_Model_Db_StandardGateway class, we can now create _fetchRowOrCreate():


// (part of App_Model_Db_StandardGateway)

    /**
     * Either retrieve a row from the DB or create a new one
     *
     * @param array $data
     * @return App_Db_Table_Row_Standard
     */
    protected function _fetchRowOrCreate($data) 
    {
        $pk $this->_table->getPrimary();
        if (!is_array($pk)) {
            $pk = (array) $pk;
        }
        
        $pkData = array();
        foreach ($pk as $fieldName) {
            if (array_key_exists($fieldName$data)) {
                $pkData[] = $data[$fieldName];
            } elseif (!empty($pkData)) {
                throw new Exception("Could not find Primary Key part '$fieldName'");
            }
        }
        
        if (empty($pkData)) {
            $row $this->_table->createRow();
        } else {
            $rowset call_user_func_array(array($this->_table 'find'), $pkData);
            if ($rowset) {
                $row $rowset->current();
            } else {
                $row $this->_table->createRow();
            }
        }
        return $row;
    }

I'm nearly sure this can be improved, but it works for both a simple autoincrement primary key like id and also for compound primary keys of multiple columns which is nice. The method makes an assumption that if you have a compound key, then the $data array will have an entry for all fields that make up the primary key. If not, an exception is thrown. We also assume that if none of the fields that make up the primary key are present in $data, then you want to create a new record.

That covers creating and updating database records from our gateway class. Deletion is a simple proxy directly to the table class:


// (part of App_Model_Db_StandardGateway)

    public function deleteById($id)
    {
        $count $this->_table->delete('id = '. (int)$id);
        return (bool) $count;
    }

so let's look at fetching data. We need two methods in the App_Model_Db_StandardGateway class: _fetchRow() and fetchAll(). These are trivial to implement as we proxy directly to the table class and then all we need to do it turn each row returned into an instance of our StandardObject class:


// (part of App_Model_Db_StandardGateway)

    protected function _fetchRow($where$order null)
    {
        $row $this->_table->fetchRow($where$order);
        if ($row) {
            $row = new $this->_rowClass($row->toArray());
        }
        return $row;
    }

    public function fetchAll($orderBy=null)
    {
        $rows null;
        if(is_null($orderBy)) {
            throw new Exception('You must choose the order of the results');
        }
        $rowset $this->_table->fetchAll(null$orderBy);
        if($rowset) {
            $rows = array();
            foreach($rowset as $row) {
                $rows[] = new $this->_rowClass($row->toArray());
            }
        }
        return $rows;
    }

The StandardObject class' full name is App_Model_Db_StandardObject and is trivial:


class App_Model_Db_StandardObject
{
    function __construct($data = array()) {
        foreach($data as $fieldName=>$value) {
            $this->$fieldName $value;
        }
    }
}

Obviously, the intention is that the model specific version, UserObject in this case, is able to do provide model specific functionality. For testing, I simply added a name() method:


class UserObject extends App_Model_Db_StandardObject  
{
    public function name()
    {
        return $this->first_name ' ' $this->last_name;
    }
}

Clearly, I could provide additional methods as required and could overload the constructor if I wanted to do a more complicated mapping from the database row array to the value object representation.

Summary

This entry has turned out much longer than I expected! This is clearly my first attempt at putting the database inside the model and hiding it from the rest of the application. Having implemented this far, I'm not sure if using Zend_Db_Table is actually useful, so my next step will be to remove it and use SQL and a Zend_Db_Adapter instead. If it turns out well, I may blog about it :)

I'm also hoping to spark more debate on the actual implementation of how to do stuff like this. I know Keith Pope is working along similar lines for the example app in his upcoming book, so if you are interested in this approach, have a look at his tactics too as they may make more sense. Matthew Weier O'Phinney has also been doing something similar in his pastebin app which is worth looking at too, though he's halfway through refactoring.

Obviously, comments and suggested improvements are welcome!

File uploads with Zend_Form_Element_File

29th November 2008

Now that Zend Framework 1.7 has been released, I thought I'd take a look at the built in file upload element, Zend_Form_Element_File, and see how it can be used. This is how to use it in its most basic form.

I decided to use the same set of form elements as before in order to make things easy.

Zend_Form_Element_File_Example.png

Let's start with the form:

The form

We extend Zend_Form and store it in the application/forms folder and so the class name is forms_UploadForm:

<?php

class forms_UploadForm extends Zend_Form
{
    public function __construct($options null)
    {
        parent::__construct($options);
        $this->setName('upload');
        $this->setAttrib('enctype''multipart/form-data');

        $description = new Zend_Form_Element_Text('description');
        $description->setLabel('Description')
                  ->setRequired(true)
                  ->addValidator('NotEmpty');

        $file = new Zend_Form_Element_File('file');
        $file->setLabel('File')
            ->setDestination(BASE_PATH '/data/uploads')
            ->setRequired(true);

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setLabel('Upload');

        $this->addElements(array($description$file$submit));

    }
}

As before, we set the name and enctype attribute of the form to allow for files to be uploaded. The form itself has two fields: a text field called 'description' and the file upload field called 'file', along with a submit button. Nothing especially complicated here.

The Zend_Form_Element_File element has a setDestination() method which is used to tell the underlying Zend_File_Transfer_Adapter_Http where we want the file that is uploaded to be stored. In this case we choose data/uploads.

The controller & view

The controller is also very standard:

<?php

class IndexController extends Zend_Controller_Action 
{
    public function indexAction() 
    {
        $this->view->headTitle('Home');
        $this->view->title 'Zend_Form_Element_File Example';
        $this->view->bodyCopy "<p>Please fill out this form.</p>";

        $form = new forms_UploadForm();

        if ($this->_request->isPost()) {
            $formData $this->_request->getPost();
            if ($form->isValid($formData)) {

                // success - do something with the uploaded file
                $uploadedData $form->getValues();
                $fullFilePath $form->file->getFileName();

                Zend_Debug::dump($uploadedData'$uploadedData');
                Zend_Debug::dump($fullFilePath'$fullFilePath');

                echo "done";
                exit;

            } else {
                $form->populate($formData);
            }
        }

        $this->view->form $form;

    }
}

The view, views/scripts/index.phtml, is trivial:


<h1><?= $this->title?></h1>
<?= $this->bodyCopy?>
<?= $this->form?>

If the form validates correctly, the $uploadedData array will contain the values of the form fields along with the filename of the file that was uploaded. Note that this filename is not fully qualified, so if you need the entire path to the file, then use the getFileName() method on the file element.

Conclusion

That's all there is to it for simple uploading of forms. There are still a few fairly important bugs in the component that we'll have to wait for 1.7.2 for. Specifically the Count validator doesn't always work as you'd expect and don't use getValues() and receive() as it isn't yet clever enough to know not to call move_uploaded_file() more than once.

As usual, here's a zip file of the project I created to test this: Zend_Form_Element_File_Example.zip (including Zend Framework (a snapshot of the release-1.7 branch) which is why it's 3.9MB large).

Hooks in Action Helpers

5th November 2008

Following on from the discussion on Zend Framework Action Helpers, let's talk about hooks within them.

Hooks are a feature of action helpers that allow you to automatically run code at certain points in the dispatch cycle. Specially, there are two hook functions available for action helpers:

  • preDispatch(): runs before the action function is called
  • postDispatch(): runs after the action function has completed

These allow you to ensure that some functionality is always run for each request. Let's consider a simple example of displaying a random quote in the footer of a website.

We start with an action helper in our controllers/helpers directory called Quote:

<?php

class Zend_Controller_Action_Helper_Quote extends Zend_Controller_Action_Helper_Abstract
{
    function preDispatch()
    {
        $view $this->getActionController()->view;
        $view->footerQuote $this->getQuote();
    }
    
    
    function getQuote()
    {
        $quotes[] = 'I want to run, I want to hide, I want to tear down the walls';
        $quotes[] = 'One man come in the name of love, One man come and go';
        return $quotes[rand(0count($quotes)-1)];
    }
}

The preDispatch() method, collects the view from the action controller and then assigns a random quote to the footerQuote property.

We need to tell the helper broker that we want this helper's hooks to be run, so in addition to the addHelperPath() call, our bootstrap requires a call to addHelper(). The bootstrap therefore has this code within it:


    // Action Helpers
    Zend_Controller_Action_HelperBroker::addPath(
        APPLICATION_PATH .'/controllers/helpers');
        
    $hooks Zend_Controller_Action_HelperBroker::getStaticHelper('Quote');
    Zend_Controller_Action_HelperBroker::addHelper($hooks);

As we used addPath() to tell the helper broker where to find the action helpers, we can use getStaticHelper() as an easy way to instantiate the class without having to require() and then call new. We can then register it with the helper broker using addHelper().

As the quote is displayed in the footer, the HTML required lives in layout.phtml:

<div id="footer">
    <div id="quote">
        <?php echo $this->footerQuote?> 
    </div> 
</div>

That's it - not too hard, is it?

Zip Files:
I've updated the example project from the last post so you can see it in context.