New Zend_Auth tutorial

26th July 2010

After too many months of neglect, I have completely rewritten my Zend_Auth tutorial so that it is compatible with Zend Framework 1.10!

As an experiment, I have written it directly in HTML, rather than PDF as before and cover the login form along with the login controller code required to authenticate a user using a database table. For good measure, I've included logging out and a view helper to show how to access the logged in user's details.

The full source code is also available, if you don't want to type it in :)

I hope you find it useful.

Akrabat_Db_Schema_Manager: table prefix support

20th June 2010

I've updated Akrabat_Db_Schema_Manager so that it now supports table prefixes.

It uses the application.ini key of resources.db.table_prefix as I couldn't think of a better one :) and then uses that for the schema_version table's name and also makes it available in your change objects.

For example, if application.ini contains resources.db.table_prefix = "myapp", then the manager will create the table myapp_schema_version to store the current version of the schema. In your change classes, you can then do this:

001-Users.php:


 class Users extends Akrabat_Db_Schema_AbstractChange 
 {
     function up()
     {
         $tableName $this->_tablePrefix 'users';
         $sql "
             CREATE TABLE IF NOT EXISTS $tableName (
               id int(11) NOT NULL AUTO_INCREMENT,
               username varchar(50) NOT NULL,
               password varchar(75) NOT NULL,
               role varchar(200) NOT NULL DEFAULT 'user',
               PRIMARY KEY (id)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
         $this->_db->query($sql);

         $data = array();
         $data['username'] = 'admin';
         $data['password'] = sha1('password');
         $data['role'] = 'admin';
         $this->_db->insert($tableName$data);
     }

     function down()
     {
         $tableName $this->_tablePrefix 'users';
         $sql"DROP TABLE IF EXISTS $tableName";
         $this->_db->query($sql);
     }

 }

which will create a table called myapp_users. Note that you are responsible for using the prefix property as the change classes cannot enforce what you do within the up() and down() methods. It also follows that you'll have to ensure that your models also use the correct prefix.

I have also made a change to the provider (Akrabat_Tool_DatabaseSchemaProvider) so that it loads the correct application.ini file based on the data in the project's profile. This shouldn't affect anyone using Akrabat_Db_Schema_Manager, except that we no longer define APPLICATION_ENV and APPLICATION_PATH for you.

Enjoy!

Community Review Team for Zend Framework

9th June 2010

On the ZF mailing lists, there's been a discussion on creating a community team with a follow-up by Matthew.

I was going to write up a little about it as I'm one of the volunteers on the team. However, Pádraic beat me to it and I don't think I could have written it any better, so go and read his write-up instead!

The CR Team at the moment is:

  • Rob Allen (Akrabat)
  • Pádraic Brady (PadraicB)
  • Steven Brown
  • Shaun Farrell (farrelley)
  • Pieter Kokx (kokx)
  • Dolf Schimmel (Freeaqingme)
  • Ben Scholzen (DASPRiD)

(alphabetical order has always suited me!)

We all accept email and can be found on irc in #zftalk.dev (freenode). Most are on Twitter too. Feel free to contact any of us about anything to do with contributing to Zend Framework and we'll find someone to help you!

Akrabat_Db_Schema_Manager: Zend Framework database migrations

29th March 2010

So, it turns out that I had a need for database migrations within a Zend Framework project that's using Zend_Db_Adapter.

Those of you with long memories may remember that I created a proposal along these lines back in 2006. Nearly four years later, I read it again and implemented the core section. One of the comments, by Wil, asked why it's worth bothering with a DDL to create tables, columns, indexes etc. as sooner or later you'll hit a wall and need to use SQL anyway. He has a point. My original idea was that as I routinely use MySQL and MS SQL Server, it would be handy to have cross-database code. Thinking about it however, there's not that much that isn't cross-database and I could just as easily write the specific SQL for those situations.

If you remove the DDL bit, then the rest is quite easy, so I wrote it and put it up on github into a project I've egotistically called Akrabat.

Migration files

It is intended that any time you want to make a change to your database schema (add columns, tables, indexes, etc), then you create a new migration file. A migration file is stored in ./scripts/migrations and is named like this 001-CreateUsersTable.php. The number defines the order that the migrations should be performed as any given migration will make assumptions about the state of the database before it is run.

The migration file contains a class that extends Akrabat_Db_Schema_AbstractChange and must contain two methods: up() and down(). It follows that up() is called when implementing the changes in this migration and down() is called to put the database back where it was if the change is backed out.

001-CreateUsersTable.php:

class CreateUsersTable extends Akrabat_Db_Schema_AbstractChange 
{
    function up()
    {
        $sql "CREATE TABLE IF NOT EXISTS users (
                  id int(11) NOT NULL AUTO_INCREMENT,
                  username varchar(50) NOT NULL,
                  password varchar(75) NOT NULL,
                  roles varchar(200) NOT NULL DEFAULT 'user',
                  PRIMARY KEY (id)
                )";
        $this->_db->query($sql);
    
        $data = array();
        $data['username'] = 'admin';
        $data['password'] = sha1('password');
        $data['roles'] = 'user,admin';
        $this->_db->insert('users'$data);
        
    }
    
    function down()
    {
        $sql "DROP TABLE IF EXISTS users";
        $this->_db->query($sql);
    }
}

