If you've read
my previous posts here, you'll notice that I advocate code testability and TDD. One of the key design concepts in achieving this is "inversion of control" or the
dependency injection pattern. While I do mostly .NET stuff these days, I still dabble with
PHP projects from time to time. So of course I'll be applying those concepts to PHP as well.
One thing that I needed to do with PHP was to come up with static references. In particular, I a wanted to create a
singleton that serves as my factory. Hmm...some of you
may frown on the word "singleton" being mentioned in the same breath as "testability" or "dependency injection" but I wanted to use that pattern so I can ensure that my running page, at any given time, has only one instance of a particular class -- say, my data access object -- and have an easy way to access that instance anywhere.
While OOP in PHP has been upgraded significantly for version 5, my project was in version 4. PHP4 doesn't support static class members or static classes. Tough luck. But it supported static variables declared inside functions, like this:
function someFunction($someParameter) {
static $someStaticVar = 0;
// do some stuff...
$someStaticVar++;
// do some more
return $someResult;
}So I thought of a workaround to do what I want:
$settingsArray = array( /* my db settings go here... */ );
function & getMyStaticFactory() {
static $myStaticInstance = null;
if ($myStaticInstance == null) {
$myStaticInstance = & new Factory();
}
return $myStaticInstance;
}
class Factory {
var $myDAL;
var $someService;
function Factory() {
$this->myDAL = null;
$this->someService = null;
}
function & GetDAL() {
global $settingsArray;
$singleFactory = & getMyStaticFactory();
if ($singleFactory->myDAL == null) {
$singleFactory->myDAL = & new MyDALClass($settingsArray);
}
return $singleFactory->myDAL;
}
function & GetSomeServiceThatDependsOnDAL() {
global $settingsArray;
$singleFactory = & getMyStaticFactory();
if ($singleFactory->someService == null) {
$dalToUse = & Factory::GetDAL();
$singleFactory->someService = & new ServiceClass(&$dalToUse,
$settingsArray);
}
return $singleFactory->someService;
}
}so what happens now is that for me to be able to retrieve a reference to a global DAL, I just do something like:
$dalReference = & Factory::GetDAL();and to retrieve a reference to a global instance of SomeService and not worry about its dependencies, I can also do:
$serviceReference = & Factory::GetSomeServiceThatDependsOnDAL();OK, so far so good. Remember my
last post where I said that PHP probably needs TDD more than other platforms do because of its loose nature? This is where it bit me. I'm using this testing framework for PHP called
SimpleTest. So I created a test case like this for my Factory (I know, I know, it's not TDD to do test cases after creating code, but I'm doing this to illustrate this problem with PHP4). It ensures that the references I got are not null (of course) and that they are identical references (pointing to just ONE instance):
function testThatIHaveOnlyOneDALInstance() {
$dal1 = & Factory::GetDAL();
$dal2 = & Factory::GetDAL();
$this->assertNotNull($dal1);
$this->assertNotNull($dal2);
$this->assertReference($dal1, $dal2);
}And guess what...it's failing! Being the enterprising programmer that I am (kapal ng mukha, ha ha), I inserted an echo statement in the getMyStaticFactory() so that I'll know if it's instantiating more than one instance of the Factory. After running the test, I saw that it did instantiate the factory twice.
So...can we conclude now that stupid old PHP4 does not support static references and the singleton pattern? Not quite. It turns out from the
PHP manual that PHP4 implements static variables as references indeed. And if you assign a reference to that static reference (in this case $myStaticInstance), it WON'T! The solution is not to assign a reference to it but just use straight assignment. So the correct function should look like:
function & getMyStaticFactory() {
static $myStaticInstance = null;
if ($myStaticInstance == null) {
$myStaticInstance = new Factory();
}
return $myStaticInstance;
}Note the removal of the ampersand (&) symbol that is used to denote references. Weird, but it worked. So now the tests pass!
So what did I learn here? First, PHP4 OOP has a lot of gotchas. Avoid it if possible (by going PHP5) if you're used to the way Java and .NET OOP operates. Second, it's great to have test cases validating the correctness of my code.