Page 2 of 2

Posted: Sun May 28, 2006 9:40 am
by Maugrim_The_Reaper
Just to reference the near final version...

Code: Select all

<?php
	
class Partholan_ServiceLocator /* implements Partholan_iServiceLocator */ {

	const SERVICE = 'service';
	const FACTORY = 'factory';
	const AUTO = 'auto';
	private $store = array(
			self::SERVICE => array(),
			self::FACTORY => array(),
			self::AUTO => array()
	);
	private static $instance = false;
	private $searchLocations = array();

	public function __construct() {

	}

	static function getInstance() {
		if(!self::$instance)
		{
			self::$instance = new Partholan_ServiceLocator();
		}
		return self::$instance;
	}
     
	public function register($className, $instance, $classType = self::AUTO) {
		if(is_object($instance) && strlen($className))
		{
			$this->store[$classType][$className] = $instance;
			return true;
		}
		trigger_error('Instance could not be registered. Requires a valid Instance name and object.');
	}

	public function remove($className, $classType = self::AUTO) {
		if($this->store[$classType][$className])
		{
			unset($this->store[$classType][$className]);
			return true;
		}
		elseif($classType == self::AUTO)
		{
			foreach($this->store as $typeStore)
			{
				if($typeStore[$className])
				{
					unset($typeStore[$className]);
					return true;
				}
			}
		}
		trigger_error('Instance to remove could not be located.', E_USER_NOTICE);
	}

	public function get($className, $classType = self::AUTO) {
		if($this->store[$classType][$className])
		{
			return $this->store[$classType][$className];
		}
		elseif($classType == self::AUTO)
		{
			foreach($this->store as $typeStore)
			{
				if($typeStore[$className])
				{
					return $typeStore[$className];
				}
			}
		}
		if(class_exists($className))
		{
			$this->store[$classType][$className] = new $className();
			return $this->store[$classType][$className];
		}
		$return = $this->_getFromFile($className, $classType);
		if(!$return && $classType != self::FACTORY)
		{
			$return = $this->_getFromFactory($className, $classType);
		}
		if($return) return $return;
		trigger_error('Unable to locate ' . $className . ' of type "' . $classType . '"', E_USER_ERROR);
	}

	public function addSearchLocations() {
		foreach(func_get_args() as $directoryPath)
		{
			$this->searchLocations[] = $directoryPath;
		}
	}

	public function exists($className, $classType = self::AUTO) {
		if($this->store[$classType][$className])
		{
			return true;
		}
		elseif($classType == self::AUTO)
		{
			foreach($this->store as $typeStore)
			{
				if($typeStore[$className])
				{
					return true;
				}
			}
		}
		return false;
	}

	public function hasSearchLocation($directoryPath) {
		if(in_array($directoryPath, $this->searchLocations))
		{
			return true;
		}
		return false;
	}

	private function _getFromFile($className, $classType) {
		foreach($this->searchLocations as $location)
		{
			if(file_exists($location . DIRECTORY_SEPARATOR . $className . '.php'))
			{
				require_once($location . DIRECTORY_SEPARATOR . $className . '.php');
				$this->store[$classType][$className] = new $className();
				return $this->store[$classType][$className];
			}
		}
		return false;
	}

	private function _getFromFactory($className, $classType) {
		$factory = $this->get($className . 'Factory', self::FACTORY);
		if($factory)
		{
			$this->store[$classType][$className = $factory->create();
			return $this->store[$classType][$className];
		}
		return false;
	}

}

?>
And unit tests...

Code: Select all

<?php

require_once(APPROOT . 'Partholan/Classes/ServiceLocator.php');
require_once('Partholan/EmptyObject.php');

class Test_ServiceLocator extends UnitTestCase {

	public function __construct() {
		$this->UnitTestCase('ServiceLocator Test');
	}

	public function setUp() { }

	public function tearDown() { }

	public function testRegistering() {
		$sl = new Partholan_ServiceLocator();
		$sl->register('EmptyObject', new EmptyObject(), Partholan_ServiceLocator::SERVICE);
		$this->assertTrue($sl->exists('EmptyObject', Partholan_ServiceLocator::SERVICE));
		$this->assertIsA($sl->get('EmptyObject', Partholan_ServiceLocator::SERVICE), 'EmptyObject');
	}

	public function testRemoving() {
		$sl = new Partholan_ServiceLocator();
		$sl->register('EmptyObject', new EmptyObject);
		$sl->remove('EmptyObject');
		$this->assertFalse($sl->exists('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 testGettingInstanceFromFile() {
		$sl = new Partholan_ServiceLocator();
		$sl->addSearchLocations(dirname(__FILE__)); // or location of EmptyObject2 class
		$mk = $sl->get('EmptyObject2');
		$this->assertIsA($mk, 'EmptyObject2');
		$this->assertTrue($sl->exists('EmptyObject2'));
	}

	// test: class_exists() method where file pre-included()
	public function testGettingInstance() {
		$sl = new Partholan_ServiceLocator();
		$mk = $sl->get('EmptyObject2');
		$this->assertIsA($mk, 'EmptyObject2');
		$this->assertTrue($sl->exists('EmptyObject2'));
	}

}

?>
Todo - cache files at each search location for quicker lookups (perf optimisation), remove AUTO from array foreach in get/exists/remove since it was checked already (perf optimisation), get/exists/remove are largely duplicated if..else conditions - maybe see if can be replaced (maybe).

Thanks for the input guys.