Yearly Archives: 2008

2008 end-of-year wrap-up

Everyone appears to do end-of-year wrap-up posts about this time of year, so I thought I would do one too…

January

We started the year with a trip to the zoo. I attempted to get a photo of a waterfall with that blurry water you see the good photographers do.

Waterfall near the seals

February

The highlight of February was talking at the UK PHP Conference in London.

Lunch!

March

A quiet month with most weekends doing something with the family. We went for a walk by the river Severn one weekend. There are many swans.

Swans (and a seagull!)

April

April saw a very nice PHPWM meet-up at a pub that served pies as well as pints. It also snowed.

Snow covered slide

May

May is the month of birthdays in our household as both the boys and myself celebrate. It also marks the start of the camping season for us.

Camping at Kingsbury Water

June

After May, June was a quiet month. Prince Charles took a ride on the Severn Valley Railway though, which I enjoyed photographing.

King Edward on the Royal Train

July

July saw sports day for our eldest son and a week's holiday camping in Norfolk towards the end of the month. I believe that that week in July was the only hot week we had all year.

The beach

August

We went away for our second week's camping and visited Legoland. The kids loved it.

Lego Models

September

A busy month with our youngest starting school. Also, I went to ZendCon!

ZCE Party at ZendCon

October

We started the move to Birmingham for the company with two members of staff there. The Wispa also returned to the shops.

Wispa is back!

November

The highlight of November was speaking at the inaugural PHPNW conference.

Jeremy's about introduce the panel...

December

The last month of the year saw both kids in school plays and most importantly, my book about Zend Framework was published!

Zend Framework in Action

Here's to 2009!

On models in a Zend Framework application

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

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

    }
}

Note that BASE_PATH is not automatically defined within Zend Framework, so you would have define it yourself within your bootstrap.

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 = "

Please fill out this form.

"; $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).

PHPNW08 Conference

PHPNW 08 took place last Saturday, and even though I wasn't too well, I had a great time.

I thought my talk went okay, and judging from the feedback, at least some people learnt something from it which is good. I hope that the tongue-in-cheek references to my book were taken in the light-hearted manner intended. The conference seemed well-attended and the organisation was top-notch. Jeremy, Jenny, Emma, Lorna and everyone else involved did a fantastic job.

At both socials, I met some excellent people who were generous enough to let me be my usual opinionated self and so I learnt as much at the socials as I did in the sessions.

One side effect of being ill is that I didn't take many photos. The few I did take are up on Flickr.

The slides for my talk are up on slideshare too:

View SlideShare presentation or Upload your own. (tags: zendframework phpnw08)

Hooks in Action Helpers

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(0, count($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.

Using Action Helpers in Zend Framework

When you have some functionality that needs to be shared across multiple controllers, one method is to use action helpers. Action helpers are very powerful and contain hooks to automatically run when you need them too, but you can ignore all that if you don't need it.

The first thing you need to do is decide where to put them. The latest default project structure document recommends using a sub folder from your controllers directory. That is: application/controllers/helpers, and it's as good a place as any.

Firstly, you need to tell the helper broker where your action helpers are. I usually do this in the bootstrap, but it could equally be done in a front controller plug-in:


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

You then need to create your action helper, we'll call it Multiples in this example and so
the filename is application/controllers/helpers/Multiples.php:

<?php

class Zend_Controller_Action_Helper_Multiples extends
Zend_Controller_Action_Helper_Abstract
{
function direct($a)
{
return $a * 2;
}
}

Note that there is a prefix to the action helper name of Zend_Controller_Action_Helper. You can change this by passing a different prefix as the second parameter to the Zend_Controller_Action_HelperBroker::addPath() call in your bootstrap.

Finally, usage within a controller action:

<?php

class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$this->view->headTitle('Home');
$this->view->title = 'Test of the Multiples action helper';

$number = 30;
$twice = $this->_helper->multiples($number);

$this->view->number = $number;
$this->view-twice = $twice;
}
}

Note that we call the action helper's name as a function of the _helper class. This maps to the direct() call within the helper.

You can put also multiple functions within the action helper:

class Zend_Controller_Action_Helper_Multiples extends
Zend_Controller_Action_Helper_Abstract
{
function direct($a)
{
return $a * 2;
}

function thrice($a)
{
return $a * 3;
}
}

To call the thrice() function within a controller action you use:

$thrice = $this->_helper->multiples->thrice($number);

As you can see if you use the action helper's name as a property of _helper, then you can call any function within the helper.

This is just a summary to get you going. Full details are in the manual.

