Multi-Instance Factories in PHP

I advocate the proper use of Singletons, but that situations where Singletons are appropriate are few and far between.

A colleague recently asked me why I advocate Singletons on my site. I explained that I advocate theĀ proper use of Singletons, but that situations where Singletons are appropriate are few and far between.

Alternatives

The first, and easiest, alternative to a Singleton implementation is documentation. Heavily document in your object where and how it should be used, and explain the dangers of multiple instantiation. This is a quick alternative, but yields absolutely zero actual protection in code.

Another alternative is to use a pseudo factory method. The object exposes a public constructor, so it can still be instantiated many times. But if, in your code you need a single, canonical instance reference you can use the class’ static [cci]::Factory()[/cci] method. Again, it works but is a bit confusing in implementation.

Named Object Factory

This is a hybrid of the factory and multiton patterns. Essentially, every object can be instantiated as many times as you want. However, there is a (separate) Factory object that can instantiate other objects for you. This factory object will keep track of the objects it has built and allows you to reference them again int he future.

class Factory {
protected static $objects = array();

/**
* Build a named multiton object and return it. Keep a reference when building
* so we can reuse it later.
*
* If you pass something like "my object" or "my-object" to $object, the Factory
* will instantiate "Class_My_Object"
*
* @param string $object Object name.
* @param string $name Optional. Name of the object.
*
* @throws InvalidArgumentException If the specified class does not exist.
*
* @returns mixed
*/
public static function build( $object, $name = 'canonical' ) {
if ( ! isset( self::$objects[ $object ] ) ) {
self::$objects[ $object ] = array();
}

$class_name = 'Class_' . ucwords( str_replace ( array( ' ', '-' ), '_', $object ) );

if ( ! class_exists( $class_name ) ) {
throw new InvalidArgumentException( 'No class exists for the "' . $object . '" object.' );
}

if ( ! isset( self::$objects[ $object ][ $name ] ) ) {
self::$objects[ $object ][ $name ] = new $class_name;
}

return self::$objects[ $object ][ $name ];
}
}

To use this, you’d call something like [cci]Factory::build( ‘my-object’ );[/cci] to instantiate the canonical [cci]Class_My_Object[/cci] object. If you want to have multiple copies around, you could use something like [cci]Factory::build( ‘dba’, ‘db1’ );[/cci] and [cci]Factory::build( ‘dba’, ‘db2’ );[/cci] to create two copies of the [cci]Class_Dba[/cci] object (for accessing two discrete databases.

The advantages of this pattern are that you can use pseudo-singletons throughout your code without actually implementing the Singleton pattern. The disadvantages are that you’re using an ambiguous static method to create you objects, so in-line autocomplete within your dev tools will struggle with typing unless you place some explicit type declarations in your documentation.