Christian Montoya

Better object handling with PHP singletons

Recently I was researching local caching options for PHP when I came across the XCache var cache — a file-based caching interface that comes with the XCache opcode cacher (and only requires a small change in the PHP config). While looking for example code to use in my project, I came across an OOP API wrapper that used a design pattern I hadn't seen before in PHP: the singleton. I'm going to repost the code that sparked my interest in using singletons consistently, which even includes examples for usage:

/**
 * XCache
 *
 * @package XCache
 * @version $Id$
 * @copyright 2007
 * @author Cristian Rodriguez <judas.iscariote@flyspray.org>
 * @license BSD {@link http://www.opensource.org/licenses/bsd-license.php}
 */
 
class XCache {
 
    private static $xcobj;
 
    private function __construct()
    {
    }
 
    public final function __clone()
    {
        throw new BadMethodCallException("Clone is not allowed");
    } 
 
    /**
     * getInstance 
     * 
     * @static
     * @access public
     * @return object XCache instance
     */
    public static function getInstance() 
    {
        if( !(self::$xcobj instanceof XCache) ) {
          self::$xcobj = new XCache();
        }
        return self::$xcobj;
    }
 
    /**
     * __set 
     * 
     * @param mixed $name 
     * @param mixed $value 
     * @access public
     * @return void
     */
    public function __set($name, $value)
    {
        xcache_set($name, $value);
    }
 
    /**
     * __get 
     * 
     * @param mixed $name 
     * @access public
     * @return void
     */
    public function __get($name)
    {
        return xcache_get($name);
    }
 
    /**
     * __isset 
     * 
     * @param mixed $name 
     * @access public
     * @return bool
     */
    public function __isset($name)
    {
        return xcache_isset($name);
    }
 
    /**
     * __unset 
     * 
     * @param mixed $name 
     * @access public
     * @return void
     */
    public function __unset($name)
    {
        xcache_unset($name);
    }
}
 
//example use...
 
$cache = XCache::getInstance();
$cache->foo = PHP_INT_MAX;
$cache->bar = range('a', 'z');
var_dump($cache->foo);
var_dump($cache->bar);
unset($cache->bar);
var_dump(isset($cache->bar));

If all this looks daunting to you, let's consider what developers usually use: global variables. One might do:

$cache = new XCache;

in a file like cache.setup.php and include that in the rest of their files. Then, in a function, one might do:

function get_from_cache($key) {
  global $cache;
 
  return $cache->$key;
}

This might seem simple enough, but there are a couple problems with this approach:

  • Even in scripts that don't need to connect to the cache / create an instance of the connection, the connection still occurs. In a high level application with a lot of scripts being run, it's important to avoid unused connection overhead.
  • Every function or method has to call the global variable with "global $var," though one could argue this isn't much different from calling a singleton object.

The important point is the first one; I'm always developing for speed in my day-to-day work, and I'd like to put all my PHP scripts together into a coherent application without having to think about which pages of an application should be connecting to the database/memcache/xcache and which ones shouldn't. I would like to have the connection interfaces available without having to worry about the connection overhead unless I absolutely need to make those connections. And I never, ever, want to connect to any of these services more than once in a page. I want to use the same connection from right when I first need to open it to the end.

Singletons make all of that easy. That's why I wrote my own for mysqli and Memcache:

/**
 * XDB
 *
 * @package XDB
 * @version $Id$
 * @copyright 2008
 * @author Christian Montoya <montoya.christian@gmail.com>
 * @license BSD {@link http://www.opensource.org/licenses/bsd-license.php}
 */
 
class XDB {
 
    private static $xdbobj;
 
    private function __construct()
    {
    }
 
    public final function __clone()
    {
        throw new BadMethodCallException("Clone is not allowed");
    } 
 
    /**
     * getInstance 
     * 
     * @static
     * @access public
     * @return object mysqli instance
     */
    public static function getInstance() 
    {
        // this is broken up for easy reading
        if( !( self::$xdbobj instanceof mysqli) ) {
          self::$xdbobj = new mysqli(DB_IP, DB_USER, DB_PASS, DB_NAME);
          // here I am using defined constants for all the DB creds
        }
        return self::$xdbobj;
    }
 
}
 
// Examples
$DB = XDB::getInstance();
$select = $DB->prepare("SELECT * FROM blog");
// ...
$DB = XDB::getInstance(); 
// still the same, single mysqli connection
/**
 * XMem
 *
 * @package XMem
 * @version $Id$
 * @copyright 2008
 * @author Christian Montoya <montoya.christian@gmail.com>
 * @license BSD {@link http://www.opensource.org/licenses/bsd-license.php}
 */
 
class XMem {
 
    private static $xmemobj;
 
    private function __construct()
    {
    }
 
    public final function __clone()
    {
        throw new BadMethodCallException("Clone is not allowed");
    } 
 
    /**
     * getInstance 
     * 
     * @static
     * @access public
     * @return object Memcache instance
     */
    public static function getInstance() 
    {
        if( !(self::$xmemobj instanceof Memcache) ) {
          self::$xmemobj = new Memcache();
          self::$xmemobj->pconnect(MEMCACHE_IP, MEMCACHE_PORT);
        }
        return self::$xmemobj;
    }
 
}
 
// Examples
$Mem = XMem::getInstance();
$Mem->get('key1234');
$Mem->set('key5678', 'saved', 0, 3600);

Remember, these singletons will return a new instance (connection) the first time they are called in a script execution, and everytime thereafter they will return the same instance that was already opened. If they are never called, a connection will never be opened, so there are no wasted connections.

In my opinion, this is way better than depending on global variables. If you are interested in using this, feel free to take the code here. Just remember to remove the examples :)

Updated 4-15-2008 with fixed code, thanks Cristian Rodriguez

Thank you for reading • Published on April 15th, 2008 • Please take a moment to share this with your friends