Page 1 of 2

The perfect service locator for PHP5 using __autoload?

Posted: Fri Sep 08, 2006 8:16 am
by Chris Corbyn
My objective was to allow __autoload() to be easily extended in complex systems/frameworks where specific libraries etc may need loading differently but you don't want to hard-code little adjustments into your working __autoload() to allow this to happen.

Using a ServiceLocator object with some static methods and properties to allow loosely coupled locators to be attached to it you can swap/change and add to the functionality of your __autoload() at runtime.

The core stuff:

Code: Select all

<?php

/**
 * Defines the methods any actual locators must implement
 * @package ServiceLocator
 * @author Chris Corbyn
 */
interface Locator
{
	/**
	 * Inform of whether or not the given class can be found
	 * @param string class
	 * @return bool
	 */
	public function canLocate($class);
	/**
	 * Get the path to the class
	 * @param string class
	 * @return string
	 */
	public function getPath($class);
}

/**
 * The main service locator.
 * Uses loosely coupled locators in order to operate
 * @package ServiceLocator
 * @author Chris Corbyn
 */
class ServiceLocator
{
	/**
	 * Contains any attached service locators
	 * @var array Locator
	 */
	protected static $locators = array();
	
	/**
	 * Attach a new type of locator
	 * @param object Locator
	 * @param string key
	 */
	public static function attachLocator(Locator $locator, $key)
	{
		self::$locators[$key] = $locator;
	}
	/**
	 * Remove a locator that's been added
	 * @param string key
	 * @return bool
	 */
	public static function dropLocator($key)
	{
		if (self::isActiveLocator($key))
		{
			unset(self::$locators[$key]);
			return true;
		}
		else return false;
	}
	/**
	 * Check if a locator is currently loaded
	 * @param string key
	 * @return bool
	 */
	public static function isActiveLocator($key)
	{
		return array_key_exists($key, self::$locators);
	}
	/**
	 * Load in the required service by asking all service locators
	 * @param string class
	 */
	public function load($class)
	{
		foreach (self::$locators as $key => $obj)
		{
			if ($obj->canLocate($class))
			{
				require_once $obj->getPath($class);
				if (class_exists($class)) return;
			}
		}
	}
}

/**
 * PHPs default __autload
 * Grabs an instance of ServiceLocator then runs it
 * @package ServiceLocator
 * @author Chris Corbyn
 * @param string class
 */
function __autoload($class)
{
	$locator = new ServiceLocator();
	$locator->load($class);
}

?>
An example Use Case:

Code: Select all

<?php

require 'ServiceLocator.php';

//Define some sort of service locator to attach...
class PearLocator implements Locator
{
	protected $base = '.';
	
	public function __construct($directory='.')
	{
		$this->base = (string) $directory;
	}
	
	public function canLocate($class)
	{
		$path = $this->getPath($class);
		if (file_exists($path)) return true;
		else return false;
	}
	
	public function getPath($class)
	{
		return $this->base . '/' . str_replace('_', '/', $class) . '.php';
	}
}

// ... attach it ...
ServiceLocator::attachLocator(new PearLocator(), 'PEAR');

// ... and code away....
$foo = new Foo_Test();

?>

Posted: Fri Sep 08, 2006 8:34 am
by Weirdan
Greatly simple. Simply great.
Image

Posted: Fri Sep 08, 2006 10:21 am
by Luke
I'm always very impressed by your classes. So simple... I was working on something kind of similar, but lost interest and forgot about it. I'll likely use this in my next php5 project.

Posted: Fri Sep 08, 2006 10:39 am
by Jenk
When using Zend Framework I use:

Code: Select all

function __autoload($class)
{
    if ($class == 'Zend') 
    {
        require_once 'Zend.php';
    }
    else
    {
        Zend::loadClass($class);
    }
}

Posted: Sat Sep 09, 2006 7:04 am
by Chris Corbyn
Updated. The service locator is an autoloader in actuality so it's been renamed. The superfluous endings in the function names have been dropped and since the object was completely monostate through and through it's been made static through and through :)

Main Core stuff:

Code: Select all

<?php

/**
 * Defines the methods any actual locators must implement
 * @package Autoloader
 * @author Chris Corbyn
 */
interface Locator
{
	/**
	 * Inform of whether or not the given class can be found
	 * @param string class
	 * @return bool
	 */
	public function canLocate($class);
	/**
	 * Get the path to the class
	 * @param string class
	 * @return string
	 */
	public function getPath($class);
}

/**
 * The main service locator.
 * Uses loosely coupled locators in order to operate
 * @package Autoloader
 * @author Chris Corbyn
 */
class Autoloader
{
	/**
	 * Contains any attached service locators
	 * @var array Locator
	 */
	protected static $locators = array();
	
	/**
	 * Attach a new type of locator
	 * @param object Locator
	 * @param string key
	 */
	public static function attach(Locator $locator, $key)
	{
		self::$locators[$key] = $locator;
	}
	/**
	 * Remove a locator that's been added
	 * @param string key
	 * @return bool
	 */
	public static function drop($key)
	{
		if (self::isActive($key))
		{
			unset(self::$locators[$key]);
			return true;
		}
		else return false;
	}
	/**
	 * Check if a locator is currently loaded
	 * @param string key
	 * @return bool
	 */
	public static function isActive($key)
	{
		return array_key_exists($key, self::$locators);
	}
	/**
	 * Load in the required service by asking all service locators
	 * @param string class
	 */
	public static function load($class)
	{
		foreach (self::$locators as $key => $obj)
		{
			if ($obj->canLocate($class))
			{
				require_once $obj->getPath($class);
				if (class_exists($class)) return;
			}
		}
	}
}

