The perfect service locator for PHP5 using __autoload?

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

The perfect service locator for PHP5 using __autoload?

Post 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();

?>
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

Greatly simple. Simply great.
Image
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post 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);
    }
}
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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 :)
User avatar
neophyte
DevNet Resident
Posts: 1537
Joined: Tue Jan 20, 2004 4:58 pm
Location: Minnesota

Post 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! :)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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?).
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post 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.
(#10850)
sike
Forum Commoner
Posts: 84
Joined: Wed Aug 02, 2006 8:33 am

Post 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)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

d11wtq wrote:Interesting :) Then maybe we should document it as a pattern.
You mean like -- an autoloader using the Strategy pattern? ;)
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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
Post Reply