PapayaSoft - Phuket web development

Refactoring Video Renderers With Design Patterns and Autoloaders

| Comments

On the face of it, it’s a very simple request from a customer.

We have a page listing our YouTube-hosted videos. Each video has a thumb and some descriptive information, all linked to a page that renders the descriptive info and the YouTube embed codes so the visitor can view the video in our site wrap. Customer wants to add a new video to the list.

Simple. Should be nothing. Piece of cake.

But it turns out that existing rendering/embedding code was set up to expect videos with only a YouTube embedding. The new video is hosted at another site which does their embeds in slightly different way. And there is every reason to think that the next video that comes up will be different still.

Further, the supporting code for summary and detail renderings (summary rendering: display a thumb, some video metadata, and links to play, download, etc; detail rendering: display the actual embed from the video provider, some video metadata, links to download, etc) was not as well structured as I would have liked. Although there was a summary rendering class and a detail rendering class and both descended from a common base class to share some common functionality, the commonality was all based on the YouTube embedding. Also, the caller instantiated the renderers directly using the new operator, rather than using a factory design pattern.

So, what it seems was needed is something that puts all the commonality (title, speaker, place, date, thumbnail, general layout of the summary pages and the detail page) in a single place; allows enough flexibility to deal with embeddings from various sources; and uses established design patterns for easy use and maintenance.

So, I refactored all our code into a more modern approach that uses a factory pattern to create renderers that are capable of dealing with the type of video embed required. Start with a parent class for renderers Customer_Video_Renderer containing much of the functionality that would be common across various renderers and video embedding types. In particular, it defines the primary interface for using the renderers:

class Customer_Video_Renderer 
{

    public function renderSummary()
    {
        // to implement
    }
    
    public function renderDetail()
    {
        // to implement
    }
    
    // other supporting code common to renderers
}

A generalized renderer for an embedded video now uses dependency injection as follows:

class Customer_Video_Renderer_Embed extends Customer_Video_Renderer
{
    protected $_embedClassName;
    
    public function __construct($video, $embedClassName = '')
    {
        parent::__construct($video);
        $this->_embedClassName = $embedClassName;
    }
}

The variable $embedClassName variable contains the name of a class that must support the static function getEmbedCode($video). So for example:

class Customer_Video_Renderer_Embed_YouTube 
{
    public static function getEmbedCode($video)
    {

        $w = $video['speech_video_width'];
        $h = $video['speech_video_height'];
        $u = self::_getEmbedUrl($video);

        return <<< EOT

        <object width="$w" height="$h">
            <param name="movie" value="$u"></param>
            <param name="allowFullScreen" value="true"></param>
            <param name="allowscriptaccess" value="always"></param>
            <embed 
                src="$u" 
                type="application/x-shockwave-flash" 
                allowscriptaccess="always" 
                allowfullscreen="true" 
                width="$w" 
                height="$h">
            </embed>
        </object>     
EOT;

    }

    protected static function _getEmbedUrl($video)
    {
        return 'http://www.youtube.com/v/'.$video['speech_embed_code'].'&hl=en&fs=1';
    }
}

Then we use a factory to create renderers:

class Customer_Video_Renderer_Factory 
{

    public function create(array $video)
    {
    
        if (!isset($video['vType'])){ 
            throw new Customer_Video_Renderer_Exception('Video does not specify video type');
        }
        
        switch ($video['vType']) {
            case 'youtube':
                $renderer = new Customer_Video_Renderer_Embed($video, 'Customer_Video_Renderer_Embed_YouTube');
                break;
            case 'moneywatch':
                $renderer = new Customer_Video_Renderer_Embed($video, 'Customer_Video_Renderer_Embed_MoneyWatch');
                break;
            case 'inline':
                $renderer = new Customer_Video_Renderer_Embed($video, 'Customer_Video_Renderer_Embed_Inline');
                break;
            default:
                throw new Customer_Video_Renderer_Exception('Video specifies unsupported video type: '.$video['vType']);
                break;
        }
        
        return $renderer;
    }
}

So the caller simply renders a video as follows:

// given a $video
echo Customer_Video_Renderer_Factory::create($video)->renderDetail();
    

And I am – finally! – using a standard __autoload function so all those irritating requires statements are no longer, well, required:

function __autoload($className){
    include_once str_replace('_', '/', $className) . '.php';
}

Can’t believe how smoothly it all went, how beautiful the code is now structured (though I see that there could be different ways of approaching it), and how easy it will be to add new video providers. Might even add unit-tests if I can find the time.

So pleased.

Update 2009-11-07: I realize now that the use of static methods was kind of a n00bish idea, that considerations of unit testing and loose-coupling should have dictated a more compositional approach, perhaps invoking one of the standard design patterns that applies. All I can say is that I’m moving that way, slowly but surely. Onward!

Comments