Как создать класс Singleton с использованием классов PHP5?
/** * Singleton class * */ final class UserFactory { /** * Call this method to get singleton * * @return UserFactory */ public static function Instance() { static $inst = null; 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;
работает.
PHP 5.3 позволяет создать наследуемый класс Singleton через позднюю статическую привязку:
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.
К сожалению , ответ Inwdr ломается, когда есть несколько подклассов.
Вот правильный наследуемый базовый класс Singleton.
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";
Вероятно, вы должны добавить частный метод __clone (), чтобы запретить клонирование экземпляра.
private function __clone() {}
Если вы не включите этот метод, получится следующее:
$inst1=UserFactory::Instance(); // to stick with the example provided above $inst2=clone $inst1;
теперь $inst1
! == $inst2
– они уже не те же самые экземпляры.
Реальный и современный способ создания Singleton Pattern:
<?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
Как вы видите, эта реализация намного более гибкая.
protected static $_instance; public static function getInstance() { if(is_null(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; }
Этот код может применяться для любого класса, не заботясь о его имени класса.
<?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;
откос:
object(DBFactoryChild)#1 (0) { } object(DBFactory)#2 (0) { }
Если вы используете PHP 5.4: укажите свой вариант, поэтому вам не нужно тратить иерархию наследования, чтобы иметь шаблон Singleton
а также обратите внимание, что если вы используете черты или расширяете Singleton class one, то свободный конец должен был создать singleton дочерних классов, если вы не добавляете следующую строку кода:
protected static $inst = null;
в дочернем классе
неожиданным результатом будет:
object(DBFactoryChild)#1 (0) { } object(DBFactoryChild)#1 (0) { }
Этот метод обеспечит соблюдение синглетов в любом классе, который вам нужен, вам нужно добавить 1 метод для класса, который вы хотите создать синглтон, и это сделает это за вас.
Это также сохраняет объекты в классе «SingleTonBase», поэтому вы можете отлаживать все свои объекты, которые вы использовали в своей системе, путем 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();
И вы можете просто добавить эту функцию singleton в любом классе, который у вас есть, и он будет создавать только один экземпляр для каждого класса.
ПРИМЕЧАНИЕ. Вы всегда должны закрывать __construct для исключения использования нового класса (); конкретизации.
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();
Вся эта сложность («поздняя статическая привязка» … харумф) является для меня просто признаком модели разбитого объекта / класса PHP. Если объекты класса были первоклассными объектами (см. Python), то «$ _instance» будет переменной экземпляра класса – членом объекта класса, в отличие от элемента / свойства его экземпляров, а также в отличие от общих его потомками. В мире Smalltalk это разница между «переменной класса» и «переменной экземпляра класса».
В PHP мне кажется, что нам нужно принять близко к сердцу руководство, что шаблоны являются руководством к написанию кода – возможно, мы подумаем о шаблоне Singleton, но пытаемся написать код, который наследуется от фактического класса «Singleton» выглядит ошибочным для PHP (хотя я предположил, что какая-то предприимчивая душа может создать подходящее ключевое слово SVN).
Я буду продолжать просто кодировать каждый синглтон отдельно, используя общий шаблон.
Обратите внимание, что я полностью остаюсь в стороне от обсуждения в одиночном одиночестве, жизнь слишком коротка.
Я знаю, что это, вероятно, вызовет ненужную пламенную войну, но я вижу, как вам может понадобиться более одного соединения с базой данных, поэтому я бы согласился с тем, что синглтон может быть не лучшим решением для этого … однако есть другие применения одноэлементного рисунка, который я считаю чрезвычайно полезным.
Вот пример: я решил свернуть свой MVC и шаблонный движок, потому что мне хотелось что-то действительно легкое. Тем не менее, данные, которые я хочу отображать, содержат множество специальных математических символов, таких как ≥ и μ, и что у вас есть … Данные хранятся как фактический символ UTF-8 в моей базе данных, а не в кодировке pre-HTML, потому что мое приложение может поставлять другие форматы, такие как PDF и CSV, в дополнение к HTML. Соответствующее место для форматирования HTML находится внутри шаблона («вид», если вы это сделаете), который отвечает за рендеринг этого раздела (фрагмента). Я хочу преобразовать их в соответствующие HTML-объекты, но функция get_html_translation_table () PHP не является очень быстрой. Имеет смысл извлекать данные один раз и хранить в виде массива, что делает его доступным для всех. Вот образец, который я сбил, чтобы проверить скорость. Предположительно, это будет работать независимо от того, были ли другие методы, которые вы используете (после получения экземпляра), были статическими или нет.
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 Время работы: 27.842966794968 секунд с использованием singleton Время работы: 237.78191494942 секунды без использования синглтона
Поэтому, хотя я, конечно, не эксперт, я не вижу более удобного и надежного способа уменьшить накладные расходы медленных вызовов для каких-то данных, делая его очень простым (одна строка кода делает то, что вам нужно). Предоставленный мой пример имеет только один полезный метод и, следовательно, не лучше, чем глобально определенная функция, но как только у вас есть два метода, вы захотите объединить их вместе, не так ли? Я так далеко от базы?
Кроме того, я предпочитаю примеры, которые на самом деле что-то делают, потому что иногда трудно представить, когда пример включает в себя такие выражения, как «// делаем что-то полезное здесь», которое я все время вижу при поиске учебников.
Во всяком случае, мне бы хотелось, чтобы любые отзывы или комментарии о том, почему использование одного синглета для такого типа вещей являются вредными (или чрезмерно сложными).
Эта статья довольно подробно освещает тему: http://www.phptherightway.com/pages/Design-Patterns.html#singleton
Обратите внимание на следующее:
- Конструктор
__construct()
объявляетсяprotected
чтобы предотвратить создание нового экземпляра вне класса черезnew
оператор.- Магический метод
__clone()
объявляется какprivate
чтобы предотвратить клонирование экземпляра класса через операторclone
.- Магический метод
__wakeup()
объявляется какprivate
чтобы предотвратить неэриализацию экземпляра класса через глобальную функциюunserialize()
.- Новый экземпляр создается с помощью поздней статической привязки в статическом методе
getInstance()
с ключевым словомstatic
. Это позволяет подклассифицироватьclass Singleton
в примере.
Вам не нужно использовать шаблон Singleton, потому что он считается антипатерном. В принципе, существует множество причин не реализовывать этот шаблон вообще. Прочтите это, чтобы начать с: Лучшая практика в классах singleton PHP .
Если в конце концов вы все еще думаете, что вам нужно использовать шаблон Singletton, тогда мы могли бы написать класс, который позволит нам получить функции singleton, расширив абстрактный класс 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();
Я согласен с первым ответом, но я также объявляю класс окончательным, так что он не может быть расширен, так как расширение SingleTon нарушает одноэлементный шаблон. Кроме того, переменная экземпляра должна быть частной, так что к ней нельзя получить доступ напрямую. Также сделайте метод __clone частным, чтобы вы не могли клонировать объект singleton.
Ниже приведен пример кода.
/** * 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(){ } }
Мне понравился метод 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
Это пример создания singleton в классе Database
шаблоны проектирования 1) singleton
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 экземпляра
Вот мой пример, который предоставляет возможность вызова как $ 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();