DevPinoy.org
A Filipino Developers Community
   
Static instances in PHP4
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.


Posted 01-19-2007 9:24 AM by cruizer

Comments

Web Clippings (Geekcraft) - 3/2/2007 » Continuous Learning wrote Web Clippings (Geekcraft) - 3/2/2007 » Continuous Learning
on 03-02-2007 9:46 AM

Copyright DevPinoy 2005-2008