Introduction
Design patterns are well-tested solutions to common problems in software development. In the famous the gang of four book, 23 design patterns were created and they have been widely used in Object-Oriented Programming, e.g. Java and C#. How about PHP? Since PHP 5.0, it has become a full Object-Oriented language, and some design patterns are also widely used in PHP and it numerous MVC frameworks. Today let’s implement the most-common Singleton Pattern in PHP, using a step by step and problem-solving approach.
Scenario
In PHP development (especially using MVC frameworks), we often need to include/require a DB class, a Upload class or a Cookie class, just as below:
<?php
require 'DB.class.php';
require 'Upload.class.php';
require 'Cookie.class.php';
but we need to make sure there is ONLY ONE instance of that DB or Cookie class, as one instance is enough and more will be problematic. This is when Singleton Pattern comes into the picture. Simply put, Singleton Pattern means that if a programmer has already instantiated an object of a certain class, another programmer cannot instantiate a second one. Now let’s see how to do it from scratch.
Implementation
Step One: Create a common class.
We just create a class named Singleton, and now we can instantiate any number of objects we want. Here we can create two objects using the class Singleton:
class Singleton {
}
$s1 = new Singleton();
$s2 = new Singleton();
Are $s1 and $s2 the same object? Of course not, and we can tell by using a simple if() statement:
class Singleton {
}
$s1 = new Singleton();
$s2 = new Singleton();
if ($s1 === $s2) {
echo 's1 and s2 are the same object';
} else {
echo 's1 and s2 are NOT the same object';
}
Note: when judging whether two objects are the same one, we must use “===”, not “==”.
Step Two: Forbid the “new” operation
If we want to prevent the class users from creating any number of Singleton class object that they want, how to do? Well, you might think that objects are created by the constructor of the class. So how about we conceal the constructor within the class itself so the outside could not use it, like below code does:
class Singleton2 {
protected function __construct() {
}
}
$s3 = new Singleton2();
Now you can see that we make the constructor protected, but the new problem is: no one can create an object anymore.
Step Three: Create an object-creation method
In order to let class users create an object, we need to leave an object-creation ‘interface’, so we add a new method called getIns(), and it is public(open to the outside) and static(so it can be called using class name):
class Singleton3 {
public static function getIns() {
return new self();
}
protected function __construct() {
}
}
We just create a method within the classs. And within getIns(), we create an object of the class itself and then we return it. Now we test again:
class Singleton3 {
public static function getIns() {
return new self();
}
protected function __construct() {
}
}
$s4 = Singleton3::getIns();
$s5 = Singleton3::getIns();
if ($s4 === $s5) {
echo 's4 and s5 are the same object';
} else {
echo 's4 and s5 are NOT the same object';
}
The result is:
s4 and s5 are NOT the same object
These two objects are still not the same object, why? Because Singleton3::getIns() is called twice and thus two objects are created. But now, the control is in our hands and we can just improve the getIns() to make it do what we want.
Step Four: Improve the getIns() method
Now we can add come checking in getIns():
class Singleton4 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
protected function __construct() {
}
}
$s6 = Singleton4::getIns();
$s7 = Singleton4::getIns();
if ($s6 === $s7) {
echo 's6 and s7 are the same object';
} else {
echo 's6 and s7 are NOT the same object';
}
You can see that now we store the instance of the class in a protected property called $ins. Then when getIns() is called, we have some checking going on here: if $ins is NULL, then we instantiate a class object. And at last, we return self::$ins.
Let’s test it:
s6 and s7 are the same object
Now we got the result we want! There is only one instance of the class now. But it is still now enough, why? If there is another class called Multi and it inherits our original class:
class Singleton4 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
protected function __construct() {
}
}
class Multi extends Singleton4 {
public function __construct() {
}
}
$s6 = Singleton4::getIns();
$s7 = Singleton4::getIns();
if ($s6 === $s7) {
echo 's6 and s7 are the same object';
} else {
echo 's6 and s7 are NOT the same object';
}
echo '<br>';
$s8 = new Multi();
$s9 = new Multi();
if ($s8 === $s9) {
echo 's8 and s9 are the same object';
} else {
echo 's8 and s9 are NOT the same object';
}
In this subclass, the visibility of the parent constructor is changed to public, and now what happens:
s6 and s7 are the same object
s8 and s9 are NOT the same object
This single line of code in the subclass:
public function __construct() {
}
destroyed everything we have done so far! So we need to prevent our subclass from changing visibility of parent class constructor.
Step Five: Prevent subclass overriding parent class constructor
Now we need to add the keyword final in front of our parent constructor:
class Singleton5 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
final protected function __construct() {
}
}
So now if we run the script again:
class Singleton5 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
final protected function __construct() {
}
}
We see the output:
Fatal error: Cannot override final method Singleton5::__construct()
Are we done now? Still not yet…If someone code:
class Singleton5 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
final protected function __construct() {
}
}
$s10 = Singleton5::getIns();
$s11 = clone $s10;
if ($s10 === $s11) {
echo 's10 and s11 are the same object';
} else {
echo 's10 and s11 are NOT the same object';
}
Result:
s10 and s11 are NOT the same object
This is because the clone keyword is used here. So the problem occurs again. How to prevent clone?
Step Six: Forbid __clone() magic function
class Singleton6 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
final protected function __construct() {
}
final protected function __clone(){
}
}
$s10 = Singleton6::getIns();
$s11 = clone $s10;
Now clone is not possible.
Finally, we have completed our Singleton Pattern in PHP!
I hope this step by step approach to attain singleton could be helpful for you. Knowing a design pattern is good, but I guess if we know why we need to create such a pattern and how to iteratively improve our code to achieve our goal is more important and useful.