如何使用PHP5类创建单例类?


当前回答

我喜欢使用trait的@jose-segura方法,但不喜欢在子类上定义静态变量的需要。下面是一个解决方案,通过将实例缓存在一个静态局部变量中到按类名索引的工厂方法中来避免这种情况:

<?php
trait Singleton {

  # Single point of entry for creating a new instance. For a given
  # class always returns the same instance.
  public static function instance(){
    static $instances = array();
    $class = get_called_class();
    if( !isset($instances[$class]) ) $instances[$class] = new $class();
    return $instances[$class];
  }

  # Kill traditional methods of creating new instances
  protected function __clone() {}
  protected function __construct() {}
}

用法与@jose-segura相同,只是在子类中不需要静态变量。

其他回答

<?php
/**
 * Singleton patter in php
 **/
trait SingletonTrait {
   protected static $inst = null;

  /**
   * call this method to get instance
   **/
   public static function getInstance(){
      if (static::$inst === null){
         static::$inst = new static();
      }
      return static::$inst;
  }

  /**
   * protected to prevent clonning 
   **/
  protected function __clone(){
  }

  /**
   * protected so no one else can instance it 
   **/
  protected function __construct(){
  }
}

使用方法:

/**
 *  example of class definitions using SingletonTrait
 */
class DBFactory {
  /**
   * we are adding the trait here 
   **/
   use SingletonTrait;

  /**
   * This class will have a single db connection as an example
   **/
  protected $db;


 /**
  * as an example we will create a PDO connection
  **/
  protected function __construct(){
    $this->db = 
        new PDO('mysql:dbname=foodb;port=3305;host=127.0.0.1','foouser','foopass');
  }
}
class DBFactoryChild extends DBFactory {
  /**
   * we repeating the inst so that it will differentiate it
   * from UserFactory singleton
   **/
   protected static $inst = null;
}


/**
 * example of instanciating the classes
 */
$uf0 = DBFactoryChild::getInstance();
var_dump($uf0);
$uf1 = DBFactory::getInstance();
var_dump($uf1);
echo $uf0 === $uf1;

respose:

object(DBFactoryChild)#1 (0) {
}
object(DBFactory)#2 (0) {
}

如果你使用的是PHP 5.4: trait,它是一个选项,所以你不必为了拥有单例模式而浪费继承层次结构

还要注意你是使用trait还是扩展Singleton类 如果你不添加以下代码行,就会创建子类的单例:

   protected static $inst = null;

在儿童班

意想不到的结果将是:

object(DBFactoryChild)#1 (0) {
}
object(DBFactoryChild)#1 (0) {
}

支持多对象,每类一行:

这个方法将在你想要的任何类上强制单例,你所要做的就是向你想要创建单例的类中添加一个方法,这个方法会为你做这件事。

这也将对象存储在“SingleTonBase”类中,因此您可以通过递归SingleTonBase对象来调试您在系统中使用的所有对象。


创建一个名为SingletonBase.php的文件,并将其包含在脚本的根目录中!

代码是

abstract class SingletonBase
{
    private static $storage = array();

    public static function Singleton($class)
    {
        if(in_array($class,self::$storage))
        {
            return self::$storage[$class];
        }
        return self::$storage[$class] = new $class();
    }
    public static function storage()
    {
       return self::$storage;
    }
}

然后,对于任何你想要创建单例的类,只需添加这个小的单方法。

public static function Singleton()
{
    return SingletonBase::Singleton(get_class());
}

这里有一个小例子:

include 'libraries/SingletonBase.resource.php';

class Database
{
    //Add that singleton function.
    public static function Singleton()
    {
        return SingletonBase::Singleton(get_class());
    }

    public function run()
    {
        echo 'running...';
    }
}

$Database = Database::Singleton();

$Database->run();

你可以在任何类中添加这个单例函数,每个类只创建一个实例。

注意:你应该始终将__construct设为private以消除new Class()的使用;实例化。

不幸的是,当有多个子类时,Inwdr的答案就失效了。

下面是一个正确的可继承单例基类。

class Singleton
{
    private static $instances = array();
    protected function __construct() {}
    protected function __clone() {}
    public function __wakeup()
    {
        throw new Exception("Cannot unserialize singleton");
    }

    public static function getInstance()
    {
        $cls = get_called_class(); // late-static-bound class name
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new static;
        }
        return self::$instances[$cls];
    }
}

测试代码:

class Foo extends Singleton {}
class Bar extends Singleton {}

echo get_class(Foo::getInstance()) . "\n";
echo get_class(Bar::getInstance()) . "\n";

这篇文章涵盖了相当广泛的主题: http://www.phptherightway.com/pages/Design-Patterns.html#singleton

Note the following: The constructor __construct() is declared as protected to prevent creating a new instance outside of the class via the new operator. The magic method __clone() is declared as private to prevent cloning of an instance of the class via the clone operator. The magic method __wakeup() is declared as private to prevent unserializing of an instance of the class via the global function unserialize(). A new instance is created via late static binding in the static creation method getInstance() with the keyword static. This allows the subclassing of the class Singleton in the example.

制作单例模式的真实和现代方法是:

<?php

/**
 * Singleton Pattern.
 * 
 * Modern implementation.
 */
class Singleton
{
    /**
     * Call this method to get singleton
     */
    public static function instance()
    {
      static $instance = false;
      if( $instance === false )
      {
        // Late static binding (PHP 5.3+)
        $instance = new static();
      }

      return $instance;
    }

    /**
     * Make constructor private, so nobody can call "new Class".
     */
    private function __construct() {}

    /**
     * Make clone magic method private, so nobody can clone instance.
     */
    private function __clone() {}

    /**
     * Make sleep magic method private, so nobody can serialize instance.
     */
    private function __sleep() {}

    /**
     * Make wakeup magic method private, so nobody can unserialize instance.
     */
    private function __wakeup() {}

}

现在你可以用它。

<?php

/**
 * Database.
 *
 * Inherited from Singleton, so it's now got singleton behavior.
 */
class Database extends Singleton {

  protected $label;

  /**
   * Example of that singleton is working correctly.
   */
  public function setLabel($label)
  {
    $this->label = $label;
  }

  public function getLabel()
  {
    return $this->label;
  }

}

// create first instance
$database = Database::instance();
$database->setLabel('Abraham');
echo $database->getLabel() . PHP_EOL;

// now try to create other instance as well
$other_db = Database::instance();
echo $other_db->getLabel() . PHP_EOL; // Abraham

$other_db->setLabel('Priler');
echo $database->getLabel() . PHP_EOL; // Priler
echo $other_db->getLabel() . PHP_EOL; // Priler

正如你所看到的,这种实现更加灵活。