Meet the TEK·X speakers

23rd April 2010

I mentioned back in January that I'll be speaking at TEK.X in Chicago next month. Since then I've fleshed out the talks that I'll be giving and think they should be quite interesting. It turns out that Zend_Form is such a large topic that I could probably speak for 3 hours on it! As a result, I've concentrated on providing a overview of the component and how to use it followed by a look at how to change the text of error messages and how decoration works.

The main reason for this post though is that Cal Evans has been doing some 5 minute interviews with some of the speakers at TEK this year. I recommend that you have a listen and find out a little about the people who are worth paying attention to.

As you can see on the schedule, I'm speaking in some very august company in Chicago this year, so highly recommend that you badger your boss and get a ticket to be there with us.

Using PDT with Zend Framework Projects

1st April 2010

I original wrote this as a comment on Victor Nicollet's blog, but I thought I should document it here too so that I can refer other people to the information.

These are some tips and tricks when using PDT with Zend Framework that make my life easier:

Autocompletion for dynamic properties

Zend_Db_Table_Row_Abstract uses __get() and __set() magic in order to map to the underlying database table row in question. This means that you can use property autocompletion on an instance of it. You can however tell your your table class which class to use for the row objects:

class Application_Model_Users extends Zend_Db_Table_Abstract
{
    protected $_name 'users'// database table name
    protected $_rowClass 'Application_Model_User'// row class to use
}

With your row class, you can take advantage of the @property docblock element to document the fields in the class:

/**
 * @property string $user_id
 * @property string $username
 * @property string $password
 */
class Application_Model_User extends Zend_Db_Table_Row_Abstact
{
}

Autocomplete on instances of Application_Model_User will now work.

Zend_View scripts are more complicated as they use $this to access magic properties. You can do however use the @var trick at the top of the .phtml file:

$user $this->user/* @var $user Application_Model_User */
$page $this->page/* @var $user Application_Model_Page */
etc...

You can now autocomplete on $user and $page. This also has the side-effect of documenting which view properties are used in this view script.

Opening an arbitrary method

View helpers are also magic methods on Zend_View's $this within a script file. This means that you can't cmd+click( or press F3) on the view helper's method name to jump directly to the code for that method. You can however take advantage of PDT's Navigation->Open Method feature. Simply select the method name and then press shift+cmd+m. The "Open Method" dialog appears and usually the view helper is selected as the first item. Just press return to go directly to the code for that view helper.

Opening an arbitrary file

For opening up an arbitrary file that's in my project, I find that PDT's Navigation->Open Resource (shift+cmd+r) is handy. For TextMate users, this is similar to the cmd+t feature.

Opening an arbitrary class

Using Navigation->Open Type (shift+cmd+t), you can also jump to classes easily. e.g. I regularly hit shift+cmd+t followed by "*Model_" and I get a list of all my models and can jump to the one I want using the down arrow key and then return.

Finding the current open file in the project tree

Just press cmd+option+w. You have to be careful here as it's very similar keyboard shortcut to close and close all! However if you have an entity file open and you name your files like I do, then the mapper is next to it in the project list, so it's only a couple of keystrokes to open it up.

Conclusion

These tips obviously work with all frameworks and any PHP coding :) Netbeans also has similar features, so I would guess all IDEs do. If you are wedded to vim or such like, then ctags is helpful for jumping around your code.

If you have a favourite tip that I haven't mentioned, let me know in the comments!

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.

Photos of Seagulls

27th March 2010

Today was a good day in photography for me as for the first time in absolutely ages, I took some time out to take photos for the sake of taking the picture. My son was at a birthday party and I had about an hour free, so I went down by the river Severn in Worcester.

Worcester has a cathedral and I was lucky enough to have some lowish sun shining on it:

Worcester Cathedral

I don't often take photos of the cathedral as I find it hard to compose a nice picture of it. Also, it's been done to death by photographers with a better eye than me. However, I really like this one. The composition is nice and the colour of the brickwork is pleasing.

As I had a mostly empty memory card, I went down to the river to try and take a photo of a bird in flight. This isn't easy and I took just under 80 shots and had 3 in focus:

Seagull (1)

Seagull (2)

Seagull (3)

