PHP, Zend Framework and Other Crazy Stuff
Archive for May 24, 2006
Well, it was bound to happen sooner or later…Refactoring Partholan
May 24th
Edit: Since the original post I have continued some unit testing driven designing – the near final version is posted to the QS forums HERE. A few amendments are likely – for example we currently search locations via PHP checks – it would be far easier to simply add the locations to our includes directory list. You thoughts on the topic are welcome, and many thanks to the Devnetwork members who contributed ideas and feedback.
I’m slowly back on the case with Partholan and Quantum Star SE. After spending a few hours looking through the source code again I’ve been putting together a small list of needed improvements. The first improvement is the most obvious, and the most damaging. A lot of classes need to make calls either to grab singletons, or grab instances from the current ApplicationHelper. In practice it works, but it’s messy. I could leave it alone, but future changes would start getting bogged down eventually. In short the classes have too many dependencies – and I need to shift these out to a single access point.
The suggested solution is to implement a ServiceLocator. A ServiceLocator is similar to a Registry (storing object instances for future use) but it also adds some additional responsibilty. For example, it is also responsible for instantiating objects – not simply storing them though it does that once its instantiated something. That’s not the only interpretation – many ServiceLocator classes I’ve found in Java and PHP are really just Registry classes under a different name. Anyway, the new Partholan class has been coded for the most part, although I have yet to implement object instantiation via Factory classes (as is needed for Database connections and such). The basic method names used are:
Public:
- registerService()
- registerFactory()
- addSearchLocations()
- removeService()
- removeFactory() [not implemented]
- getService()
- hasService()
- hasFactory()
- hasSearchLocations()
Private:
- getServiceFromCache() [not implemented]
- getServiceFromClass() [not implemented]
- getServiceFromFile() [not implemented]
- getServiceFromFactory() [not implemented]
The 4 private methods are likely to result from refactoring (Extract Method) in the public getService() where the 4 possible avenues to locating and returing an instantiated object are determined by a conditional if…else block. Haven’t bothered with the refactoring yet since it’s not worth the effort but adding factories will turn that getService() method into a 100 line or more smelly monster.
Anyways, the second difference in this round of Partholan updating is that I have committed to bringing out the big guns. I’ll be writing unit tests for all the changes I make, especially since refactoring without testing of some kind is a sure way to introduce small annoying bugs. I’ve already posted this to Devnetworks for some feedback but here’s the new ServiceLocator class with unit tests…
private static$instance = false;
private $instances = array();
private $factories = array();
private $searchLocations = array();
public function __construct(){// allow direct instances
}
staticfunction getInstance(){
if(!self::$instance)
{
self::$instance = new Partholan_ServiceLocator();
}
return self::$instance;
}
public function registerService($serviceName, $serviceInstance){
if(is_object($serviceInstance)&& strlen($serviceName))
{
$this->instances[$serviceName] = $serviceInstance;
returntrue;
}
trigger_error(‘Service could not be registered. Requires a valid Service name and object.’);
}
public function removeService($serviceName){
unset($this->instances[$serviceName]);
}
public function getService($serviceName){
if(isset($this->instances[$serviceName]))
{
return$this->instances[$serviceName];
}
elseif(is_class($serviceName))
{
$this->instances[$serviceName] = new$serviceName();
return$this->instances[$serviceName];
}
else
{
foreach($this->searchLocationsas$location)
{
if(file_exists($location . DIRECTORY_SEPARATOR . $serviceName . ‘.php’))
{
require_once($location . DIRECTORY_SEPARATOR . $serviceName . ‘.php’);
$this->instances[$serviceName] = new$serviceName();
return$this->instances[$serviceName];
}
}
}
// factory lookups to be added later
trigger_error(‘Specified Service could not be located.’, E_USER_ERROR);
}
public function registerFactory($factoryName, $serviceFactory){
if(is_object($serviceFactory)&& strlen($factoryName))
{
$this->factories[$factoryName] = $serviceFactory;
returntrue;
}
trigger_error(‘Factory could not be registered. Requires a valid Factory name and object.’);
}
public function addSearchLocations(){
foreach(func_get_args()as$dirPath)
{
$this->searchLocations[] = $dirPath;
}
}
public function hasService($serviceName){
if($this->instances[$serviceName])
{
returntrue;
}
returnfalse;
}
public function hasFactory($factoryName){
if($this->factories[$factoryName])
{
returntrue;
}
returnfalse;
}
public function hasSearchLocation($dirPath){
if(in_array($dirPath, $this->searchLocations))
{
returntrue;
}
returnfalse;
}
}
My simple empty object (used in testing object presences)…
public function __construct(){}
}
The unit tests…
private $data = array();
public function __construct(){
$this->UnitTestCase(‘ServiceLocator Test’);
}
public function setUp(){}
public function tearDown(){}
public function testRegisteringService(){
$sl = new Partholan_ServiceLocator();
$sl->registerService(‘EmptyObject’, new EmptyObject);
$this->assertTrue($sl->hasService(‘EmptyObject’));
$this->assertIsA($sl->getService(‘EmptyObject’), ‘EmptyObject’);
}
public function testRemovingService(){
$sl = new Partholan_ServiceLocator();
$sl->registerService(‘EmptyObject’, new EmptyObject);
$sl->removeService(‘EmptyObject’);
$this->assertFalse($sl->hasService(‘EmptyObject’));
}
public function testAddingSearchLocations {
$sl = new Partholan_ServiceLocator();
$sl->addSearchLocations(‘/tmp’, ‘/home’);
$this->assertTrue($sl->hasSearchLocation(‘/tmp’));
$this->assertTrue($sl->hasSearchLocation(‘/home’));
}
// test: no class, no file included – get file and instantiate object
public function testGettingServiceInstanceFromFile(){
$sl = new Partholan_ServiceLocator();
$sl->setSearchLocations(dirname(__FILE__)); // or location of EmptyObject.php if other
$mk = $sl->getService(‘EmptyObject’);
$this->assertIsA($mk, ‘EmptyObject’);
$this->assertTrue($sl->hasService(‘EmptyObject’));
}
// test: is_class method where file pre-included()
public function testGettingServiceInstance(){
$sl = new Partholan_ServiceLocator();
$mk = $sl->getService(‘EmptyObject’);
$this->assertIsA($mk, ‘EmptyObject’);
$this->assertTrue($sl->hasService(‘EmptyObject’));
}
public function testRegisteringFactory(){
$sl = new Partholan_ServiceLocator();
$sl->registerFactory(‘EmptyObject’, new EmptyObject);
$this->assertTrue($sl->hasFactory(‘EmptyObject’));
$this->assertIsA($sl->getFactory(‘EmptyObject’), ‘EmptyObject’);
}
}
Anyone with comments, I’d love to hear from you. Especially if you’ve used something similar in the past and have an opinion on its use.

Recent Comments