Tuesday, January 18, 2011

PHP factory methods hacked

I came across a question on stackoverflow a little while ago that sparked an idea (whether or not it's actually a good idea is another matter) in regards to factory design pattern methods.

The question was, basically how to get around the problem of massive switch blocks that would happen if you had a lot of options in terms of what concrete classes you could create via the factory method. The question was posed with the following snippet:

class ServiceFactory { 
    public static function getInstance($token) { 
        switch($token) {
            case 'location':
                return new StaticPageTemplateService('location');
                break;
            case 'product':
                return new DynamicPageTemplateService('product');
                break;
            case 'user'
                return new UserService();
                break;
            default:
                return new StaticPageTemplateService($token);
         }
    }
}

I thought.. What if you could have every class type created by the factory method register itself, leaving the factory method to simply supply a map of identifiers to concrete classes? Here's what I came up with:

The updated ServiceFactory class:
class ServiceFactory
{
    private static $BASE_PATH = './dirname/';
    private $m_aServices;

    function __construct()
    {
        $this->m_aServices = array();

        $h = opendir(ServiceFactory::$BASE_PATH);
        while(false !== ($file = readdir($h)))
        {
            if($file != '.' && $file != '..')
            {
                require_once(ServiceFactory::$BASE_PATH.$file);
                $class_name = substr($file, 0, strrpos($file, '.'));

                $tokens = call_user_func(array($class_name, 'get_tokens'));
                foreach($tokens as &$token)
                {
                    $this->m_aServices[$token] = $class_name;
                }
            }
        }
    }

    public function getInstance($token)
    {
        if(isset($this->m_aServices[$token]))
        {
            return new $this->m_aServices[$token]();
        }
        return null;
    }
}

interface IService
{
    public static function get_tokens();
}

And a sample concrete implementation:
class UserService implements IService
{
    function __construct()
    {
        echo '__construct()';
    }

    public static function get_tokens()
    {
        return array('user', 'some_other');
    }
}

Sure, it's a little more code in the short term and the downside is that the concrete implementations need to know what the client code should be using as an identifier to return a constructed class, but there's no need for any switch block at all :)