Alternative autoloader for PSR-0 proposal

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

Post Reply
User avatar
Zyxist
Forum Contributor
Posts: 104
Joined: Sun Jan 14, 2007 10:44 am
Location: Cracow, Poland

Alternative autoloader for PSR-0 proposal

Post by Zyxist »

The code below is an alternative to SplClassLoader which loads the classes following the naming convention described in http://groups.google.com/group/php-stan ... l-proposal . I wrote it because I found out that the original autoloader implementation is quite slow for a bigger number of libraries (vendors). Instead of registering different autoloader instance for each class that forces us to perform lots of unnecessary string operation when matching class names, I have one instance for all the classes and the paths are kept in an array:

Code: Select all

<?php
/**
 * Aggregate autoloader that implements the technical interoperability
 * standards for PHP 5.3 namespaces and class names.
 *
 * http://groups.google.com/group/php-standards/web/final-proposal
 *
 * @author Tomasz Jędrzejewski <http://www.zyxist.com/en/>
 */
class AggregateAutoloader
{
	/**
	 * The list of available libraries.
	 * @var array
	 */
	private $_libraries = array();
	
	/**
	 * The loaded file extension
	 * @var string
	 */
	private $_fileExtension = '.php';
	
	/**
	 * The namespace separator
	 * @var string
	 */
	private $_namespaceSeparator = '\\';

	/**
	 * Registers a new library to match.
	 *
	 * @param string $library The library name to add.
	 * @param string $path The path to the library.
	 */
	public function addLibrary($library, $path)
	{
		if(isset($this->_libraries[(string)$library]))
		{
			throw new Exception('Library '.$library.' is already added.');
		}
		if($path[strlen($path) - 1] != DIRECTORY_SEPARATOR)
		{
			$path .= DIRECTORY_SEPARATOR;
		}
		$this->_libraries[(string)$library] = $path;
	} // end addLibrary();
	
	/**
	 * Checks if the specified library is available.
	 *
	 * @param string $library The library name to check.
	 */
	public function hasLibrary($library)
	{
		return isset($this->_libraries[(string)$library]);
	} // end hasLibrary();
	
	/**
	 * Removes a recognized library.
	 *
	 * @param string $library The library name to remove.
	 */
	public function removeLibrary($library)
	{
		if(!isset($this->_libraries[(string)$library]))
		{
			throw new Exception('Library '.$library.' is not available.');
		}
		unset($this->_libraries[(string)$library]);
	} // end removeLibrary();

	/**
	 * Sets the namespace separator used by classes in the namespace of this class loader.
	 * 
	 * @param string $sep The separator to use.
	 */
	public function setNamespaceSeparator($sep)
	{
		$this->_namespaceSeparator = $sep;
	} // end setNamespaceSeparator();

	/**
	 * Gets the namespace seperator used by classes in the namespace of this class loader.
	 *
	 * @return string
	 */
	public function getNamespaceSeparator()
	{
		return $this->_namespaceSeparator;
	} // end getNamespaceSeparator();

	/**
	 * Sets the file extension of class files in the namespace of this class loader.
	 * 
	 * @param string $fileExtension
	 */
	public function setFileExtension($fileExtension)
	{
		$this->_fileExtension = $fileExtension;
	} // end setFileExtension();

	/**
	 * Gets the file extension of class files in the namespace of this class loader.
	 *
	 * @return string $fileExtension
	 */
	public function getFileExtension()
	{
		return $this->_fileExtension;
	} // end getFileExtension();

	/**
	 * Installs this class loader on the SPL autoload stack.
	 */
	public function register()
	{
		spl_autoload_register(array($this, 'loadClass'));
	} // end register();

	/**
	 * Uninstalls this class loader from the SPL autoloader stack.
	*/
	public function unregister()
	{
		spl_autoload_unregister(array($this, 'loadClass'));
	} // end unregister();
	
