Jump to content

Singleton pattern for database connection


keeps21

Recommended Posts

Just a quick query for you:

 

If you have the following code, which is a singleton to create a database connection. Where does/could this code get the database connection details from?

 

<?php
class Database 
{ 
    // Store the single instance of Database 
    private static $m_pInstance; 

    private function __construct() { ... } 

    public static function getInstance() 
    { 
        if (!self::$m_pInstance) 
        { 
            self::$m_pInstance = new Database(); 
        } 

        return self::$m_pInstance; 
    } 

     // Prevent singleton from being cloned
     private function __clone() {
     }
}  
?>

 

Cheers

Link to comment
Share on other sites

Oh, by the way, a DB layer shouldn't be a singleton.  What if you need to connect to two DBs?

 

I disagree with this on a certain level.  My database classes kinda a singleton but not really.  I do store an instance of the database object(since i generally only connect to one database server), however, the constructor is public so if you needed to connect to a seperate database server, you can.

Link to comment
Share on other sites

Then it's not a Singleton. I also don't think that's the best solution for allowing multiple connections. Better would be a type safe registry.

well i did say is was kinda a singleton but not really.  As what if you need to connect to two different database servers, how can you do that without multiple connections?

Link to comment
Share on other sites

What I'm saying is that if you want the global/easy access that comes with a Singleton, but not the property of ensuring a single instance, there are better ways to do that.

 

My suggestion would be a specialized Singleton Registry (i.e. DbhRegistry for objects of type Dbh, etc).

Link to comment
Share on other sites

Storing an instance != singleton.  My DB class does the same thing, and it's definitely not a singleton.  I guess returning a cached instance is an attribute of a singleton, but in a singleton, the same cached instance is always returned, so I guess  a registry is just a dynamic singleton in a sense.....  Hrmmm..... lol

 

 

448191 do you mean like:

 

 

$db = DhbRegistry::GetDb('db1');

 

 

Where GetDb will spawn the instance if it doesn't exist, and then it will return a reference to the instance?  Just wondering because that's what my DB layer does, and I've been wondering if there's a better way.

 

 

Damn just realized I mildly thread hijacked....  Atleast it's semi-ontopic.

Link to comment
Share on other sites

@corbin

 

Yes, something like that. Advantage over a generic Registry instance is that the return type is known. That at least addresses one of the negative properties of a Singleton Registry.

 

Though I wouldn't create the objects on demand by the Registry. At some point, somewhere in your app, you're going to have to provide the details for connecting/constructing. Set the Registry instance then. If you prefer, you could mend a Factory to do do that.

 

Use a Registry that is sensitive to the difference between 'add' and 'set'. Or perhaps 'set' shouldn't be supported at all (force 'remove' + 'add'). Invoking 'add' with a used key should throw an exception.

 

@keeps21

 

Check out the 'tutlet' on the main site: http://www.phpfreaks.com/tutorial/design-patterns---singleton-and-singleton-registry

 

It was written by myself, so I may sound pretentious when I say it is better than the link you gave, but I do believe it is.

 

 

Link to comment
Share on other sites

Here's another tip: try a Virtual Proxy for a database handler class. Here's an example evolving around the DSN:

 

abstract class Dbh
{
    private $_dsn;
        
    public function __construct($dsn)
    {
        $this->_dsn = $dsn;
    }
    
    public function getDsn()
    {
        return $this->_dsn;
    }
    
    public static function factory($dsn)
    {        
        /**
         * Parse DSN
         */
        $impName = substr($dsn, 0, strpos($dsn, ':'));
        
        $className = ucfirst($impName) . 'Dbh';
        
        $self = new $className($dsn);
        
        /**
         * Replace in Registry to prevent unnessary repeated delegation
         */
        DbhRegistry::set($self);
        
        return $self;
    }
    
    public abstract function query($sql);
    
}
class MySqlDbh extends Dbh
{    
    public function query($sql)
    {
        //execute query
    }
}
class DbhVProxy extends Dbh
{
    private $_dbh;
    
    public function query($sql)
    {
        return $this->_getDbh()->query($sql);
    }

    private function _getDbh()
    {
        if($this->_dbh === null)
        {
            $this->_dbh = Dbh::factory($this->getDsn());
        }
        return $this->_dbh;
    }
}

class RegistryException extends Exception {}

abstract class SingletonRegistry
{
    private $_map;
    
final protected function __construct()
{}
    
protected function doGet($key)
{
	return $this->_map[$key];
}

protected function doAdd($key, $object)
{
    if(isset($this->_map[$key]))
    {
        throw new RegistryException("Key already used");
    }
	return $this->_map[$key] = $object;
}

protected function doRemove($key)
{
    if(!isset($this->_map[$key]))
    {
        throw new RegistryException("Key not found");
    }
	unset($this->_map[$key]);
}

final private function __clone()
{}
    	
}
class DbhRegistry extends SingletonRegistry
{	
private static $_instance;

private $_tTable = array();

public static function getInstance()
{
	if(self::$_instance === null)
	{
		self::$_instance = new self();
	}
	return self::$_instance;
}

public static function setTranslationTable(array $tTable)
{
    self::getInstance()->_tTable = $tTable;
}

public static function get($key)
{
    $dsn = self::getInstance()->_getDsn($key);
    
    if(!$dbh = self::getInstance()->doGet($dsn))
    {
        $dbh = new DbhVProxy($dsn);
        
        self::add($dbh);
    }
    return $dbh;
}	

public static function set(Dbh $dbh)
{
    try {
        self::add($dbh);    
    }
    catch (RegistryException $e)
    {
        self::getInstance()->doRemove($dbh->getDsn());
        
        self::add($dbh);
    }
    
}

public static function remove($key)
{
    $dsn = self::getInstance()->_getDsn($key);
     
        return self::getInstance()->doRemove($dsn);
}

public static function add(Dbh $dbh)
{
    return self::getInstance()->doAdd($dbh->getDsn(), $dbh);
}

private function _getDsn($key)
{
    return isset($this->_tTable[$key]) ? $this->_tTable[$key] : null;
}

}

$config = array(
"somepurpose" => "mysql://user:password@myserver/mydatabase",
    "someotherpurpose" => "mysql://user:password@myotherserver/myotherdatabase"
);

DbhRegistry::setTranslationTable($config);

//..//

$dbh = DbhRegistry::get("somepurpose");

var_dump($dbh);

$dbh->query("SELECT foo FROM bar");

var_dump($dbh);

var_dump(DbhRegistry::get("somepurpose"));

$dbh = DbhRegistry::get("someotherpurpose");

var_dump($dbh);

$dbh->query("SELECT foo FROM bar");

var_dump($dbh);

var_dump(DbhRegistry::get("someotherpurpose"));

var_dump(DbhRegistry::getInstance());

 

Until you actually use the database handler, the Registry will just return an empty shell. You can provide a translation table at startup, decoupling purpose from the actual DSN. If you look at the result of the dumps at bottom the inner workings should become clear to you.

 

I believe this also addresses the OP's concern of configuration.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.