在PHP5中创建单例设计模式

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

202383 次浏览
/**
* Singleton class
*
*/
final class UserFactory
{
private static $inst = null;


// Prevent cloning and de-serializing
private function __clone(){}
private function __wakeup(){}




/**
* Call this method to get singleton
*
* @return UserFactory
*/
public static function Instance()
{
if ($inst === null) {
$inst = new UserFactory();
}
return $inst;
}
    

/**
* Private ctor so nobody else can instantiate it
*
*/
private function __construct()
{
        

}
}

使用方法:

$fact = UserFactory::Instance();
$fact2 = UserFactory::Instance();

$fact == $fact2;

但是:

$fact = new UserFactory()

抛出错误。

参见http://php.net/manual/en/language.variables.scope.php#language.variables.scope.static了解静态变量作用域,以及为什么设置static $inst = null;有效。

你可能应该添加一个私有的__clone()方法来禁止克隆实例。

private function __clone() {}

如果您不包含此方法,则可能出现以下情况

$inst1=UserFactory::Instance(); // to stick with the example provided above
$inst2=clone $inst1;

现在$inst1 !== $inst2 -它们不再是同一个实例了。

PHP 5.3允许通过后期静态绑定创建可继承的单例类:

class Singleton
{
protected static $instance = null;


protected function __construct()
{
//Thou shalt not construct that which is unconstructable!
}


protected function __clone()
{
//Me not like clones! Me smash clones!
}


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

这解决了一个问题,在PHP 5.3之前,任何扩展了Singleton的类都会生成父类的实例,而不是它自己的实例。

现在你可以做:

class Foobar extends Singleton {};
$foo = Foobar::getInstance();

$foo将是Foobar的一个实例而不是Singleton的一个实例。

支持多对象,每类一行:

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

它还将对象存储在“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()的使用;实例化。

protected  static $_instance;


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

这段代码可以应用于任何类,而无需关心类名。

class Database{


//variable to hold db connection
private $db;
//note we used static variable,beacuse an instance cannot be used to refer this
public static $instance;


//note constructor is private so that classcannot be instantiated
private function __construct(){
//code connect to database


}


//to prevent loop hole in PHP so that the class cannot be cloned
private function __clone() {}


//used static function so that, this can be called from other classes
public static function getInstance(){


if( !(self::$instance instanceof self) ){
self::$instance = new self();
}
return self::$instance;
}




public function query($sql){
//code to run the query
}


}




Access the method getInstance using
$db = Singleton::getInstance();
$db->query();

我知道这可能会引起不必要的争论,但是我知道您可能需要多个数据库连接,所以我承认单例可能不是最好的解决方案……然而,我发现单例模式还有其他非常有用的用途。

这里有一个例子:我决定滚动我自己的MVC和模板引擎,因为我想要一些真正轻量级的东西。但是,我想要显示的数据包含了很多特殊的数学字符,如≥和μ;你有什么…数据存储为实际的UTF-8字符在我的数据库中,而不是预HTML编码,因为我的应用程序可以提供其他格式,如PDF和CSV除了HTML。格式化HTML的适当位置是负责呈现该页面部分(代码片段)的模板(如果您愿意,也可以称为“视图”)内部。我想将它们转换为适当的HTML实体,但php get_html_translation_table()函数不是超级快。更好的方法是一次性检索数据并将其存储为数组,使所有人都可以使用。这是我用来测试速度的样本。据推测,无论您使用的其他方法(在获得实例之后)是否是静态的,这都可以工作。

class EncodeHTMLEntities {


private static $instance = null;//stores the instance of self
private $r = null;//array of chars elligalbe for replacement


private function __clone(){
}//disable cloning, no reason to clone


private function __construct()
{
$allEntities = get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES);
$specialEntities = get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES);
$this->r = array_diff($allEntities, $specialEntities);
}


