Quickshiftin - Clever Crazy Code

PHP traits in the real world

November 29th, 2014

I started dabbling with traits in PHP back when they were under development in 2010. It’s a little surprising then that I’ve just barely come round to actually using them in a project. Seems like PHP 5.3 has been so solid there’s been little need to upgrade, now I’m making the jump from 5.3 straight to 5.5 on a lot of my servers. Anyway, now that I’m rolling out servers with PHP 5.5 traits are on the table. Rather than regurgitating some Hello World example, I’ll illustrate a practial example from my new Softwear codebase and show how traits can save us from carpel tunnel syndrome we contracted before their advent.

Emulating Multiple Inheritance in PHP

Anyone who knows PHP knows there is no direct feature for multiple inheritance in PHP. PHP seemingly took the Java notion of being a single inheritance language with interfaces. The idea then for a pseudo-multiple-inheritance architecture is to write classes that implement interface X then delegate to them to add functionality to some existing class. The forwarding code is the most painful to write. Let me show you what I’m talking about.

Image model objects that double as Imagick objects

Imagine there’s a model out there that represents records from a product_images table in the database. Naturally, these records represent an image file somewhere on the server. The ORM provides the code to handle retrieving and storing the object to the database as well as its interaction with related classes based on the database schema. I’ll walk you through the motivation for a consolidated interface as well as an implementation in pre-traits PHP.

ProductImage model class

Here’s a look at the original ProductImage class definition. It’s extending a base class provided by the ORM with all the database voodoo. Let’s suppose getFilename is one of the functions provided by the base class, that returns the filename of the image stored on disk.

class ProductImage extends BaseProductImage
{
    public function getFilename()
    {
        // Returns the filename column from the database record
        // Implemented in BaseProductImage
        // Shown here for illustration
    }
}

Thumbnailing a ProductImage

We want to keep the model class extending the base class, because that’s where all the magic is that makes it a model class. Imagine now though, we’d like to introduce some image manipulation functionality. After all, the database record is representative of an image stored on disk. We may way to operate on it, say for example to scale it to a thumbnail. Now we could easily do that all in client code by first getting the object from the database, then creating an Imagick instance out of the path to the file.

// Fecth ProductImage from database
$oProductImage =
    ProductImageQuery::create()
    ->filterByName('Softwear TShirt')
    ->findOne();

// Explicitly create Imagick instance
$oProductImagick = new Imagick($oProductImage->getFilename());

// Thumbnail the image
$oProductImagick->thumbnailImage(200, 200);

That’s not bad for one-off code, but what if we intend to use numerous methods from the Imagick class with our ProductImage objects or instantiating an Imagick instance against ProductImage objects throughout client code? It would be a lot smoother if the ProductImage objects could walk and talk like Imagicks! Since ProductImage needs to extend BaseProductImage from the ORM, we can go the route of the interface.

Treating ProductImage instances like Imagick instances

interface SwImagick
{
    public function thumbnailImage($columns, $rows, $bestfit=false, $fill=false);
}

Now that we have an interface we implement it on the ProductImage class.

class ProductImage extends BaseProductImage implements SwImagick
{
    private $_oImagick;

    public function getImagick()
    {
        if($this->_oImagick === null)
            $this->_oImagick = new Imagick($oProductImage->getFilename());
        return $this->_oImagick;
    }

    public function thumbnailImage($columns, $rows, $bestfit=false, $fill=false)
    {
        return $this->getImagick()->thumbnailImage(200, 200);
    }
}

Once that’s done, our client code becomes more elegant.

// Fetch ProductImage from database
$oProductImage =
    ProductImageQuery::create()
    ->filterByName('Softwear TShirt')
    ->findOne();

// Now thumbnail image is a method of ProductImage!
$oProductImage->thumbnailImage(200, 200);

As we move on to more more model classes that want to behave like Imagicks, we’ll need to break out the code that is common to all ‘image models’ and in doing so you’ll see how much boilerplate code we need to write to get a strongly typed system with multiple behaviors in the absence of traits.