1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Loader; 11 12// Grab SplAutoloader interface 13require_once __DIR__ . '/SplAutoloader.php'; 14 15/** 16 * PSR-0 compliant autoloader 17 * 18 * Allows autoloading both namespaced and vendor-prefixed classes. Class 19 * lookups are performed on the filesystem. If a class file for the referenced 20 * class is not found, a PHP warning will be raised by include(). 21 */ 22class StandardAutoloader implements SplAutoloader 23{ 24 const NS_SEPARATOR = '\\'; 25 const PREFIX_SEPARATOR = '_'; 26 const LOAD_NS = 'namespaces'; 27 const LOAD_PREFIX = 'prefixes'; 28 const ACT_AS_FALLBACK = 'fallback_autoloader'; 29 const AUTOREGISTER_ZF = 'autoregister_zf'; 30 31 /** 32 * @var array Namespace/directory pairs to search; ZF library added by default 33 */ 34 protected $namespaces = array(); 35 36 /** 37 * @var array Prefix/directory pairs to search 38 */ 39 protected $prefixes = array(); 40 41 /** 42 * @var bool Whether or not the autoloader should also act as a fallback autoloader 43 */ 44 protected $fallbackAutoloaderFlag = false; 45 46 /** 47 * Constructor 48 * 49 * @param null|array|\Traversable $options 50 */ 51 public function __construct($options = null) 52 { 53 if (null !== $options) { 54 $this->setOptions($options); 55 } 56 } 57 58 /** 59 * Configure autoloader 60 * 61 * Allows specifying both "namespace" and "prefix" pairs, using the 62 * following structure: 63 * <code> 64 * array( 65 * 'namespaces' => array( 66 * 'Zend' => '/path/to/Zend/library', 67 * 'Doctrine' => '/path/to/Doctrine/library', 68 * ), 69 * 'prefixes' => array( 70 * 'Phly_' => '/path/to/Phly/library', 71 * ), 72 * 'fallback_autoloader' => true, 73 * ) 74 * </code> 75 * 76 * @param array|\Traversable $options 77 * @throws Exception\InvalidArgumentException 78 * @return StandardAutoloader 79 */ 80 public function setOptions($options) 81 { 82 if (!is_array($options) && !($options instanceof \Traversable)) { 83 require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 84 throw new Exception\InvalidArgumentException('Options must be either an array or Traversable'); 85 } 86 87 foreach ($options as $type => $pairs) { 88 switch ($type) { 89 case self::AUTOREGISTER_ZF: 90 if ($pairs) { 91 $this->registerNamespace('Zend', dirname(__DIR__)); 92 $this->registerNamespace('ZendXml', dirname(dirname((__DIR__))) . DIRECTORY_SEPARATOR . 'ZendXml'); 93 } 94 break; 95 case self::LOAD_NS: 96 if (is_array($pairs) || $pairs instanceof \Traversable) { 97 $this->registerNamespaces($pairs); 98 } 99 break; 100 case self::LOAD_PREFIX: 101 if (is_array($pairs) || $pairs instanceof \Traversable) { 102 $this->registerPrefixes($pairs); 103 } 104 break; 105 case self::ACT_AS_FALLBACK: 106 $this->setFallbackAutoloader($pairs); 107 break; 108 default: 109 // ignore 110 } 111 } 112 return $this; 113 } 114 115 /** 116 * Set flag indicating fallback autoloader status 117 * 118 * @param bool $flag 119 * @return StandardAutoloader 120 */ 121 public function setFallbackAutoloader($flag) 122 { 123 $this->fallbackAutoloaderFlag = (bool) $flag; 124 return $this; 125 } 126 127 /** 128 * Is this autoloader acting as a fallback autoloader? 129 * 130 * @return bool 131 */ 132 public function isFallbackAutoloader() 133 { 134 return $this->fallbackAutoloaderFlag; 135 } 136 137 /** 138 * Register a namespace/directory pair 139 * 140 * @param string $namespace 141 * @param string $directory 142 * @return StandardAutoloader 143 */ 144 public function registerNamespace($namespace, $directory) 145 { 146 $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR; 147 $this->namespaces[$namespace] = $this->normalizeDirectory($directory); 148 return $this; 149 } 150 151 /** 152 * Register many namespace/directory pairs at once 153 * 154 * @param array $namespaces 155 * @throws Exception\InvalidArgumentException 156 * @return StandardAutoloader 157 */ 158 public function registerNamespaces($namespaces) 159 { 160 if (!is_array($namespaces) && !$namespaces instanceof \Traversable) { 161 require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 162 throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable'); 163 } 164 165 foreach ($namespaces as $namespace => $directory) { 166 $this->registerNamespace($namespace, $directory); 167 } 168 return $this; 169 } 170 171 /** 172 * Register a prefix/directory pair 173 * 174 * @param string $prefix 175 * @param string $directory 176 * @return StandardAutoloader 177 */ 178 public function registerPrefix($prefix, $directory) 179 { 180 $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR; 181 $this->prefixes[$prefix] = $this->normalizeDirectory($directory); 182 return $this; 183 } 184 185 /** 186 * Register many namespace/directory pairs at once 187 * 188 * @param array $prefixes 189 * @throws Exception\InvalidArgumentException 190 * @return StandardAutoloader 191 */ 192 public function registerPrefixes($prefixes) 193 { 194 if (!is_array($prefixes) && !$prefixes instanceof \Traversable) { 195 require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 196 throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable'); 197 } 198 199 foreach ($prefixes as $prefix => $directory) { 200 $this->registerPrefix($prefix, $directory); 201 } 202 return $this; 203 } 204 205 /** 206 * Defined by Autoloadable; autoload a class 207 * 208 * @param string $class 209 * @return false|string 210 */ 211 public function autoload($class) 212 { 213 $isFallback = $this->isFallbackAutoloader(); 214 if (false !== strpos($class, self::NS_SEPARATOR)) { 215 if ($this->loadClass($class, self::LOAD_NS)) { 216 return $class; 217 } elseif ($isFallback) { 218 return $this->loadClass($class, self::ACT_AS_FALLBACK); 219 } 220 return false; 221 } 222 if (false !== strpos($class, self::PREFIX_SEPARATOR)) { 223 if ($this->loadClass($class, self::LOAD_PREFIX)) { 224 return $class; 225 } elseif ($isFallback) { 226 return $this->loadClass($class, self::ACT_AS_FALLBACK); 227 } 228 return false; 229 } 230 if ($isFallback) { 231 return $this->loadClass($class, self::ACT_AS_FALLBACK); 232 } 233 return false; 234 } 235 236 /** 237 * Register the autoloader with spl_autoload 238 * 239 * @return void 240 */ 241 public function register() 242 { 243 spl_autoload_register(array($this, 'autoload')); 244 } 245 246 /** 247 * Transform the class name to a filename 248 * 249 * @param string $class 250 * @param string $directory 251 * @return string 252 */ 253 protected function transformClassNameToFilename($class, $directory) 254 { 255 // $class may contain a namespace portion, in which case we need 256 // to preserve any underscores in that portion. 257 $matches = array(); 258 preg_match('/(?P<namespace>.+\\\)?(?P<class>[^\\\]+$)/', $class, $matches); 259 260 $class = (isset($matches['class'])) ? $matches['class'] : ''; 261 $namespace = (isset($matches['namespace'])) ? $matches['namespace'] : ''; 262 263 return $directory 264 . str_replace(self::NS_SEPARATOR, '/', $namespace) 265 . str_replace(self::PREFIX_SEPARATOR, '/', $class) 266 . '.php'; 267 } 268 269 /** 270 * Load a class, based on its type (namespaced or prefixed) 271 * 272 * @param string $class 273 * @param string $type 274 * @return bool|string 275 * @throws Exception\InvalidArgumentException 276 */ 277 protected function loadClass($class, $type) 278 { 279 if (!in_array($type, array(self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK))) { 280 require_once __DIR__ . '/Exception/InvalidArgumentException.php'; 281 throw new Exception\InvalidArgumentException(); 282 } 283 284 // Fallback autoloading 285 if ($type === self::ACT_AS_FALLBACK) { 286 // create filename 287 $filename = $this->transformClassNameToFilename($class, ''); 288 $resolvedName = stream_resolve_include_path($filename); 289 if ($resolvedName !== false) { 290 return include $resolvedName; 291 } 292 return false; 293 } 294 295 // Namespace and/or prefix autoloading 296 foreach ($this->$type as $leader => $path) { 297 if (0 === strpos($class, $leader)) { 298 // Trim off leader (namespace or prefix) 299 $trimmedClass = substr($class, strlen($leader)); 300 301 // create filename 302 $filename = $this->transformClassNameToFilename($trimmedClass, $path); 303 if (file_exists($filename)) { 304 return include $filename; 305 } 306 } 307 } 308 return false; 309 } 310 311 /** 312 * Normalize the directory to include a trailing directory separator 313 * 314 * @param string $directory 315 * @return string 316 */ 317 protected function normalizeDirectory($directory) 318 { 319 $last = $directory[strlen($directory) - 1]; 320 if (in_array($last, array('/', '\\'))) { 321 $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR; 322 return $directory; 323 } 324 $directory .= DIRECTORY_SEPARATOR; 325 return $directory; 326 } 327} 328