public static function replace($string)
{
if(!(self::$instance instanceof self) ){
self::$instance = new self();
}
return strtr($string, self::$instance->r);
}
}
//test one million encodings of a string
$start = microtime(true);
for($x=0; $x<1000000; $x++){
$dump = EncodeHTMLEntities::replace("Reference method for diagnosis of CDAD, but clinical usefulness limited due to extended turnaround time (≥96 hrs)");
}
$end = microtime(true);
echo "Run time: ".($end-$start)." seconds using singleton\n";
//now repeat the same without using singleton
$start = microtime(true);
for($x=0; $x<1000000; $x++){
$allEntities = get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES);
$specialEntities = get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES);
$r = array_diff($allEntities, $specialEntities);
$dump = strtr("Reference method for diagnosis of CDAD, but clinical usefulness limited due to extended turnaround time (≥96 hrs)", $r);
}
$end = microtime(true);
echo "Run time: ".($end-$start)." seconds without using singleton";

基本上,我看到的典型结果如下:

php test.php
Run time: 27.842966794968 seconds using singleton
Run time: 237.78191494942 seconds without using singleton

因此,虽然我不是专家,但我认为没有更方便、更可靠的方法来减少对某些数据的缓慢调用的开销,同时使其超级简单(只需一行代码就可以完成所需的工作)。假设我的例子只有一个有用的方法,因此并不比一个全局定义的函数好,但是一旦你有了两个方法,你就想把它们组合在一起,对吗?我是不是太离谱了?

此外,我更喜欢真正做一些事情的例子,因为有时很难想象一个例子中包含“//在这里做一些有用的事情”这样的语句,这是我在搜索教程时经常看到的。

无论如何,我希望有任何关于为什么在这种情况下使用单例是有害的(或过于复杂)的反馈或评论。

下面是我的例子,它提供了调用as $var = new Singleton()的能力,并创建了3个变量来测试它是否创建了新对象:

class Singleton{


private static $data;


function __construct(){
if ($this::$data == null){
$this->makeSingleton();
}
echo "<br/>".$this::$data;
}


private function makeSingleton(){
$this::$data = rand(0, 100);
}


public function change($new_val){
$this::$data = $new_val;
}


public function printme(){
echo "<br/>".$this::$data;
}


}




$a = new Singleton();
$b = new Singleton();
$c = new Singleton();


$a->change(-2);
$a->printme();
$b->printme();


$d = new Singleton();
$d->printme();

不幸的是,当有多个子类时,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";
<?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: < em > < / em >特征,它是一个选项,所以你不必为了拥有< em > < / em >单例模式而浪费继承层次结构

,还要注意你是使用< em > < / em >特征还是< em >扩展单例< / em >类 如果你不添加下面的代码行

,就会创建子类的单例
   protected static $inst = null;

在儿童班

意想不到的结果将是:

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

所有这些复杂性(“后期静态绑定”……对我来说,harumph)只是PHP对象/类模型破碎的标志。如果类对象是一级对象(参见Python),则"$_instance"将是类实例变量——类对象的成员,而不是其实例的成员/属性,也与其后代共享相反。在Smalltalk世界中,这就是“类变量”和“类实例变量”之间的区别。

在PHP中,在我看来,我们似乎需要牢记模式是编写代码的指南——我们可能会考虑Singleton模板,但试图编写从实际的“Singleton”类继承的代码对PHP来说是错误的(尽管我认为一些有进取心的人可以创建一个合适的SVN关键字)。

我将继续使用共享模板分别编写每个单例。

请注意,我绝对不会参与单身是邪恶的讨论,生命太短暂了。

我同意第一个答案,但我也会将类声明为final,这样它就不能被扩展,因为扩展单例违背了单例模式。此外,实例变量应该是私有的,这样就不能直接访问它。还要将__clone方法设为私有,这样就不能克隆单例对象。

下面是一些示例代码。

/**
* Singleton class
*
*/
final class UserFactory
{
private static $_instance = null;


/**
* Private constructor
*
*/
private function __construct() {}


/**
* Private clone method
*
*/
private function __clone() {}


/**
* Call this method to get singleton
*
* @return UserFactory
*/
public static function getInstance()
{
if (self::$_instance === null) {
self::$_instance = new UserFactory();
}
return self::$_instance;
}
}

示例使用

$user_factory = UserFactory::getInstance();