/**
 * PHPs default __autoload
 * Just a wrapper to Autoloader::load()
 * @package Autoloader
 * @author Chris Corbyn
 * @param string class
 */
function __autoload($class)
{
	Autoloader::load($class);
}

?>
An example locator:

Code: Select all

<?php

/**
 * The PEAR locator for use with Autoloader
 * Locates files containing the correct class
 * @package Autoloader
 * @author Chris Corbyn
 */
class PearLocator implements Locator
{
	/**
	 * The base directory where classes reside
	 * @var string directory
	 */
	protected $base = '.';
	
	/**
	 * Constructor
	 * @param string base directory
	 */
	public function __construct($directory='.')
	{
		$this->base = (string) $directory;
	}
	/**
	 * Report whether or not the class file is found
	 * @param string class
	 * @return bool
	 */
	public function canLocate($class)
	{
		$path = $this->getPath($class);
		if (file_exists($path)) return true;
		else return false;
	}
	/**
	 * Return the path to the file
	 * @param string class
	 * @return string
	 */
	public function getPath($class)
	{
		return $this->base . '/' . str_replace('_', '/', $class) . '.php';
	}
}

?>
Use case:

Code: Select all

<?php

//This stuff would obviously be in some common file like index.php or a controller
require_once 'Autoloader.php';

require_once 'PearLocator.php';
Autoloader::attach(new PearLocator, 'PEAR');

//And then voila! It works!
$test = new Example_Test_Thing();
$test->doSomething();

?>
I don't think there's much more I could change really so there you have it :)

Posted: Sat Sep 09, 2006 8:13 am
by neophyte
Very nice service locator. I like the fact that you decoupled the various types of services. Would be very easy to add additional types without reworking code. Great job! Very creative way of using the __autoload function as well! :)

Posted: Mon Sep 11, 2006 9:56 am
by Maugrim_The_Reaper
Are there any openings for d11wtq Apprentices? :)

Could you post some misconceived code just so everyone knows you're not perfect? Another tidbit of creative code for my mind tank. Cool.

Posted: Mon Sep 11, 2006 8:19 pm
by Ambush Commander
That is so spiffy! Perhaps the only (slight) complaint I had was that I read attach() and drop() and was like "Huh?" before I read the docs. Perhaps addLocator() and removeLocator()? But this is precisely perfect solution to the __autoload from multiple places problem (perhaps a Pattern for PHP?).

Posted: Tue Sep 12, 2006 12:51 am
by Chris Corbyn
Ambush Commander wrote:That is so spiffy! Perhaps the only (slight) complaint I had was that I read attach() and drop() and was like "Huh?" before I read the docs. Perhaps addLocator() and removeLocator()?
I guess the names maybe could be changed. Basically I removed the endings because I figured they were a little superfluous initially :)
Ambush Commander wrote:But this is precisely perfect solution to the __autoload from multiple places problem (perhaps a Pattern for PHP?).
Yes, although I've since discovered spl_autoload_register() if you have SPL enabled :) That's slightly different however.

Posted: Tue Sep 12, 2006 1:05 am
by Christopher
Though it really does not look like a Service Locator, it is a very cool autoloader. I like the plug-in loader design. I think maybe replace "Locator" with "Loader" and "Locate" with "Load" and it will be clearer what it is.

If you want it to be a Service Locator we could discuss how to get from here to there.

Posted: Tue Sep 12, 2006 6:11 am
by sike
hi,

i am using the exact same way for autoloading in my projects (:
just a different naming (which i am going to rework) :

Code: Select all

/*
  * Autoloader
  */
  Autoload_Locator::addStrategy(new Autoload_Strategy_Package(PACKAGE_ROOT));
  Autoload_Locator::addStrategy(new Autoload_Strategy_PEAR(PEAR_ROOT));
 
  function __autoload($className)
  {
     Autoload_Locator::load($className);
  }
it proved very helpful when you have to incorporate external libs (like pear)

Posted: Tue Sep 12, 2006 11:16 am
by Chris Corbyn
sike wrote:hi,

i am using the exact same way for autoloading in my projects (:
just a different naming (which i am going to rework) :

Code: Select all

/*
  * Autoloader
  */
  Autoload_Locator::addStrategy(new Autoload_Strategy_Package(PACKAGE_ROOT));
  Autoload_Locator::addStrategy(new Autoload_Strategy_PEAR(PEAR_ROOT));
 
  function __autoload($className)
  {
     Autoload_Locator::load($className);
  }
it proved very helpful when you have to incorporate external libs (like pear)
Interesting :) Then maybe we should document it as a pattern.

Posted: Tue Sep 12, 2006 2:26 pm
by Christopher
d11wtq wrote:Interesting :) Then maybe we should document it as a pattern.
You mean like -- an autoloader using the Strategy pattern? ;)

Posted: Tue Sep 12, 2006 3:51 pm
by Chris Corbyn
arborint wrote:
d11wtq wrote:Interesting :) Then maybe we should document it as a pattern.
You mean like -- an autoloader using the Strategy pattern? ;)
.... and off d11 goes to do some research :D

Posted: Wed Sep 13, 2006 3:30 am
by Maugrim_The_Reaper
If you pound it out as a Pattern demonstration (as well as cool code) you can add it to http://www.patternsforphp.com