I'm pleased with all three as getting a shot of a bird in flight where the bird's eye isn't blurred takes lots and lots of practice. As I haven't practiced, clearly I got lucky! I set my D80 to use the centre auto-focus point, as it's the most sensitive in the camera, then told it to continually focus and I just pressed the button whilst trying to pan with the moving bird. It's remarkably difficult to keep the bird in the view finder, let alone keeping the focus point on the eye!

I think I did okay and I'm keen to try again sometime.

Sending a file to IE using SSL

23rd March 2010

I keep coming across this one, so I'm noting it here so I can find it again.

Internet Explorer doesn't like certain headers related to caching when you send it a file from an SSL site. The Microsoft knowledge base article, Internet Explorer is unable to open Office documents from an SSL Web site explains the problem quite well:

When you attempt to open or download a Microsoft Office document (.doc file, .xls file, .ppt file, and so on) from a secure Web site in Internet Explorer, you may receive one of the following error messages, even though the document is available and downloaded from the server

It turns out that the problem is directly due to sending these headers:


Pragmano-cache  
Cache-controlno-cache,max-age=0,must-revalidate

So, make sure you don't!

Incidentally, IE6 also gets upset if you set max-age to 0 and attachment to inline, so don't do that either!

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 :)

Mobile web monopoly

9th February 2010

There's a recent post by Peter-Paul Koch called The iPhone obsession about how Mobile Safari is being treated by web developers as the only web browser to develop for. PPK likens this to how we all used to only develop for IE6. Unfortunately, the article has lots of hyperbole and iPhone hate which significantly detracts from the actual message.

The fundamental point is that Mobile Safari is not the only web browser available on phones and if you are serious about providing mobile web to your users, then developing for the other 85% should be part of your modus operandi.

However (and there's always a however!) it's not easy. It's trivial for me to have multiple web browsers and multiple operating systems on one computer for testing. It is not trivial for me to have 10 phones with different browsers and screen sizes. We don't do enough mobile work to justify the costs.

What actually annoyed me was Rentzch's response. Rentzch started by saying that Koch's argument was "stupid/lazy/undisciplined" and then goes on to say:

I see a quiet revolution of mobile developers waiting for other phones to catch up to the iPhone.

and

I think mobile developers intuitively grasp that accommodating the insufficient software+hardware of non-iPhones out there will only prolong the status quo, extending needless suffering for developers and users alike.

That's exactly what we all said when we wanted Netscape 4 to die because IE was so much better. Rentzch has made Koch's point for for him whilst trying to disagree.

For what it's worth, I think that any mobile device manufacturer who wants web developers to develop for their device's browser, should provide hardware that doesn't need a plan. It doesn't even need to be a phone. Mobile Safari is the easiest mobile browser to test against as it's available in the iPod Touch.

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

Redirecting email whilst developing

15th January 2010

One common problem whilst developing is that you don't want to send emails out to the client (or their clients!). Ideally, we want to alter our development environment so that this doesn't happen, but still allows us to test the contents of emails that are sent by our web applications.

Windows

On Windows, the mail() function uses SMTP over port 25. Unless you've changed your php.ini file, then it will try to connect to localhost in order to send an email. On Windows VM, I use Fakemail. This is an SMTP mail server written in perl (or python) that store emails as files into a given directory. When your web application sends an email, you check in the directory and look at the files created. One top tip: alter the script to give each file a .txt extension. Then you can double click :)

Fakemail is also very useful in Linux/Mac if you are using the SMTP transport mail in Zend_Mail or Swiftmailer or whatever.

Linux / OS X

On the *nix based systems, mail() sends email using an application on the system called sendmail (or any number of compatible alternatives). By default it will call the sendmail binary, however you can change this in your php.ini with the sendmail_path setting.

I set my development boxes like this:


sendmail_path = /usr/local/bin/trapmail

Now, mail() will call my trapmail script. This script is trivial:


formail -R cc X-original-cc 
  -R to X-original-to 
  -R bcc X-original-bcc 
  --A"To: rob@akrabat.com" 
| /usr/sbin/sendmail --i

This script causes all emails to be redirect to my email address with the original to, cc and bcc fields renamed in the headers, so they can be checked!. I like this solution even better than Fakemail as it's easier to see exactly what the mail looks like in a mail client, especially for HTML format.

I just wish I had thought of this script myself! However Sean Coates came up with the idea in 2005 and I've been using it every since.

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