这阻止你做什么(这将违反单例模式..

你不能这么做!

$user_factory = UserFactory::$_instance;


class SecondUserFactory extends UserFactory { }

这应该是Singleton的正确方式。

class Singleton {


private static $instance;
private $count = 0;


protected function __construct(){


}


public static function singleton(){


if (!isset(self::$instance)) {


self::$instance = new Singleton;


}


return self::$instance;


}


public function increment()
{
return $this->count++;
}


protected function __clone(){


}


protected function __wakeup(){


}


}

我喜欢使用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相同,只是在子类中不需要静态变量。

数据库类,检查是否有任何现有的数据库实例,它将返回先前的实例。

   class Database {
public static $instance;
public static function getInstance(){
if(!isset(Database::$instance) ) {
Database::$instance = new Database();
}
return Database::$instance;
}
private function __cunstruct() {
/* private and cant create multiple objects */
}
public function getQuery(){
return "Test Query Data";
}
}
$dbObj = Database::getInstance();
$dbObj2 = Database::getInstance();
var_dump($dbObj);
var_dump($dbObj2);




/*
After execution you will get following output:


object(Database)[1]
object(Database)[1]


*/

Ref http://www.phptechi.com/php-singleton-design-patterns-example.html

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

注意事项:

  • 构造函数__construct()被声明为protected,以防止通过new操作符在类外部创建新实例。
  • 神奇的方法__clone()被声明为private,以防止通过clone操作符克隆类的实例。
  • 神奇的方法__wakeup()被声明为private,以防止通过全局函数反序列化类的实例 李unserialize()。< / > 在静态创建方法getInstance()中使用关键字static通过后期静态绑定创建一个新实例。这 允许子类化示例中的class Singleton

这是在数据库类上创建单例的例子

< p >设计模式 1)单例< / p >
class Database{
public static $instance;
public static function getInstance(){
if(!isset(Database::$instance)){
Database::$instance=new Database();


return Database::$instance;
}


}


$db=Database::getInstance();
$db2=Database::getInstance();
$db3=Database::getInstance();


var_dump($db);
var_dump($db2);
var_dump($db3);

那么输出是

