The singleton pattern applies to the situation where you need a single, global instance of a class. It fits situations where a factory object returns uniform objects, such as file handles or user sessions. These aren’t good candidates for being implemented in a PHP script because the language provides built-in solutions such as persistent database connections.
1) The fact that it’s a global instance though should warn you that a Singleton should be used wisely (for essentially the same reasons as global variables). You have been warned.
The easiest way to understand a singleton pattern is by example. But first we have to overcome a small problem in PHP - the lack of static class variables (something that’s coming in the Zend 2 engine. The good news is PHP functions can have static variables, like so;
// A generic function to create and fetch static objects function staticInstance($class) { // Declare a static variable to hold the object instance static $instance; // If the instance is not there, create one if(!isset($instance)) { $instance =& new $class; } return($instance); }
We can now call that function from anywhere (assuming it’s included somewhere) and fetch the static instance of whatever class we gave it.
Note: There are alternatives - try PHP Static Class Variables
Now we’ve overcome that problem, let’s put the Singleton pattern to good use. In PHP and DOM: The Way of the Widget we looked at building XHTML “widgets” with the DOM extension. One thing you may have noticed in that article is we had to pass each widget an instance of the DOM API we’d constructed elsewhere, which can become cumbersome.
As such, DOM makes a prime candidate for a Singleton. To use it this way we need a function like;
// A function to create static instances of the DOM API
function staticDom($type=null,$source=null) {
// Declare a static variable to hold the dom object
static $dom;
// If the instance is not there, create one
if(!isset($dom)) {
// Deal with the possible ways DOM can be constructed
switch ( $type ) {
case "file":
$dom=domxml_open_file($source); // $source: path to file
break;
case "mem":
$dom=domxml_open_mem($source); // $sounce: XML as string
break;
default:
$dom=domxml_new_doc('1.0'); // create a new one
break;
}
}
return($dom);
}
OK - now let’s re-create the LinkWidget class from the PHP and DOM article, this time with a better design;
<?php /** * Creates an XHTML link tag */ class LinkWidget { /** * Private * $dom an instance of the DOM API */ var $dom; /** * Private * $out whether to return a DOM object or an XML string */ var $out; /** * Private * $link the link XML object */ var $link; //! A constructor. /** * Constucts a new LinkWidget object * @param $out switch between XML as string or DOM object */ function LinkWidget ($out='string') { $this->dom=& staticDom(); // Construct DOM from static instance $this->out=$out; $this->createLink(); } //! An accessor /** * Create an <a /> tag * @return void */ function createLink () { $this->link=$this->dom->create_element('a'); } //! An accessor /** * Adds an XML attribute e.g. href="http://www.php.net" * @return void */ function addAttribute($name,$value) { $this->link->set_attribute($name,$value); } //! An accessor /** * Creates the text for the link * @return void */ function addText($text) { $text=$this->dom->create_text_node($text); $this->link->append_child($text); } //! An accessor /** * Returns either XML as a string or a DOM object * @return mixed */ function &fetch () { if ( $this->out=='string') { return $this->dom->dump_node ($this->link); } else { return $this->link; } } } ?>
Notice this line in the constructor?
$this->dom=& staticDom(); // Construct DOM from static instance
To see this in action, let’s compare calling the LinkWidget in two ways.
<?php
require_once('lib/static.php');
require_once('lib/LinkWidget.php');
$lw=new LinkWidget();
$lw->addAttribute('href','http://www.php.net');
$lw->addAttribute('style','{background-color: yellow; color: navy}');
$lw->addText('Powered by PHP');
echo ($lw->fetch());
?>
In the above code, the LinkWidget is the cause for the DOM object to be created - no need to instaniate it from our calling script.
The result?
<a href="http://www.php.net" style="{background-color: yellow; color: navy}">Powered by PHP</a>
Now how about this;
<?php
require_once('lib/static.php');
require_once('lib/LinkWidget.php');
$dom=& staticDom();
$lw=new LinkWidget('DOM object');
$lw->addAttribute('href','http://www.php.net');
$lw->addAttribute('style','{background-color: yellow; color: navy}');
$lw->addText('Powered by PHP');
$dom->append_child($lw->fetch());
echo ($dom->dump_mem());
?>
This time we created the DOM object before we instantiated the widget, giving us the following output;
<?xml version="1.0"?>
<a href="http://www.php.net" style="{background-color: yellow; color: navy}">Powered by PHP</a>
Again - use with caution...
Update
[20 August 2003]
For an example of implementing a Singleton in PHP5, try here