As you can see, this code will create a database table called users and then insert the first user. The backing out code (down()) just drops the table. As you can see, easy enough and uses Zend_Db_Adater so the database calls should be familiar.

For this to work, we need to store the current version of the schema within the database. This is done using a table called schema_version which contains just one column, version, and one row holding the current version number. Akrabat_Db_Schema_Manager will create this table if it does not exist.

Akrabat_Db_Schema_Manager

This is the class that does the work. It's based on the skeleton on the proposal and the code is here. There are two public methods in Akrabat_Db_Schema_Manager :

  • getCurrentSchemaVersion()
  • updateTo($version)<

In order to operate, Akrabat_Db_Schema_Manager needs the directory holding the migration files and a database adapter, so these are passed in via the constructor. The operation of updateTo() is fairly simple:

  1. Find and order migration files from current version to target version
  2. Iterate over migration files and call up() or down() as appropriate
  3. Update the current version number in the database

Obviously, getCurrentSchemaVersion() simply queries the database for the current version number.

Command line

To actually run the schema manager, we hook into Zend_Tool so we can use the zf command line tool. This means that we need a provider, Akrabat_Tool_DatabaseSchemaProvider. A provider is used by Zend_Tool to provide functionality for zf. We create a public method for each operation we want to be available at the command line:

  • updateTo($version, $env='development', $dir='./scripts/migrations')
  • update($env='development', $dir='./scripts/migrations')
  • current($env='development', $dir='./scripts/migrations')

The update() method is simply an alias to updateTo() that ensures that you update to the latest migration script that you have.

They are used like this:

$ zf current database-schema
Current schema version is 0

$ zf update database-schema
Schema updated to version 2

$ zf current database-schema
Current schema version is 2

The update-to operation allows you to roll-back also:

$ zf current database-schema
Current schema version is 2

$ zf update-to database-schema 1
Schema updated to version 1

$ zf current database-schema
Current schema version is 1

It turns out that adding a new provider to Zend_Tool requires a little bit of work. Ralph Schindler is the man who writes it all and Cal Evans is the ZF command line guru that writes it up in a way I can understand. A quick search of his blog helped me work this out. Thanks Cal!

These are the steps that you need to do:

  1. At the command line, run zf --setup storage-directory
    This creates a folder for storing Zend_Tool configuration files. Make a note of which directory it has created.
  2. Run zf --setup config-file
    This creates zf.ini in your storage directory.
  3. Edit zf.ini and change the php.include_path path so that it includes the full path to Akrabat/library in addition to the path to Zend Framework.For my installation, I have placed both Zend Framework and the Akrabat library in /usr/local/include, so I have:
    php.include_path "/usr/local/include/zend-framework/library:/usr/local/include/Akrabat/library/"
    
  4. Finally, we need to tell Zend_Tool about our new provider. This is done using the basicloader.classes key in zf.ini. So add:
    basicloader.classes.0 "Akrabat_Tool_DatabaseSchemaProvider"
    

Once you've done that (and installed Akrabat into /usr/local/include or wherever), then you should now be able to run $ zf ? database-schema and get a result:
Screen shot 2010-03-28 at 11.18.13.png
Everything is now in place. All you need to do is create a script/migrations directory and populate it with migration scripts and you're good to go!

If you come across any bugs, please report any bugs you find (preferably with the fix!)

Update: Table prefixes are now supported.

Zend Framework, IIS and 500 errors

4th March 2010

One of the dangers of frameworks in general is that you forget that they do lots of handy things for you.

Consider Zend Framework's default error controller:


   public function errorAction()
    {
        $errors $this->_getParam('error_handler');
        
        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            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;
        }
        
        // Log exception, if logger available
        if ($log $this->getLog()) {
            $log->crit($this->view->message$errors->exception);
        }
        
        // conditionally display exceptions
        if ($this->getInvokeArg('displayExceptions') == true) {
            $this->view->exception $errors->exception;
        }
        
        $this->view->request   $errors->request;
    }

The error handler in ZF will catch any exceptions and route them to the error action in the error controller. This then sets the correct HTTP response code, logs the error and optionally displays it if a config setting is set.

Obviously on our production boxes, we don't display the exceptions, but we do on our local development machines.

IIS has the concept of custom error pages that it displays when the app returns a 4xx or 5xx status code:
Screen shot 2010-03-04 at 10.07.41.png

There is also some settings for this page:
Screen shot 2010-03-04 at 10.28.06.png

By default this is set so that if you access the site using the localhost domain, then you'll get the ZF error page and if you access the site remotely then you'll get the IIS custom page and won't see the error.

If, like me, you are developing with your IIS in a VM and using the host OS's browser and developer tools, then you need to change the setting to "Detailed":

detailed-iis-errors.png

Now you can see your exceptions and it doesn't look like PHP has crashed :)

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 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.