Zend Framework Tutorial for ZF 1.10

7th February 2010

Zend Framework 1.10, was released a week or so ago.

As a result, I have updated my Zend Framework tutorial so that it is completely current. The main change I made was to remove the _init methods in the Bootstrap as they are no longer needed. I also take advantage of the new features of the zf tool to enable layouts and create forms. It's a shame that it gets the class name of the form wrong though!

Screen short of Zend Framework tutorial

Determining if a ZF view helper exists

12th January 2010

This is another one of those posts that exists as a record for me so I can find it again if i need it!

If you need to know whether a view helper exists before you call it, one way is to write a simple view helper to tell you:


class App_View_Helper_HelperExists extends Zend_View_Helper_Abstract
{
    function helperExists($name) {
        return (bool)$this->view->getPluginLoader('helper')->load($namefalse);
    }
}

You can then use it in a view scripts like this:

<?php if ($this->helperExists('doMagic')) { echo $this->doMagic(); } ?>

As Jeremy mentions in the comments, this code came out of a discussion on the PHPNW IRC channel #phpnw

Custom Zend_Application Resources

11th January 2010

Sooner or later, you want to leverage Zend_Application better by creating your own resource plugins. This lets you reuse your initialisation work in multiple application that much easier and keeps your Boostrap class that much shorter!

In my case, I wanted to create a resource for CouchDb that checked that the database was created and if not, create it.

Creating your own plugin is easy enough. The obvious place is library/App/Application/Resource and a typical resource would look like this:


class App_Application_Resource_Couchdb extends Zend_Application_Resource_ResourceAbstract
{
    /**
     * Defined by Zend_Application_Resource_Resource
     *
     * @return Phly_Couch|null
     */
    public function init()
    {
         // do stuff here to init CouchDB
        $options $this->getOptions(); 
        // $options contains everything under 'resources.couchdb' in application.ini
    }
}

You then need to tell Zend_Application about your new plugins. This is done with this line in application.ini:


pluginPaths.App_Application_Resource_ "App/Application/Resource"

You can now have as many resource plugins as you like within the App_Application_Resource_ class-space.

Also, Matthew Weier O'Phinney has also written an article on Zend_Application which you should read too.

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'; 
        }
        $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 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.

Three years of my Zend Framework Tutorial

16th August 2009

Three years ago today, I published my first Getting Started with Zend Framework tutorial. This was the announcement. Back then, Zend Framework was at version 0.1.5 and a considerably smaller download than now :)

Three years later and I haven't lost my enthusiasm for Zend Framework as you can tell since the latest version of the tutorial supports ZF 1.8 and 1.9 and uses the new features like Zend_Application and the command line Zend_Tool scripts. And I wrote a Zend Framework book!

I wonder what will happen in the next three years?!

My tutorial is compatible with Zend Framework 1.9

5th August 2009

I've just updated my tutorial to version 1.6.3 after checking that it is still compatible with version 1.9 of Zend Framework.

The only changes I had to make were:

  • ZF 1.9 comes with its own BaseUrl view helper, so there's no need to write our own.
  • ZF 1.9.0's command line tool doesn't work on Windows. I've created patches on issues ZF-7464 and ZF-7465. I'm sure this will be sorted with 1.9.1 though.

ZF 1.9 looks like a really solid release too.

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.