views:

816

answers:

6

If you had a factory class that creates new objects of some kind, and that factroy class is a singleton, like this:

class Database_Factory extends Base_Factory {
    private static $factory;
    private $objects = array();

    public function __get($profile) {
        // check for object and return it if it's created before
    }

    public static function getInstance(){
        if (!self::$factory)
            self::$factory = new self();
        return self::$factory;
    }
}

The same code repeats anytime where some object needs it's own factory. So i decided to make this factory class abstract and implement only specific routines for each factory. But PHP does not allow to instantiate abstract class.

abstract class Base_Factory {
    public static function getInstance(){
        if (!self::$factory)
            self::$factory = new self();
        return self::$factory;
    }
}

Fatal error: Cannot instantiate abstract class Base_Factory

What would you do?

A: 

Well you could do a check to make sure the class calling the function isn't the Base_Factory.

if(__CLASS__!='Base_Factory')

Then use $this instead of self to refer to the current object instead of the current class.

if (!$this->factory)
        $this->factory = new self();
    return $this->factory;
Shane
Static functions have no `$this`. Futhermore, `__CLASS__` always represents the class where a function is defined, in your case, it will always be 'Base_Factory', even in derived classes.
Ferdinand Beyer
Ah sorry your right, you can't even use get_class($this) as an alternative.
Shane
A: 

By definition abstract classess cannot be instantiated in PHP like any other object oriented languages. So your Base_Factory should be interface instead of abstract class.

From the PHP manual: "It is not allowed to create an instance of a class that has been defined as abstract."

eyazici
Mark seems to know that he cannot instantiate his abstract base class. Hew wants to dynamically instantiate derived, non-abstract classes from code defined in the abstract base class.
Ferdinand Beyer
Yes Ferdinand, that's precise.
Deniss Kozlovs
+1  A: 

PHP >= 5.3 only

abstract class Base_Factory {
    protected static $factory;
    public static function getInstance(){
        if (!self::$factory) {
            $class = get_called_class();
            self::$factory = new $class();
        }
        return self::$factory;
    }
}
Lukman
+4  A: 

In PHP methods, self always refers to the class where the method is defined. Since version 5.3.0, PHP supports “late static binding”, where you can use the static keyword to access overridden static methods, as well as the function get_called_class() to get the name of the derived class in static context.

However, your design has a major flaw: The static property $factory defined in Base_Factory is shared across all derived classes. Therefore, the first time a singleton is created and stored in this property, all other calls to getInstance() will return the same object, no matter what derived class is used.

You could use a static dictionary mapping class names to singleton objects:

abstract class Base_Factory {
    private $_instances = array();
    public static function getInstance() {
        $class = get_called_class();
        if (!isset(self::$_instances[$class])) {
            self::$_instances[$class] = new $class();
        }
        return self::$_instances[$class];
    }
}

Oh, one more thing: The fact that you are looking for a possibility to re-use code for singleton objects could be a cue to the fact that you are over-using the singleton design pattern! Ask yourself if the classes you are planning to implement as singletons really are singletons and if there will be no use case where you might want to have multiple instances of the particular class.

Often it is much better to use just one singleton representing the current “application context” that provides accessors for objects that are singletons with respect to this context.

Ferdinand Beyer
Thanks, but unfortunatelly i don't have PHP >= 5.3 on my shared hosting at the moment. But i'll keep this code for the times i will :).
Deniss Kozlovs
+1  A: 

Register your singletons in a simple class like this

class Singletons {
    static private $singleton = array();
    public function getSingleton($class) {
    if (!isset(self::$singleton[$class])) {
        self::$singleton[$class] = new $class;
    }
    return self::$singleton[$class];
    }
}

Then do this

class aSingleton {
    public $i;
    public function test() {
    ++$this->i;
    echo get_class() . " called {$this->i} times\n";
    }
}

Singletons::getSingleton('aSingleton')->test();
Singletons::getSingleton('aSingleton')->test();

Output

aSingleton called 1 times
aSingleton called 2 times
Peter Lindqvist
May be you will have to have a static member variable that tracks the single instance of aSingleton.
CodeMedic
+1  A: 

PHP 5.3+

abstract class Singleton
{
    /**
     * Instance
     *
     * @var Singleton
     */
    protected static $_instance;

    /**
     * Constructor
     *
     * @return void
     */
    protected function __construct() {}

    /**
     * Get instance
     *
     * @return Singleton
     */
    public final static function getInstance() {
        if (null === static::$_instance) {
            static::$_instance = new static();
        }

        return static::$_instance;
    }
}
Sergey