Have fun and avoid copy and paste with your common functionality!

Zip Files:
I've created an example project so you can see it in context.

Where is php ?

A question came up on the phpwomen channel today about how to find the path to php executable for use in cron scripts. The answer that came back very quickly to use which or whereis.

It turns out that the server in question is a shared host with a control panel for access to cron and ftp to upload your files. A test proved that shell_exec() wasn't going to work too. After discussing it for a little while and trying a few combinations, we came up with putting:

echo `whereis php` > DIR/whereisphp.txt

into the cron script control panel and set it to run in 2 minutes time. Then it was just a case of looking in the file and discovering that the php binary was in /usr/local/bin.

And this post is here to save me a little bit of time when no doubt I'll run into the same problem in a year's time…

PHPNW08 schedule posted

From one conference to another one. On the 22nd November 2008, the PHPNW08 conference in Manchester takes place and I'm speaking!

My talk is entitled "First steps with Zend Framework" and it will cover an introduction to ZF and then walk through building an MVC application using the Zend Framework.

There are lots of interesting talks scheduled so far. They look aimed at a wide and I want to see every one.

If you are in the UK in November, you should come along.

ZendCon 08

I'm now back from ZendCon which was the most intense conference I've ever been too. I arrived late Saturday evening and after dropping off the stuff at my hotel, I walked the mile or so to the conference hotel to meet up with some #phpc people. I didn't stay in the Hyatt as it was too expensive for me. The walk took a little longer than I expected as I kept walking the wrong way, but I eventually worked out where I was going and it ended up being quite easy in the end. $8 for a beer at the Hyatt was a bit steep though!

On Sunday I met up with Lorna from IBuildings for breakfast and then we headed to the conference hotel to meet up with lots of people. We split into two groups with Derick, Cal, Marcus and myself heading to Napa for wine tasting and purchasing. The others headed to the mall and a theme park. Napa is the other side of San Francisco and it took a couple of hours to get there. I really enjoyed the day – especially seeing the wine cellars. We went to a Mexican restaurant on Sunday evening for food and beer.

Mike and Matthew

Monday was tutorial day and after registration I header for Matthew Weier O'Phinney and Mike Naberezny's Best Practices tutorial. This talk covered a lot of ground about stuff I mostly knew, but I picked up some hints and tips that I'll see if we can add into our workflow back in the office. After lunch, I went to Sebastian Bergmann's Quality Assurance tutorial. Sebastian is the author of PHPUnit and he showed off some of the new stuff in version 3.3 which he released just prior to the talk. Obviously, I immediately upgraded that day!

Zend held a party for ZCEers in the evening which was great fun. They had pizza and alcohol – what more could you want?! After the party, a group of us headed out to hit some bars. I couldn't tell you where we went as someone else drove! We went to a couple of different bars. In the first one, a band was playing and I borrowed Zack's SB-600 flashgun which is awesome. I have so got to get myself one of those. We then moved on to another bar where much pool was played.

Tuesday was the first day of the conference proper and was the start of an intense couple of days. Breakfast was provided from 7:30 with the first keynote starting at 8:30. The keynote, given by Harold and Andi of Zend, covered what Zend was doing in PHP including announcing a partnership with Adobe and a new version of Zend Studio. It was quite a marketing talk and so didn't seem aimed at me and I wasn't really paying that much attention. We then started the talks where I started learning stuff :) Jay Pipes of MySQL talked about SQL tuning which was very informative and I have notes to go through some of our queries and fix them! I then went to Juliette's Uncon discussion about women in IT which was interesting. I firmly believe that we'll see more women in IT when mothers start influencing their daughters in the same way that we now have many women in medicine, but didn't 30 years ago. It's quite depressing hearing about the way some women are treated in their workplace though. I'd hit the roof if such behaviour was happening in my office…

I stayed in the Uncon with Jason's Zend Framework modules talk. It was interesting to see how he went about creating a distributable module for ZF as this is an area that is not well covered. I think that there's much work to do there before we see many ZF modules though. His thoughts on Doctrine were also interesting and I should check it out at some point. Back into the main sessions, I went to Eli White's "Knight Rider" talk. This was a funny talk as he was playing Knight Rider clips from the 80s whilst talking about tools to help with software development. I didn't get much from this talk as it was too high level for me.

I then headed over for some C-fu from Sara Golemon who talked about extension writing. Although beset with technical difficulties, I learnt quite a bit in this session. It surprised me that extension writing didn't seem that hard. The final session for the day for me was Matthew's talk on Zend_Form and Dojo. I've been heads down in editing and work recently so haven't had a chance to look at the new Dojo stuff, so this was a good opportunity to catch up. Matthew's work is really impressive as he's abstracted it all away into a set of components that just work. This is especially noticeable in the form components.

