namespace Ando\Core;

class Exception extends \Exception
{}

abstract class Singleton
{
    /**
     * Is the repository for instances of subclasses
     *
     * @var array
     */
    static private $_instances = array();
    
    /**
     * 1: Disables the 'new' operation
     * 2: Calls Singleton::_construct() with all the arguments passed to
     * Singleton::instance(), except the first one, which is the class name
     * 3: Creates a shortcut for accessing the instance by the class name
     *
     * @param array $args
     */
    private function __construct( $args )
    {
        $init = array($this, '_construct');
        call_user_func_array( $init, $args );
        
        $class = get_class($this);
        preg_match('/^(\\\\?(?:[^\\\\]+\\\\)*)([^\\\\]+)$/', $class, $matches);
        $namespace = $matches[1];
        $function  = $matches[2];
        $shortcut = "
if (! function_exists('$function'))
{
    function $function()
    {
        return Ando\Core\Singleton::instance('$class');
    }
}
";
        eval($shortcut);
    }
    
    /**
     * Disables the 'clone' operation
     */
    private function __clone()
    {}
    
    /**
     * 1: Initializes the instance
     * 2: Gets called once and automatically by Singleton::instance()
     * 3: Receives all the arguments passed to Singleton::instance(), except the
     * first one, which is the class name
     */
    protected function _construct()
    {}
    
    /**
     * Returns true if there is an instance of the $class in the repository
     *
     * @param string $class
     * @return boolean
     */
    static public function instanceAvailable( $class )
    {
        return isset( self::$_instances[ $class ] );
    }
    
    /**
     * 1: Returns the instance of the $class
     * 2: Creates, initializes and checks the instance the first time it gets
     * called with a given $class name
     *
     * @param string $class
     * @return Singleton
     */
    static public function instance( $class )
    {
        if (! self::instanceAvailable( $class ))
        {
            $args = array_slice( func_get_args(), 1 );
            $instance = new $class( $args );
            if ($instance instanceof self)
            {
                self::$_instances[ $class ] = $instance;
            }
            else
            {
                throw new Exception('Expected a subclass of Ando\Core\Singleton');
            }
        }
        return self::$_instances[ $class ];
    }
}

Example

require_once "singleton.php";

/**
 * Basic
 */
class C extends Ando\Core\Singleton {};

/**
 * Value
 */
class A extends Ando\Core\Singleton
{
	protected $val = null;
	
	public function set($val)
    {
        $this->val = $val;
    }
    
    public function get()
	{
		return $this->val;
	}
	
	protected function _construct( $a )
    {
        $this->set($a);
    }
}

/**
 * Extended Value
 */
class AA extends A
{
    protected function _construct( $a, $b )
    {
        $this->set(C()->hello . ($a + $b->get()));
    }
}



//$test = new A();
//Fatal error: Call to private Ando\Core\Singleton::__construct() from invalid context...

use Ando\Core\Singleton;

Singleton::instance('C')->hello = 'world';
echo "<p>Now C holds " . C()->hello . "</p>";

Singleton::instance('A', 3);
echo "<p>Now A holds " . A()->get() . "</p>";

Singleton::instance('A', 5);
Singleton::instance('AA', 7, A());
echo "<p>Now AA holds " . AA()->get() . "</p>";

A()->set(5);
echo "<p>Now A holds " . A()->get() . "</p>";
AA()->set(5);
echo "<p>Now AA holds " . AA()->get() . "</p>";
$result = AA() === A() ? 'Yes' : 'No';
echo "<p>AA() is A() ? $result</p>";

$result = Singleton::instance('A') === A() ? 'Yes' : 'No';
echo "<p>Singleton::instance('A') is A() ? $result</p>";


class B
{
    protected $val = 0;
    public function __construct( $b )
    {
        $this->b = $b;
    }
    
    public function get()
    {
        return $this->b;
    }
}

//Singleton::instance('B', 1);
//Fatal error: Uncaught exception 'Ando\Core\Exception' with message 'Expected a subclass...

Results

Now C holds world

Now A holds 3

Now AA holds world10

Now A holds 5

Now AA holds 5

AA() is A() ? No

Singleton::instance('A') is A() ? Yes