Testable Factories

by
Annika Backstrom
in misc, on 1 January 2011. It is tagged #Programming, #PHP, and #unit testing.

So I wrote a post about testable classes in PHP, and before I even hit "Publish" I read something on Stack Overflow that challenged some of my opinions. I wrote that I didn't need testable factories, but what if I did?

For me, the main appeal of a factory is one-line instantiation of classes. This allows for clean, clear dependency injection in object instantiation, but quick access to an object without a lot of duplicated code, and also has the potential to reduce overhead when creating an interface to a data store. (In my previous example, the factory method cached a reference to the database object using a static.)

But what if the factory itself were its own class?

class UserFactory {
    public static function userstore( $new_store = null ) {
        static $userstore;

        if( $new_store !== null ) {
            $userstore = $new_store;
        }

        // default userstore if one wasn't provided
        if( $userstore === null ) {
            $userstore = new UserStore_Database( DB_USER, DB_PASS );
        }

        return $userstore;
    }

    public static function load_by_id( $id ) {
        $user = new User( self::userstore() );
        $user->load( $id );

        return $user;
    }
}

$dbstore = new UserStore_Database( DB_USER, DB_PASS );
UserFactory::userstore( $dbstore );
$user = UserFactory::load_by_id( 12 );

Or maybe it's better to have the factory as an object rather than a collection of static methods:

class UserFactory {
    public $userstore;

    public function __construct( $userstore ) {
        $this->userstore = $userstore;
    }

    public function load_by_id( $id ) {
        $user = new User( $this->userstore );
        $user->load( $id );

        return $user;
    }
}

$dbstore = new UserStore_Database( DB_USER, DB_PASS );
$uf = new UserFactory( $dbstore );
$user = $uf->load_by_id( 12 );

I'm not sure if one implementation is better than the other. The former seems testable, and it can be used in any scope without instantiation or prior setup, allowing for one-liners.