After the last talk, there was drinks and snacks in the exhibition hall. I had a walk around and collected two cuddly monkeys for the kids from Remy :) I then went to dinner (at another Mexican) with the guys and gals on the ZF Certification team. This was a really nice meal and we also celebrated Ralph's birthday.

Ed talking about AIR during the Unconf

On Wednesday, the morning keynote was about Magento, so I skipped it and caught up with work email and checked in with work to ensure that all was going okay. Elizabeth Smith gave an excellent talk about all the bits in PHP 5 that we should all be using nowadays and emphasised that we should use built-in functions and extensions in preference to rolling our own code in PHP. This was one of the few talks where I took notes and need to investigate php_check_syntax(), scandir() and file_put_contents(). I then checked out Alex Russell's talk about Dojo where he explained why Dojo is the way it is as well as showing some examples of how to use it. In the questions afterwards he pointed out the Dojo's module that does what JQuery does is approximately the same size and although there's a lot in Dojo, you can pick and choose what you need and don't have to pay a big download or performance penalty for stuff you aren't using.

In the afternoon, I found myself exclusively at the Uncon talks – Keith Casey really did a good job! Ed Finkler started the afternoon talking about AIR applications. This was very interesting as I knew nothing about them and learnt quite a bit. Note to self: never use eval() in JS either! Staying in the same room, Terry Chay was next up with a talk about when to use frameworks and when not to. This was an interesting talk with the main message being to think about the consequences of your decisions as nothing is free. I then had to swap rooms to the other Uncon room to learn about PHP-GTK and using PHP on the desktop. Like AIR, This is an area that I'm not familiar, so I got a lot out of it. I also liked the less formal atmosphere in the uUncon sessions. The final session of the day was Matthew and Lorna's Uncon talk on subversion tips and tricks. Both Lorna and Matthew are good speakers and I picked up some knowledge that I'm going to need for our migration from CVS to subversion. It's interesting that most of the tips applied to any version control system and they could probably emphasise this more in future talks.

In the evening Yahoo! had a party in the Hyatt. It was slightly odd as you had to get a ticket for one drink and then had to pay for any more. The weirdest thing was that the hotel bar was cheaper! As a result, we headed downstairs to the hotel bar and Derick drank lots of Margeritas!

Thursday, the final day of the conference was a half-day. I went to a case-study about Zero 9's use of Zend Framework and Zend Platform and then went to the "What's new in PHP 5.3" talk. This was a panel discussion where the funniest bit was when Marcus Boerger stated "There is no goto" whilst passing his hand over us. In other words, the goto statement that's been added to PHP 5.3 is for specialised usage, not for general use. The final keynote was in two parts. Mark de Visser of Zend provided an overview of the conference and thanked all the participants. David Neff of the American Cancer Society then talked about how they were using PHP to build their video sharing talk. It was high level, but nevertheless very well presented and engaging.

After the conference ended, a good number of us popped over to a local In 'n' Out burger place for some food before heading back to the bar at the Hyatt to while away the afternoon. Good conversation was had during this time as everyone was relaxed and didn't need to rush off to (or give) a session. Derick, Zack and Liz even spent some time in the pool! We gave ZendCon a final send off with a meal at Morton's steak house. This was an excellent meal, if a little pricy… next time no doubt, we'll go to a Mexican!

I had a excellent time and talked to many great people. My thanks to everyone who put up with my company; I hope I didn't bore you too much.

Talking after the meal

Uninstalling MySQL on Mac OS X Leopard

To uninstall MySQL and completely remove it (including all databases) from your Mac do the following:

  • Use mysqldump to backup your databases to text files!
  • Stop the database server
  • sudo rm /usr/local/mysql
  • sudo rm -rf /usr/local/mysql*
  • sudo rm -rf /Library/StartupItems/MySQLCOM
  • sudo rm -rf /Library/PreferencePanes/My*
  • edit /etc/hostconfig and remove the line MYSQLCOM=-YES-
  • rm -rf ~/Library/PreferencePanes/My*
  • sudo rm -rf /Library/Receipts/mysql*
  • sudo rm -rf /Library/Receipts/MySQL*
  • sudo rm -rf /private/var/db/receipts/*mysql*

The last three lines are particularly important as otherwise, you can't install an older version of MySQL even though you think that you've completely deleted the newer version!