Приглашаем посетить
Орловка (orlovka.niv.ru)

Hack 77. Create Constant Objects with Singletons

Previous
Table of Contents
Next

Hack 77. Create Constant Objects with Singletons

Hack 77. Create Constant Objects with Singletons Hack 77. Create Constant Objects with Singletons

Use the Singleton pattern to create objects that exist as a solitary object in the system.

Of all of the patterns detailed in the Gang of Four's Design Patterns, none seems to have been used as often as the Singleton pattern, probably in part because it's so easy to implement. Besides, who can beat coding up a singleton and saying, "There can be only one!" Or maybe that's just a Highlander thing.

A singleton is an object that can have only one instance at any given time in the system. A great example of a potential singleton is a database handle. For each instance of the PHP interpreter, there should be only one database handle. This hack implements just such a setup, a singleton version of a database handle.

Figure 7-11 shows the UML for the database handle singleton (I told you it was simple!).

Figure 7-11. The database singleton
Hack 77. Create Constant Objects with Singletons


Not much to look at, really. The object contains the database handle and has two methods. The first is the constructor, which is private to ensure that nobody outside of the class can create the object. It also contains a static method called get_handle that returns the database handle.

7.12.1. The Code

Save the code in Example 7-15 as singleton1.php.

Example 7-15. A database wrapper singleton
	<?php
	require( 'DB.php' );

	class Database
	{
	  private $dbh;

	  private function Database()
	  {
	    $dsn = 'mysql://root:password@localhost/test';
		$this->dbh =& DB::Connect( $dsn, array() );
		if (PEAR::isError($this->dbh)) { die($this->dbh->getMessage()); }
	  }

	  public static function get_handle()
	  {
		static $db = null;
		if ( !isset($db) ) $db = new Database();
		return $db->dbh;
	  }
    }

	echo( Database::get_handle()."\n" );
	echo( Database::get_handle()."\n" );
	echo( Database::get_handle()."\n" );
	?>

This simple singleton has a constructor that logs into the database and one static accessor that creates an object if one hasn't been created already and returns the database handle from that object. If you use this method to get to database handles, you can rest assured that you will connect to the database only once per page fetch.

7.12.2. Running the Hack

You run this script on the command line using the PHP command-line interpreter:

	% php singleton1.php
	Object id #2
	Object id #2
	Object id #2

This demonstrates that the multiple calls to the get_handle() static method are returning the same object time and time again. This means that each time the call was made, the same Database object, and thus the same database handle, was used.

7.12.3. Hacking the Hack

Database handles are one thing, but what about something more complex? Let's try a shared list of states, as shown in Example 7-16.

Example 7-16. A singleton array of states
	<?php
	class StateList
	{
	  private $states = array();
	  private function StateList()
	  {
	  }

	  public function addState( $state )
	  {
	    $this->states []= $state;
	  }
	  
	  public function getStates()
	  {
	    return $this->states;
	  }

	  public static function instance()
	  {
	    static $states = null;
		if ( !isset($states) ) $states = new StateList();
		return $states;
	  }
	}

	StateList::instance()->addState( "Florida" );
	var_dump( StateList::instance()->getStates( ) );

	StateList::instance()->addState( "Kentucky" );
	var_dump( StateList::instance()->getStates( ) );
	?>

This code creates a singleton class, StateList, which contains a list of states. You can add states to the list, as well as getting a listing of the states. To access the single shared instance of this object, you have to use the static instance( ) method (instead of creating an instance directly).

To run this script, use the command-line interpreter:

	% php singleton2.php
	array(1) {
	  [0]=>
	  string(7) "Florida"
	}
	array(2) {
	  [0]=>
	  string(7) "Florida"
	  [1]=>
	  string(8) "Kentucky"
	}

The first dump shows that just the first state, Florida, is in the list. The second dump shows the addition of Kentucky to the shared object.

I am a little hesitant to recommend the Singleton pattern too highly. In my experience, it's often overused. In fact, I've seen code that has some rather ugly workarounds to deal with singleton objects; more often than not, these reflect that the Singleton pattern is being used incorrectly. If you're taking significant steps to work with a singleton, it might not be an appropriate use of the pattern.


Previous
Table of Contents
Next