« XDebug Slides – OSCON 2009 | Where Multiple Levels Of Inheritance Will Kill You » |
One of the biggest challenges in OOP programming with PHP is the ability to pass around objects and let other objects use them. This challenge can be solved with careful design, however. Here we will discuss the registry pattern, not a member of the GoF’s original patterns but still an important pattern nonetheless.
Take the following code example:
<?php class DatabaseObj { protected $_resource; public function __construct($user, $pass, $db) { // Connect to the database. $this->_resource = $returnedResource; } public function getData($string) { // Query the database return $resultResource; } public function clean($var) { // Do cleaning here. return $varClean; } } class Authenticate { protected $dbObj; public function checkCredentials($user, $pass) { $userClean = $dbObj->clean($user); $passClean = $dbObj->clean($pass); return $dbObj->getData('SELECT * FROM users WHERE user = "' . $userClean . '" AND pass = MD5("' . $passClean . '")'); } }
This is fairly straight-forward, right? Well, no. We want to use our defined database object. However, we have to supply that object to Authenticate in order to use it (and avoid getting an error). There are a few ways to do this. We could do this:
<?php class Authenticate { protected $dbObj; public function __construct($user, $pass, $db) { $this->dbObj = new DatabaseObj($user, $pass, $db); } public function checkCredentials($user, $pass) { $userClean = $this->dbObj->clean($user); $passClean = $this->dbObj->clean($pass); return $this->dbObj->getData('SELECT * FROM users WHERE user = "' . $userClean . '" AND pass = MD5("' . $passClean . '")'); } } ?>
This would certainly give us a database object to use. However, if we have six or seven objects in our script, creating a new database object each time means that you’d have six or seven MySQL connections by the end of the script, which is not optimal! Also, if you ever change the signature of the constructor in the database object, you have to change it everywhere else that instantiates the object, which could be every single object. This is a problem.
We could also turn the database class into a singleton, and make our code look like this:
<?php class DatabaseObj { protected static $thisObj = false; protected $_resource; protected $user = 'me'; protected $pass = 'P@ssw0rd!'; protected $db = 'mydb'; protected function __construct($user = null, $pass = null, $db = null) { // Connect to the database. $this->_resource = $returnedResource; } public function getData($string) { // Query the database return $resultResource; } public function clean($var) { // Do cleaning here. return $varClean; } public static function getConnection($user = null, $pass = null, $db = null) { if((self::$thisObj instanceof DatabaseObj)) return self::$thisObj; self::$thisObj = new DatabaseObj($user, $pass, $db); return self::$thisObj; } } class Authenticate { protected $dbObj; public function __construct() { $this->dbObj = DatabaseObj::getConnection(); } public function checkCredentials($user, $pass) { $userClean = $this->dbObj->clean($user); $passClean = $this->dbObj->clean($pass); return $this->dbObj->getData('SELECT * FROM users WHERE user = "' . $userClean . '" AND pass = MD5("' . $passClean . '")'); } } ?>
Using this design pattern will ensure that you always get the same database object no matter what. This is exactly the behavior we want: it ensures that we never establish more than one connection and it takes care of the signature problem (albeit not completely). Let’s take a moment to discuss how and why this works.
In PHP 4, objects were essentially arrays with functions associated with them. That changed in PHP 5, when objects became more like real objects, being stored in memory and returning a “pointer” to the lookup table (for more on how objects work in PHP5 you should read Sara Golemon’s blog post “You’re Being Lied To”.) This new feature allows us to pass around an object without creating a copy of it; instead, we’re passing around the pointer, and that pointer points to whatever object.
At this point, we could probably stop and call it a day. But there is still a drawback to this particular design. What happens if you actually have to connect to more than one database server, such as in a slave-master setup? You are bound to using the same database object, and this is problematic. Also, you’re setting the database object as a property of the Authenticate class; this can create problems with loose pointers later on. That’s where we need the registry pattern. Take a look at this code:
<?php class DatabaseObj { protected $_resource; protected $user = 'me'; protected $pass = 'P@ssw0rd!'; protected $db = 'mydb'; public function __construct($user = null, $pass = null, $db = null) { // Connect to the database. $this->_resource = $returnedResource; } public function getData($string) { // Query the database return $resultResource; } public function clean($var) { // Do cleaning here. return $varClean; } } abstract class Registry { protected static $_cache; public static function set($k, $v) { if(!is_object($v)) throw new Exception('Object expected; ' . gettype($v) . ' given'); if(!is_array(self::$_cache)) self::$_cache = array(); self::$_cache[$k] = $v; return $v; } public static function get($k) { if(isset(self::$_cache[$k])) return self::$_cache[$k]; else throw new Exception("Object doesn't exist!"); } } class Authenticate { public function checkCredentials($user, $pass) { $dbObj = Registry::get('mydb'); $userClean = $dbObj->clean($user); $passClean = $dbObj->clean($pass); return $dbObj->getData('SELECT * FROM users WHERE user = "' . $userClean . '" AND pass = MD5("' . $passClean . '")'); } } ?>
Let’s examine what is going on here. We create a static class that accepts a key and an object. This object is then stored in a static property, and can be accessed through the Registry::get() method. We access a database object in the Authenticate class, making it a local variable for our use, and let the pointer go out of scope at the end of the method’s execution.
This is a better method. Why is this better? After all, it has more code! That’s right, but it’s better in several ways. First, it allows us to connect to more than one database server. This is critical, especially for large applications where your data may be assembled in a master-slave setup. Second, it allows you to store not just one object, but an unlimited number of them, and access them all at the same time. We have essentially created a core repository for objects, all of which can be accessed by any other object by simply requesting it. This is a huge advantage, because it means that our Authenticate class, as well as our ReadEntry class and our WritePost class can access the database object without having to recreate it and each class can theoretically access the others.
An additional benefit of this pattern is that when you extend your code, you don’t have to redesign your object model per se. Instead, since you have a core repository of all the objects you have direct access to them at any point throughout your application. This makes plugging things in much easier.
There are some drawbacks, though. First, the Registry class throws an exception if you request an object that doesn’t actually exist. This means that planning when your objects will exist is critical. Second, we are to some degree hard-coding in an object reference, and this can mean that changing it can be tough. A simple solution is to create a Constants class, which contains the value for various keys you use frequently. You can also use an autoloader and extend the database class for each database you need to connect to, and have the key and the object type be the same. The way you implement this is up to you.
There’s also a great amount of debate over whether to use the registry pattern or dependency injection. Much of this argument focuses on access control and concurrency issues, and source code readability. These are not trivial concerns and they have some bearing on your application. When deciding to use a pattern, you must decide if the solution you have found solves the problem you’ve been presented with. They aren’t appropriate to every problem, and the registry pattern is no exception.
Properly using design patterns can make your code easier to read and maintain. The registry pattern is just one of many solutions to common programming problems. Learn about them, and good luck!
Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP
Posted on 7/21/2009 at 5:30 pm
fqqdk (@fqqdk) wrote at 7/22/2009 2:12 am:
I advise against the Registry design pattern. State that is accessed through static methods can be considered global state, and it is in no way different from using a superglobal.
What would be the difference between
$dbObj = Registry::get(‘mydb’);
and
$dbObj = $GLOBALS[‘mydb’];
?
Well, the first version will throw an Exception, and the second one will trigger a warning. And also the validation of what to put into the global variable pile will look different. But these differences are insignificant.Why is global state bad? http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
Dependency Injection is the way to go.
Mastodont wrote at 7/22/2009 5:52 am:
fqqdk: Constants are global. Functions are global. Have you any problems with them?
Jani Hartikainen (@jhartikainen) wrote at 7/22/2009 6:00 am:
Yes, congratulations on creating a global scope which throws exceptions… ;)
What fqqdk says is correct and I agree with him completely. The blog he links is an excellent resource on various practices regarding testability, dependency injection and general code structuring and most recommended reading.
Brandon Savage (@brandonsavage) wrote at 7/22/2009 7:44 am:
I disagree with both of you. Dependency injection isn’t a bad concept, or a bad format, but for building a framework or application with lots of moving parts and/or plugins it just doesn’t work well sometimes.
It’s a matter of taste, I guess. I’m on Martin Fowler’s side.
Diogo (@garotosopa) wrote at 7/22/2009 8:06 am:
Isn’t it harder to test Authenticate now that it uses Registry?
You’ll have to mockup a Registry class, with a get() method that is expected to be called statically with ‘mydb’ string as an argument. That returns a mock object representing the database with methods that are expected to be called accordingly.
I think passing $dbObj to Authenticate constructor is much easier to test and develop, or even defining it as Authenticate::setDefaultDatabaseAdapter($dbObj) so that every instance of Authenticate can use it.
wbr,
Diogo
fqqdk (@fqqdk) wrote at 7/22/2009 9:53 am:
@Mastodont the problem is not with globalness itself this time, but with ‘mutable global state’. It makes tests harder to write (Miško’s blog is full of explanations, so I won’t go into that).
@Brandon when did Martin Fowler say anything along the lines of “prefer Registry instead of Dependency Injection”? Singleton was among the Gang of Four design patterns, and nowadays it’s considered an antipattern. Registry was in Fowler’s enterprise pattern catalog, but now we should just kill it with fire. :)
Chuck Burgess (@ashnazg) wrote at 7/22/2009 10:21 am:
Testing Authenticate seems easy enough to me. The only thing to mockup is the database object that you load into Registry. If you are truly “unit testing” Authenticate, then all you are doing is ensuring that checkCredentials() is calling the dbobj/mockup methods and returning an expected result. Once you mockup DatabaseObj’s clean() and getData() methods, checkCredentials() actual job is quite trivial.
Unit testing the Registry object looks like it would require no mockups at all, since it’s written to accept any object you pass it.
Now, if you want to get into “integration testing” for checkCredentials(), so that an actual database call occurs, it’s still a matter of what DatabaseObj you load into Registry. Looks to me like the original DatabaseObj would suffice for this, instantiated and connected to whatever database you want the test to hit.
Diogo (@garotosopa) wrote at 7/22/2009 10:37 am:
@ashnazg:
It might be a matter of taste then.
Another down side is that you cannot reuse Authenticate as a standalone class without Registry running and correctly populated.
Implementing a DatabaseAdapter interface and assigning the object to Authenticate looks more reusable.
Loose coupling is the new black :P
Varda wrote at 7/22/2009 1:22 pm:
Hey!
Slightly off-topic, but i was just wondering this part of the post:
“However, if we have six or seven objects in our script, creating a new database object each time means that you’d have six or seven MySQL connections by the end of the script, which is not optimal!”
But doesn’t mysql_connect() reuse an open connection to MySQL if there is one? Atleast thats how i’ve understood it, please correct me if i’m wrong.
Giorgio Sironi (@giorgiosironi) wrote at 7/22/2009 1:50 pm:
@Chuck: then you are “integration test” the registry and the authenticator, if you do not mock registry.
@Brandon: Fowler speaks about Registry pattern because it is included in his book, and the site lists all patterns he presented. The book is agnostic on that and range from Transaction Scripts to Domain Models, presenting everything.
Mastodont wrote at 7/22/2009 3:41 pm:
@fqqdk: What exactly is mutable on Registry without setters? You only pull objects from it. DI is IMHO more prone to errors, because here you can pass any instance of given class.
I do not want to flame, but “war” about singletons, this is old issue (mostly boring). The rejection of Registry is new level for me. What pattern is going to be next enemy of DI fans? :)
Herman Radtke (@hermanradtke) wrote at 7/22/2009 10:44 pm:
The issue isn’t so much with globals as it is with design. Globals and Dependency Injection are not mutually exclusive. It isn’t the worst thing in the world for people to use a global, as long as they have good design.
Example:
$dbObj = Registry::get(‘mydb’); // <— global
$auth = new Authenticate($dbobj); // <— Dependency InjectionThe danger with using globals is that people start taking shortcuts when in a hurry and start introducing tight coupling between classes. To keep myself honest, I try to steer clear of them.
fqqdk (@fqqdk) wrote at 7/23/2009 3:25 am:
@Mastodont If it is not mutable, how do you put the DB object in it?
Daniel O'Connor (@clockwerx) wrote at 7/23/2009 4:34 am:
I think its fair to say that DI proponents would view the Registry pattern as bad, as it is a sign your design (1) doesn’t know what it wants (2) or it is doing way too much.
If you are accessing the Registry through static methods, you are really just ignoring the built in language level mechanism to pass variables into methods – arguments.
If you want a database connection; why not just put it and a type hint in the method signature?
class A {
function b(DB_common $db) {
return $db->query(“SELECT hello from WORLD”);
}
}
// vs
class A {
function b() {
$db = Registry::get(‘db’);
return $db->query(“SELECT hello from WORLD”);
}
}I’m personally always going to prefer the first over the second for clarity, simplicity, testability.
Another example
require_once ‘A.php’;
class B {
function a(SMS $sms) {
return $sms->send(“hi”);
}
}class B {
function a() {
$sms = Registry::get(‘sms’);
return $sms->send(“hi”);
}
}Looks the same as the first; yeah?
But to test both classes, you have to build a registry and fill it with both SMS and DB instances – suddenly, the execution time of both tests is a little bit longer.
So, your simple SMS code now has these two rare but annoying problems:
* The SMS test is connecting to a database server (slow)
* The SMS test didn’t run because the database server went away, and we couldn’t load the registry (its all broken), and you’re looking at what’s wrong with the SMS server (completely the wrong place).
Mastodont wrote at 7/23/2009 5:07 am:
@fqqdk: I do not put DB object in registry as I already wrote. Registry creates it. This is big difference.
fqqdk (@fqqdk) wrote at 7/23/2009 7:18 am:
@Mastodont
Having Registry create a heavyweight object, like a DB connection, is actually far worse than making Registry mutable. How the hell do you mock the DB connection then???
Ivan Jovanovic (@ivanjovanovic) wrote at 7/23/2009 10:36 am:
@Diogo
I agree with you about the problems with this design. Even if someone use Registry pattern it shouldn’t be used within the class but prior to wiring the objects to pull the needed objects from the global repository.
Brandon Savage (@brandonsavage) wrote at 7/23/2009 7:43 pm:
I appreciate the discussion. I still think this is a matter of taste, and that dependency injection is a vital technique as well. Thanks those of you who have contributed.
Mastodont wrote at 7/24/2009 7:08 am:
@fqqdk: Maybe it surprises you, but I never mock DB connection. I am used to test real objects in “real” environment – and test DB server is a obvious requisite. But if you really want it, you can of course amend the code in registry class.
@clockwerx: … you have to build a registry and fill it with both SMS and DB instances … But to test your first method versions, you must pass existing instances as arguments. Is there any difference in term of speeed?
CloCkWeRX (@clockwerx) wrote at 7/24/2009 7:15 am:
@Mastodont – scenario 1:
1. Build registry
2. Connect DB
3. Connect SMS
4. Run DB test
5. Teardown
6. Build registry
7. Connect DB
8. Connect SMS
9. Run SMS test
10. TeardownScenario 2 (injecting only what you need):
1. Connect DB
2. Run DB test
3. Teardown
4. Connect SMS
5. Run SMS test
6. TeardownAdditionally, by controlling your environment tightly, you can rule out the SMS component failing as a cause of your DB errors / vice versa; which is harder in scenario 1
Mastodont wrote at 7/24/2009 9:37 am:
Well, I do not understand why you mix DB amd SMS now, because originally you wrote it as two examples.
… can rule out the SMS component failing as a cause of your DB errors / vice versa; which is harder in scenario 1 …
No way. We have e.g. exceptions.
class A {
function b() {
try {
$db = Registry::get(’db’);
….
} catch ….
}
Herman Radtke (@hermanradtke) wrote at 7/24/2009 10:30 am:
@Mastodont: The reason people dislike the Registry Pattern is because it makes unit testing hard. The funamental idea of unit testing is that you are testing a single unit of functionality. That means you want tests to be simple and concise. Trying to use a real world database object to test functionality becomes very hard and can produce false positives.
Given some class that requires a database connection, mocking the database makes testing the business logic much easier:
class Finance {
function __construct($db) { $this->db = $db; }function calcAmountFinanced($dealno, $term, $payment) {
$amount = $term * $payment;// save amount in database
$this->db->query( “INSERT INTO deal (DealNum, Amount) VALUES ($dealno, $amount)” );return $amount; // return to client so they can see amount
}
}class FakeDb {
function query($sql) {
return true;
}
}function testCalcAmountFinanced()
{
$db = new FakeDb();$finance = new Finance($db);
$amount = calcAmountFinanced(60, 150.00);
if ($amount == 9000) {
return true;
}return false;
}Now I can automate that test in a cronjob each night without managing a database. This becomes even more useful when I test a particular method multiple times. I don’t have to reset the database information to do it. This does not remove the requirement of integration testing with a live database, but it makes it easier for me to spot a problem earlier.
CloCkWeRX (@clockwerx) wrote at 7/25/2009 12:31 am:
@Mastodont re the two examples, that’s a great illustration of how registry can mask it.
They are two classes in the same application (and example 2 require_once’s the first) – but it’s not very obvious.Re the ruling out through exceptions, imagine this sequence of events:
1. Build registry
2. Connect DB (exception thrown)
3. Connect SMS (test failed: exception in DB::connect)
4. Run DB test (test failed: exception in DB::connect)
5. Teardown (test failed: exception in DB::connect)
6. Build registry
7. Connect DB (exception thrown)
8. Connect SMS (test failed: exception in DB::connect)
9. Run SMS test (test failed: exception in DB::connect)
10. Teardown (test failed: exception in DB::connect)I look at the SMS test first, and it’s talking about failing when the database didn’t connect. Huh? What? I look at the code for you and you don’t even use a database!
Typically this results in anger, frustration, grumbling before…. Oh, it’s some kind of outside influence! It’s in the Registry building code, over here.
Suddenly, I have to fix my problems connecting to the Database before I can know if I’ve even got problems with my SMS code.
Another example:
http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.htmlThat post talks about singletons, but Registry::get() is similar – you have to set it up just right in all sorts of places which are non obvious, and can lead to nasty results and strange interactions for no clear reason.
fqqdk (@fqqdk) wrote at 7/25/2009 7:48 am:
(Amending the code for the sake of testing is bad. Why would i want to do that?)
Well I can’t really argue with you now. I think it is better to always go for the most decomposed code with the least amount of coupling, and separating unit tests from integration tests gives me that. I can test the methods logic with the DB mocked out very quickly, and the actual integration in a separate suite of tests that won’t run that frequently. That maximizes the speed of feedback, makes my code cleaner, with APIs that do not lie about the dependencies, and that makes me happy. You keep your logic and integration testing coupled, and have your classes depend implicitly on some god object.. erm.. registry, it’s your call. But I still think it’s wrong.
Mastodont wrote at 7/26/2009 2:45 am:
Herman, return value of calcAmountFinanced is completely independent on DB layer, so here is no problem with mocking. You can safely comment line with db->query. But what if return value is dependent? Why to bother yourself with writing more complex fake code and not to use directly DB? This way you are also pressed to deal with all “unavailable DB” issues.
@clockwerx: … I have to fix my problems connecting to the Database before …
Yes, you must. You can of course simulate roof without walls but in reality you need walls at first. No anger, no frustration. And the risk of omitting something important is high (I have an experience).@fqqdk: I believe that programmer should use the best methods or patterns he can. I am big fan e.g. of stored procedures (evil). If analysis shows the singleton (evil) is applicable I can freely choose it. If I need more return values, I consider output parameters (evil). If total decoupling seems better, have no problems with it.
By the way – Zend Framework, the flagship of PHP, is full of static methods, the Registry is also there. And some classes are singletons (e.g. Zend_Auth). The world is colorful.
CloCkWeRX (@clockwerx) wrote at 7/26/2009 4:24 am:
@mastodont
“You can of course simulate roof without walls but in reality you need walls at first.”Why does the SMS code above need a database connection? It doesn’t; and if the SMS code doesn’t run because the database code is broken, that’s annoying.
“And the risk of omitting something important is high (I have an experience).”
Perhaps, but you haven’t given any code examples or scenarios to back your point of view, so…
“By the way – Zend Framework, the flagship of PHP, is full of static methods, the Registry is also there. And some classes are singletons (e.g. Zend_Auth). The world is colorful.”
Zend_Auth is annoying to test and code built using it is too.
// Why should this:
$auth = new Zend_Auth(); // Cause: Fatal error – private method __construct(); ?// A typical example using a singleton, no injection
class GuardCow {
/** Greets people it knows, moos at strangers */
function noise() {
$auth = Zend_Auth::getInstance();return $auth->hasIdentity() ? “Hi” : “moo”;
}
}The above isn’t easy to test if I want to make sure that $auth->hasIdentity() returns true or false or whatever I please.
How would you write the 2x unit test to assert the cow moos at strangers, and greets friends?
1. You have to open up the code in Zend_Auth, read it, and realise that you need to mock out the Storage layer.
2. Create a mock
3. In each test, load the auth object, set the desired resultfunction testShouldMooAtStrangers() {
$auth = Zend_Auth::getInstance();
$auth->setStorage(new MockStorage(false));$cow = new GuardCow();
$this->assertSame(“moo”, $cow->noise());
}function testShouldGreetFriends() {
$auth = Zend_Auth::getInstance();
$auth->setStorage(new MockStorage(true));$cow = new GuardCow();
$this->assertSame(“Hi”, $cow->noise());
}What happens if I forget the first two lines in each test case? Why do I need to mock out Auth Storage to say my guard cow doesn’t know me? It’s not obvious when I look at the GuardCow class that I have to think about storage at all.
However, if Zend_Auth wasn’t a singleton, I could have just:
function testShouldMooAtStrangers() {
$cow = new GuardCow(new MockAuth(false));
$this->assertSame(“Hi”, $cow->noise());
}function testShouldGreetFriends() {
$cow = new GuardCow(new MockAuth(true));
$this->assertSame(“Hi”, $cow->noise());
}Less code, I don’t need to read the internals of Zend_Auth, and I’m doing what I care about – testing my guard cow.
So, I’d love to see examples where statics, registry pattern or singletons both solve initial design problems and (this is the important one for me and many other DI fans) are still flexible enough when you want to unit test it that it’s trivial / easy to do so, *and saves you time*.
fqqdk (@fqqdk) wrote at 7/26/2009 6:42 am:
@Mastodont if you choose patterns you deem appropriate for the problem at hand, then I am happy for you. Other people will just copy+paste what they see on the net, and that’s where lack of discussion, pros and contras and alternatives will become a problem.
Yes, I’ve spent quite a lot of time browsing the ZF API, and I think the things you mention are problematic.
TheWorkingMan wrote at 7/26/2009 1:34 pm:
I didn’t know that using a singleton and registry seems like a bad coding practice. Basing on the discussions here, I can see the points of both sides. It’s just sad that the use of these patterns are greatly influenced by how you will test your code. Testing is important but I think there should be a balance on the way you design the architecture of your code without regard if it’s going to be easy to test or not. In that way, everyone is free to use whatever pattern there is that seems applicable.
I hope there are some sort of better solutions to make testing of singletons, registries, and similar patterns easier.
Fake51 wrote at 7/28/2009 6:39 am:
Registry and Singleton are not bad design patterns, neither are they anti-patterns. People only think along those lines because they’re blind idealists – and if there’s one thing that should make alarm bells go off, it’s listening to fanatics.
You can abuse singletons and you can abuse the registry pattern … just like you can abuse any other pattern. And that’s what it comes down to.To consider: you could very easily modify Brandon’s code to make it ‘write-once-read-forever’ – in order to allow for creating and storing a database connection once in the registry but not allow anything to overwrite this. Which obviously makes all the high-pitched cries about globals meaningless. In other words: use whichever design matches what you’re doing – don’t listen to people fervently arguing that something or other ‘is evil’.
Regards
Fake
Daniel O'Connor (@clockwerx) wrote at 7/30/2009 8:59 pm:
@Fake51
“Registry and Singleton are not bad design patterns, neither are they anti-patterns. People only think along those lines because they’re blind idealists – and if there’s one thing that should make alarm bells go off, it’s listening to fanatics.”Unfortunately, you haven’t provided one scrap of evidence to support your assertation: that they are patterns that solve design problems with less drawbacks than the alternatives.
Perhaps it would do you well to look at the examples raised in this thread and the links to Misko Hervey’s talks.
These are numerous examples grounded in real life experience about the negatives of using these patterns in your designs, and to date, there are very few drawbacks to dependency injection listed here.
The main reason we’re so passionate about this is pretty simple: you, the author of the original code make a bunch of choices to use statics, global state, singletons, etc; and can’t see the harm in it.
We, the poor saps who have to unit test it, fix it, debug it, and maintain it get stuck with an impossible pile of code which refuses to play nicely with our expectations, breaks unexpectedly, and frustrates us.
That makes us cranky, and makes us think things like “the person who implemented this design is being evil to us”.
Phillip Harrington (@philsown) wrote at 7/31/2009 8:51 am:
For multiple databases (like ‘read’ and ‘write’ in a replication situation) I wrote my own database class which is a ‘multiple singleton’.
It works just like a singleton, except instead of having one $instance variable, I have an array of $instances which is keyed by the name of the database connection I want.
My factory method gives me back the appropriate singleton instance like so:
$DB = DB::factory(‘write’);That way there are only database connections for as many names as I have open, which are usually only the two I mentioned: ‘read’ and ‘write’.
I manage the configurations in an array with names that match the connection names, ‘read’ and ‘write’ like so:
$database = array(
‘read’ => array(
‘host’ => ‘localhost’,
‘user’ => ‘web_db’,
‘pass’ => ‘dog1cat’,
‘data’ => ‘slave_database’,
),
‘write’ => array(
// etc…
‘data’ => ‘master_database’,
),
);
« XDebug Slides – OSCON 2009 | Where Multiple Levels Of Inheritance Will Kill You » |