Click to See Complete Forum and Search --> : PHP5 Singleton Database


bubblenut
02-21-2005, 06:04 PM
This is a singleton database class which implements Iterator (so you can use it in a for loop) it's also implementing a workaround for PHP5's lack of method overloading so that you can set default values for the database but still retain the ability to set them at runtime.

Database is an abstract class which all database classes extend. I wasn't able to encapsulate the $instance variable in the super class because of the way the singleton method works. If anyone can think of a workaround for this I'd love to hear it as this is the one thing which I think really isn't ideal.

I've only provided one concrete class, the MySQLDatabase one but I'm sure you can figure out how different ones would be made. Another point I'm not too sure about is the implementation of the Iterator methods. It works but I'm not quite sure if I have the state checking quite right.


The attached file is a gzipped tarball with a .txt extension stuck on the end. I made it then realized that I couldn't upload it but unfortunately the call of the pub is too strong...

Shrike
02-23-2005, 07:50 AM
Very funky, with regard to the static variable - can't it just be a public static variable in the super class, referred to with parent::$variable ?

Ah just 'got' the iteration bit. That's very useful :)

bubblenut
02-24-2005, 03:57 PM
It could have been actually, yes, but then my newest addition wouldn't work because there would only ever be one database instance. There is no reason why the user should not be allowed to have one MySQLDatabase instance and one PostgreSQLDatabase instance. I didn't even think about this until this morning (completely embarrassed myself on the train when I suddenly said "Of course! It's bloody obvious!") when I was thinking about what I would do if I wanted to change the database that was being read from across the whole system. As the pattern was before I'd have had to do a search an replace for wherever MySQLDatabase was and replace it with the new one (hopefully PostreSQLDatabase when my host sorts it out). The book I'm reading at the moment (Head First Design Patterns -- v.v.good book) had mentioned something which stuck in my mind before "Encapsulate what changes", so I created another class DatabaseFactory from which the user can get an instance of the default Database class or of any other valid one.

I've just added the code for the DatabaseFactory class as the others have not changed.

<?php

static class DatabaseFactory {
public static function getDatabase($dbname='MySQLDatabase') {
//check for the existance & permissions of the class file
$classfile=CLASS_PATH.$dbname.'.class.php';
if(!file_exists($classfile) || !is_readable($classfile)) {
throw new FileNotFoundException($classfile);
}
//require it
require_once $classfile;
//check it is of the right type
if(!class_exists($dbname) || get_parent_class($dbname)!='Database') {
throw new YoungstockException('Class doesn\'t exists or is not a database class');
}
//return it's instance
//return $dbname::getInstance(); php doesn't allow this
return call_user_func(array($dbname, 'getInstance'));
}
}
?>


What do you think?

Shrike
02-25-2005, 05:50 AM
That's a good approach. Presumably you now call

DatabaseFactory::getInstance($dbtype);

rather than instantiating the MySQL class directly.

Here is the approach it took to the same problem (Database instance is held in a Singleton array, called with Repository::getInstance()). Database methods are called on a generic database class, which uses __call() to call methods on the specific database class.

class SQLQueryHandler
{
/**
* List of available database wrappers
* @var $types;
* @access private
*/
private $types = array (
"mysql" => "MySQLQueryHandler",
"pgsql" => "PostgreSQLQueryHandler",
"sqlite" => "SQLiteQueryHandler"
);

/**
* A DAO
* @var $db;
* @access private
*/
private $db;



/**
* Construct a database object
*
*/
public function __construct()
{
if(array_key_exists(Repository::getInstance("Environment")->getEnv('dbtype'), $this->types))
{
$this->db = Repository::getInstance($this->types[Repository::getInstance("Environment")->getEnv('dbtype')]);
}
else
{
throw new Exception("No class found for ".Repository::getInstance("Environment")->getEnv('dbtype'));
}
}

/**
* Overloaded call to handler methods via magic __call()
* @access public
* @param string $m
* @param array $a
* @return array
*/
function __call($m, $a)
{
return $this->db->$m($a[0]);
}
}

Here the database type is held in a config file, so I just change it there to change across the framework.

bubblenut
02-25-2005, 06:12 AM
Sweet, that looks nice! Would I be able to sneak a peek at Repository? That looks like it could be a damned usefull class.

Shrike
02-25-2005, 06:24 AM
Sure.

abstract class Repository
{

static private $repository = array();

/**
* Returns a static instance of $class
* @param string $class
* @return obj $$class
*/
final static function getInstance($class, $arg = NULL)
{
if(is_array($arg))
{
$arg = implode(",", $arg);
}
if(!isset(Repository::$repository[$class]))
{
Repository::$repository[$class] = new $class($arg);
}
return Repository::$repository[$class];
}
}

I also make use of the __autoload() function to load in classes when Repository makes an instance.

bubblenut
03-05-2005, 10:48 AM
Ah, I never replied to your post! I just wandered back into the post for a laugh and realised I never answered. That Repositroy is very cool. I've got a funny feeling I've seen it before. Did you use it, or something similar, on that app you had on dotgeek? Anyway, I'm thinking of implementing something similar in my new project but I'm going to try and incorperate the composite pattern (even though I really don't need to as I'll only have about four or five singletons in the whole app) so that they can be organized into groups better.

bubblenut
03-06-2005, 03:30 PM
I think I'm comming down with pattern fever :( :p

Do you guys think this is prehapse going a little far? I can see it being usefull for a large site with the potential for loads of configuration options but mine is only going to be pretty small. Oh well, I think I'm going to use it anyway just for the fun of it.

I've got a few ideas for extra functionality which I was hoping for some views on. A print method so you can display the current configuration options, a get iterator method for traversing the tree and a remove method.

Attached is the code for the coposite pattern classes. RepositoryComponent, Repository and RepositoryItem.

Below is an example of use:

<?php
include 'classes/autoloader.php';

//Here's the setup, this would be in a seperate configuration file included at the top
$repository = new Repository('Root', 'The main repository for all my singletons');
$databases = new Repository('Databases', 'A sub-repository to hold my database instances');
$mysql = new RepositoryItem('MySQLDatabase', 'Here\'s my MySQL connection instance');

$databases->add($mysql);
$repository->add($databases);

//Here's the usage
try {
$db=$repository->getChild('Databases')->getChild('MySQLDatabase')->getInstance();
$db->sql("SELECT * FROM test");
foreach($db as $result) {
echo("{$result['unique_id']}:{$result['thetime']}:{$result['astring']}\n");
}
} catch(Exception $e) {
print_r($e);
}
?>

Shrike
03-06-2005, 04:50 PM
I think fever is the right word. Where is the code?? :confused:

bubblenut
03-06-2005, 05:10 PM
Ooops, sorry.

Shrike
03-15-2005, 09:47 AM
Sorry I've not had a chance to look at this thorougly. One thing that struck me was the abstract classes having methods which throw Exceptions, rather than being defined as abstract methods.

Also there may be some scope for use of interfaces here, it might be worth putting all of the methods in Repository into an interface and having the Repository class just define the properties. That way if you needed to extend a component's functionality you could just write a new interface and have the concrete class implement it.

Overall this looks really good. I've not ever used the Composite pattern, it'll be good to see how this pans out.