The Montoya Herald — ChristianMontoya.com
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
heh, I did wrote that, and it has a seriuos omission, that I btw never fixed E_TOO_BUSY
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 .
Oh wow, that is a big omission, I need to go and fix it everywhere.
i love you.christian!
I have similar negative views on singletons. Fortunately they can usually be designed out easily, so they don't come up much anymore.
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.
Have you tried to clone your object?
You'll see that PHP will clone you Singleton object… I'm having problems with this.
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->.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?
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;
}