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.
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.
<?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);
}
?>
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.
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
<?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);
}
?>
<?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';
}
}
?>
<?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
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!
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?).
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.
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.