<?php /** * A tool for autoloading PHP classes within a root directory and namespace. * * @author Taddeus Kroes * @version 1.0 * @date 13-07-2012 */ namespace BasicWeb; require_once 'base.php'; /** * Object that to automatically load classes within a root directory. * Simple example: all classes are located in the 'classes' directory. * <code> * $loader = new Autoloader('classes'); * $loader->load_class('FooBar'); // Includes file 'classes/foo_bar.php' * </code> * * An Autoloader instance can register itself to the SPL autoload stack, so * that explicit 'include' statements for classes are not necessary anymore. * Applied to the example above: * <code> * $loader = new Autoloader('classes'); * $loader->register(); * $foobar = new FooBar(); // File 'classes/foo_bar.php' is automatically included * </code> * * Namespaces are assumed to indicate subdirectories: * <code=php> * Autoloader::create('classes')->register(); * $bar = new Foo\Bar(); // Includes 'classes/foo/bar.php' * $baz = new Foo\Bar\Baz(); // Includes 'classes/foo/bar/baz.php' * </code> * * Multiple autoloaders can be registered at the same time. Be sure to disable * exceptions on the previously added loaders! * <code> * <code> * File structure: * classes/ * | foo.php // Contains class 'Foo' * other_classes/ * | bar.php // Contains class 'Bar' * </code> * Autoloader::create('classes', false)->register(); * Autoloader::create('other_classes')->register(); * $foo = new Foo(); // Includes 'classes/foo.php' * $bar = new Bar(); // Includes 'other_classes/bar.php', since 'classes/bar.php' does not exist * $baz = new Baz(); // Throws an exception, since 'other_classes/baz.php' does not exist * </code> * * @package BasicWeb */ class Autoloader extends Base { /** * The root directory to look in. * * @var string */ private $root_directory; /** * The namespace classes in the root directory are expected to be in. * * This namespace is removed from the beginning of loaded class names. * * @var string */ private $root_namespace = '\\'; /** * Whether to throw an exception when a class file does not exist. * * @var bool */ private $throw_errors; /** * Create a new Autoloader instance. * * @param string $directory Root directory of the autoloader. * @param bool $throw Whether to throw an exception when a class file does not exist. */ function __construct($directory, $throw=false) { $this->set_root_directory($directory); $this->set_throw_errors($throw); } /** * Set whether to throw an exception when a class file does not exist. * * @param bool $throw Whether to throw exceptions. */ function set_throw_errors($throw) { $this->throw_errors = !!$throw; } /** * Whether an exception is thrown when a class file does not exist. * * @return bool */ function get_throw_errors() { return $this->throw_errors; } /** * Set the root directory from which classes are loaded. * * @param string $directory The new root directory. */ function set_root_directory($directory) { $this->root_directory = self::path_with_slash($directory); } /** * Get the root directory from which classes are loaded. * * @return string */ function get_root_directory() { return $this->root_directory; } /** * Set the root namespace that loaded classes are expected to be in. * * @param string $directory The new root namespace. */ function set_root_namespace($namespace) { // Assert that the namespace ends with a backslash if( $namespace[strlen($namespace) - 1] != '\\' ) $namespace .= '\\'; $this->root_namespace = $namespace; } /** * Get the root namespace that loaded classes are expected to be in. * * @return string */ function get_root_namespace() { return $this->root_namespace; } /** * Append a slash ('/') to the given directory name, if it is not already there. * * @param string $directory The directory to append a slash to. * @return string */ static function path_with_slash($directory) { return $directory[strlen($directory) - 1] == '/' ? $directory : $directory.'/'; } /** * Convert a class name to a file name. * * Uppercase letters are converted to lowercase and prepended * by an underscore ('_'). * * @param string $classname The class name to convert. * @return string */ static function classname_to_filename($classname) { return strtolower(preg_replace('/(?<=.)([A-Z])/', '_\\1', $classname)); } /** * Strip the root namespace from the beginning of a class name. * * @param string $classname The name of the class to strip the namespace from. * @return string The stripped class name. */ private function strip_root_namespace($classname) { $begin = substr($classname, 0, strlen($this->root_namespace)); if( $begin == $this->root_namespace ) $classname = substr($classname, strlen($this->root_namespace)); return $classname; } /** * Create the path to a class file. * * Any namespace prepended to the class name is split on '\', the * namespace levels are used to indicate directory names. * * @param string $classname The name of the class to create the file path of. */ function create_path($classname) { $namespaces = array_filter(explode('\\', $classname)); $dirs = array_map('self::classname_to_filename', $namespaces); $path = $this->root_directory; if( count($dirs) > 1 ) $path .= implode('/', array_slice($dirs, 0, count($dirs) - 1)).'/'; $path .= end($dirs).'.php'; return strtolower($path); } /** * Load a class. * * Any namespace prepended to the class name is split on '\', the * namespace levels are used to indicate directory names. * * @param string $classname The name of the class to load, including pepended namespace. * @param bool $throw Whether to throw an exception if the class file does not exist. * @return bool * @throws FileNotFoundError If the class file does not exist. */ function load_class($classname, $throw=null) { $classname = $this->strip_root_namespace($classname); $path = $this->create_path($classname); if( !file_exists($path) ) { if( $throw || ($throw === null && $this->throw_errors) ) throw new FileNotFoundError($path); return false; } require_once $path; return true; } /** * Register the autoloader object to the SPL autoload stack. * * @param bool $prepend Whether to prepend the autoloader function to * the stack, instead of appending it. */ function register($prepend=false) { spl_autoload_register(array($this, 'load_class'), true, $prepend); } } ?>