	/**
	 * Loads the given class or interface.
	 *
	 * @param string $className The name of the class to load.
	 * @return void
	 */
	public function loadClass($className)
	{
		$replacement = str_replace(array('_', $this->_namespaceSeparator), DIRECTORY_SEPARATOR, ltrim($className, $this->_namespaceSeparator));
		$id = strpos($replacement, DIRECTORY_SEPARATOR);
		if($id === false)
		{
			return false;
		}
		$match = substr($replacement, 0, $id);
		$path = substr($replacement, $id+1, strlen($replacement) - $id);
		
		if(!isset($this->_libraries[$match]))
		{
			return false;
		}
		require($this->_libraries[$match].$path.$this->_fileExtension);
		return true;
	} // end loadClass();
} // end AggregateAutoloader;
Sample use:

Code: Select all

$loader = new AggregateAutoloader;
$loader->addLibrary('Foo', './path/to/Foo/');
$loader->addLibrary('Bar', './path/to/Bar/');
$loader->addLibrary('Joe', './path/to/Joe/');
$loader->register();
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: Alternative autoloader for PSR-0 proposal

Post by AbraCadaver »

It would be interesting to see some benchmarks between the two, loading a large number of files.
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
User avatar
Zyxist
Forum Contributor
Posts: 104
Joined: Sun Jan 14, 2007 10:44 am
Location: Cracow, Poland

Re: Alternative autoloader for PSR-0 proposal

Post by Zyxist »

Here they are: http://media.zyxist.com/download/autoloadBench.tar.gz

I tested loading 200 classess grouped in 10 libraries:

* Manual (bare) class loading: 0.0366496 s
* AggregateAutoloader: 0.0423926 s
* SplClassLoader: 0.0501879 s

And loading 200 classes grouped in 1 library:

* Manual (bare) class loading: 0.0374809 s
* AggregateAutoloader: 0.0417581 s
* SplClassLoader: 0.0432021 s

For more libraries, AggregateAutoloader tends to be significantly faster, whereas in case of a single library the results are within a statistical error.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Alternative autoloader for PSR-0 proposal

Post by Weirdan »

Couple of notes here:
  1. Don't throw Exception, throw your own specific descendant exception class. This will allow client to perform selective catch.
  2. File extension is defined globally for entire autoloader, while in reality it could be different for each included library.
  3. '/' works as a separator on both windows and netware - no need to use platform-specific directory separator. In fact, should someone pass 'path/to/dir/' in a addLibrary() call on windows, it would add '\' to the path.
  4. PSR-0 says '_' is not translated into directory separator when used as a part of namespace, yet your autoloaded does just that.
User avatar
Zyxist
Forum Contributor
Posts: 104
Joined: Sun Jan 14, 2007 10:44 am
Location: Cracow, Poland

Re: Alternative autoloader for PSR-0 proposal

Post by Zyxist »

Exceptions -> I know; if I was to deploy it somewhere, I would add some kind of specific exception, but just for testing it was unnecessary.

File extensions -> I know, but actually I haven't seen any library that used something else than .php for quite a long time. For some extra features, the autoloader could be extended to keep an array of settings for each library, not only with the path, but also file extension and other things, if needed.

Underscore -> hmm, actually you're right:
The underscore '_' has no special meaning in namespaces.
I'll fix it, as well as directory separator issues.

----
Edit:

OK, the improved version is available under the same link. The autoloading function has been rewritten:

Code: Select all

public function loadClass($className)
{
	$className = ltrim($className, $this->_namespaceSeparator);
	$match = strstr($className, $this->_namespaceSeparator, true);
		
	if(false !== $match || !isset($this->_libraries[$match]))
	{
		return false;
	}
	$rest = strrchr($className, $this->_namespaceSeparator);
	$replacement =
		str_replace($this->_namespaceSeparator, '/', substr($className, 0, strlen($className) - strlen($rest))).
		str_replace(array('_', $this->_namespaceSeparator), '/', $rest);
	require($this->_libraries[$match].$replacement.$this->_extensions[$match]);
	return true;
} // end loadClass();
Benchmark results for 10 libraries:

* Bare class loading: 0.0359044 s
* AggregateAutoloader: 0.0416718 s
* SplClassLoader: 0.0476685 s

Benchmark results for 1 library:

* Bare class loading: 0.0363744 s
* AggregateAutoloader: 0.0426363 s
* SplClassLoader: 0.0414774 s
Post Reply