The Montoya Herald, a weblog about Blueprint, jQuery, design, music and life, publishing on the web since September 2005. Written by Christian Montoya: developer, designer and entrepreneur.

The Montoya Herald — ChristianMontoya.com

Search

Buy My DVD!

Like What I Do?

My Amazon.com Wish List

On this domain

Elsewhere

Better object handling with PHP singletons

Posted on April 15, 2008.

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:

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

Get a trackback link

9 Comments

  1. Cristian Rodriguez on April 15, 2008

    heh, I did wrote that, and it has a seriuos omission, that I btw never fixed E_TOO_BUSY :P

    the problem is here.
    // this line split up for readability
    return (self::$xcobj instanceof XCache) ?
    self::$xcobj :
    new XCache;

    gotcha ? when self::$xcobj is not an instanceof XCache it will return you a new instance of Xcache but never assign it to self::$xcobj .

  2. Christian Montoya on April 15, 2008

    Oh wow, that is a big omission, I need to go and fix it everywhere.

  3. jamesanthony on April 26, 2008

    i love you.christian!

  4. Preston Lee on July 4, 2008

    I have similar negative views on singletons. Fortunately they can usually be designed out easily, so they don't come up much anymore.

  5. Christian Montoya on July 4, 2008

    Preston: Maybe you should read the comments your own readers have posted on your blog. You state one negative aspect of singletons and then make ridiculous exaggerations to attempt humor. There are good uses for everything, and singletons are especially useful for static, repeated uses of an object handler.

  6. Ramiro on January 15, 2009

    Have you tried to clone your object?
    You'll see that PHP will clone you Singleton object… I'm having problems with this.

  7. Christian Montoya on January 15, 2009

    Ramiro, I thought that was the whole point of the __clone() method? Also, all calls to the object are static calls; you never instantiate an object or do anything else that would involve the ->.

  8. Jonathan on June 25, 2009

    Christian,
    Could you provide an example of your mysqli class with database name variable in the constructor? I don't always connect to the same database and would like to have that as an option.

    Also, should a "function __destruct() " be called at the end of the class to close the connection?

  9. Jonathan on June 25, 2009

    This works…


    public static function getInstance($dbase='')
    {
    // this is broken up for easy reading
    if( !( self::$xdbobj instanceof mysqli) ) {
    self::$xdbobj = new mysqli(dbhostname, dbusername, dbpassword, (dbase)?$dbase:dbdatabase);
    // here I am using defined constants for all the DB creds
    }
    return self::$xdbobj;
    }

Leave a comment

Use Markdown or basic HTML. For posting code, use Postable. Please keep comments respectful and on topic.