  object(Database)[1]
object(Database)[1]
object(Database)[1]

只使用单个实例,不要创建3个实例

真实和现代创建单例模式的方法是:

<?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

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

您实际上不需要使用单例模式,因为它被认为是反模式。基本上,有很多理由根本不实现这种模式。从PHP单例类的最佳实践开始阅读。

如果你仍然认为你需要使用单例模式,那么我们可以写一个类,允许我们通过扩展我们的SingletonClassVendor抽象类来获得单例功能。

这就是我解决这个问题的方法。

<?php
namespace wl;




/**
* @author DevWL
* @dosc allows only one instance for each extending class.
* it acts a litle bit as registry from the SingletonClassVendor abstract class point of view
* but it provides a valid singleton behaviour for its children classes
* Be aware, the singleton pattern is consider to be an anti-pattern
* mostly because it can be hard to debug and it comes with some limitations.
* In most cases you do not need to use singleton pattern
* so take a longer moment to think about it before you use it.
*/
abstract class SingletonClassVendor
{
/**
*  holds an single instance of the child class
*
*  @var array of objects
*/
protected static $instance = [];


/**
*  @desc provides a single slot to hold an instance interchanble between all child classes.
*  @return object
*/
public static final function getInstance(){
$class = get_called_class(); // or get_class(new static());
if(!isset(self::$instance[$class]) || !self::$instance[$class] instanceof $class){
self::$instance[$class] = new static(); // create and instance of child class which extends Singleton super class
echo "new ". $class . PHP_EOL; // remove this line after testing
return  self::$instance[$class]; // remove this line after testing
}
echo "old ". $class . PHP_EOL; // remove this line after testing
return static::$instance[$class];
}


/**
* Make constructor abstract to force protected implementation of the __constructor() method, so that nobody can call directly "new Class()".
*/
abstract protected 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() {}


}

使用的例子:

/**
* EXAMPLE
*/


/**
*  @example 1 - Database class by extending SingletonClassVendor abstract class becomes fully functional singleton
*  __constructor must be set to protected becaouse:
*   1 to allow instansiation from parent class
*   2 to prevent direct instanciation of object with "new" keword.
*   3 to meet requierments of SingletonClassVendor abstract class
*/
class Database extends SingletonClassVendor
{
public $type = "SomeClass";
protected function __construct(){
echo "DDDDDDDDD". PHP_EOL; // remove this line after testing
}
}




/**
*  @example 2 - Config ...
*/
class Config extends SingletonClassVendor
{
public $name = "Config";
protected function __construct(){
echo "CCCCCCCCCC" . PHP_EOL; // remove this line after testing
}
}

只是为了证明它能像预期的那样工作:

/**
*  TESTING
*/
$bd1 = Database::getInstance(); // new
$bd2 = Database::getInstance(); // old
$bd3 = Config::getInstance(); // new
$bd4 = Config::getInstance(); // old
$bd5 = Config::getInstance(); // old
$bd6 = Database::getInstance(); // old
$bd7 = Database::getInstance(); // old
$bd8 = Config::getInstance(); // old


echo PHP_EOL."COMPARE ALL DATABASE INSTANCES".PHP_EOL;
var_dump($bd1);
echo '$bd1 === $bd2' . ($bd1 === $bd2)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; // TRUE
echo '$bd2 === $bd6' . ($bd2 === $bd6)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; // TRUE
echo '$bd6 === $bd7' . ($bd6 === $bd7)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; // TRUE


echo PHP_EOL;


echo PHP_EOL."COMPARE ALL CONFIG INSTANCES". PHP_EOL;
var_dump($bd3);
echo '$bd3 === $bd4' . ($bd3 === $bd4)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; // TRUE
echo '$bd4 === $bd5' . ($bd4 === $bd5)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; // TRUE
echo '$bd5 === $bd8' . ($bd5 === $bd8)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; // TRUE

我很久以前就写过,想在这里分享

class SingletonDesignPattern {


//just for demo there will be only one instance
private static $instanceCount =0;


//create the private instance variable
private static $myInstance=null;


//make constructor private so no one create object using new Keyword
private function  __construct(){}


//no one clone the object
private function  __clone(){}


//avoid serialazation
public function __wakeup(){}


//ony one way to create  object
public static  function  getInstance(){


if(self::$myInstance==null){
self::$myInstance=new SingletonDesignPattern();
self::$instanceCount++;
}
return self::$myInstance;
}


public static function getInstanceCount(){
return self::$instanceCount;
}


}


//now lets play with singleton design pattern


$instance = SingletonDesignPattern::getInstance();
$instance = SingletonDesignPattern::getInstance();
$instance = SingletonDesignPattern::getInstance();
$instance = SingletonDesignPattern::getInstance();


echo "number of instances: ".SingletonDesignPattern::getInstanceCount();

简单的例子:

final class Singleton
{
private static $instance = null;


private function __construct(){}


private function __clone(){}


private function __wakeup(){}


public static function get_instance()
{
if ( static::$instance === null ) {
static::$instance = new static();
}
return static::$instance;
}
}

希望有帮助。

以上答案都是可以的,但我还会再补充一些。

无论谁在2021年来到这里,我都将展示另一个使用Singleton Pattern类作为trait并在任何类中重用此类的示例。

<?php


namespace Akash;


trait Singleton
{
/**
* Singleton Instance
*
* @var Singleton
*/
private static $instance;


/**
* Private Constructor
*
* We can't use the constructor to create an instance of the class
*
* @return void
*/
private function __construct()
{
// Don't do anything, we don't want to be initialized
}


/**
* Get the singleton instance
*
* @return Singleton
*/
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new self();
}


return self::$instance;
}


/**
* Private clone method to prevent cloning of the instance of the
* Singleton instance.
*
* @return void
*/
private function __clone()
{
// Don't do anything, we don't want to be cloned
}


/**
* Private unserialize method to prevent unserializing of the Singleton
* instance.
*
* @return void
*/
private function __wakeup()
{
// Don't do anything, we don't want to be unserialized
}
}

所以,像在任何课堂上一样轻松地使用它。假设,我们想在UserSeeder类中实现单例模式。

<?php


class UserSeeder
{
use Singleton;


/**
* Seed Users
*
* @return void
*/
public function seed()
{
echo 'Seeding...';
}
}