ZF-10024: allow using closures as autoloaders
[zend/radio.git] / library / Zend / Loader / Autoloader.php
blob98e262aade2047f3fefe92c93e6b8e0af33dbd3d
1 <?php
2 /**
3 * Zend Framework
5 * LICENSE
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
15 * @category Zend
16 * @package Zend_Loader
17 * @subpackage Autoloader
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
19 * @version $Id$
20 * @license http://framework.zend.com/license/new-bsd New BSD License
23 /** Zend_Loader */
24 require_once 'Zend/Loader.php';
26 /**
27 * Autoloader stack and namespace autoloader
29 * @uses Zend_Loader_Autoloader
30 * @package Zend_Loader
31 * @subpackage Autoloader
32 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
33 * @license http://framework.zend.com/license/new-bsd New BSD License
35 class Zend_Loader_Autoloader
37 /**
38 * @var Zend_Loader_Autoloader Singleton instance
40 protected static $_instance;
42 /**
43 * @var array Concrete autoloader callback implementations
45 protected $_autoloaders = array();
47 /**
48 * @var array Default autoloader callback
50 protected $_defaultAutoloader = array('Zend_Loader', 'loadClass');
52 /**
53 * @var bool Whether or not to act as a fallback autoloader
55 protected $_fallbackAutoloader = false;
57 /**
58 * @var array Callback for internal autoloader implementation
60 protected $_internalAutoloader;
62 /**
63 * @var array Supported namespaces 'Zend' and 'ZendX' by default.
65 protected $_namespaces = array(
66 'Zend_' => true,
67 'ZendX_' => true,
70 /**
71 * @var array Namespace-specific autoloaders
73 protected $_namespaceAutoloaders = array();
75 /**
76 * @var bool Whether or not to suppress file not found warnings
78 protected $_suppressNotFoundWarnings = false;
80 /**
81 * @var null|string
83 protected $_zfPath;
85 /**
86 * Retrieve singleton instance
88 * @return Zend_Loader_Autoloader
90 public static function getInstance()
92 if (null === self::$_instance) {
93 self::$_instance = new self();
95 return self::$_instance;
98 /**
99 * Reset the singleton instance
101 * @return void
103 public static function resetInstance()
105 self::$_instance = null;
109 * Autoload a class
111 * @param string $class
112 * @return bool
114 public static function autoload($class)
116 $self = self::getInstance();
118 foreach ($self->getClassAutoloaders($class) as $autoloader) {
119 if ($autoloader instanceof Zend_Loader_Autoloader_Interface) {
120 if ($autoloader->autoload($class)) {
121 return true;
123 } elseif (is_array($autoloader)) {
124 if (call_user_func($autoloader, $class)) {
125 return true;
127 } elseif (is_string($autoloader) || is_callable($autoloader)) {
128 if ($autoloader($class)) {
129 return true;
134 return false;
138 * Set the default autoloader implementation
140 * @param string|array $callback PHP callback
141 * @return void
143 public function setDefaultAutoloader($callback)
145 if (!is_callable($callback)) {
146 throw new Zend_Loader_Exception('Invalid callback specified for default autoloader');
149 $this->_defaultAutoloader = $callback;
150 return $this;
154 * Retrieve the default autoloader callback
156 * @return string|array PHP Callback
158 public function getDefaultAutoloader()
160 return $this->_defaultAutoloader;
164 * Set several autoloader callbacks at once
166 * @param array $autoloaders Array of PHP callbacks (or Zend_Loader_Autoloader_Interface implementations) to act as autoloaders
167 * @return Zend_Loader_Autoloader
169 public function setAutoloaders(array $autoloaders)
171 $this->_autoloaders = $autoloaders;
172 return $this;
176 * Get attached autoloader implementations
178 * @return array
180 public function getAutoloaders()
182 return $this->_autoloaders;
186 * Return all autoloaders for a given namespace
188 * @param string $namespace
189 * @return array
191 public function getNamespaceAutoloaders($namespace)
193 $namespace = (string) $namespace;
194 if (!array_key_exists($namespace, $this->_namespaceAutoloaders)) {
195 return array();
197 return $this->_namespaceAutoloaders[$namespace];
201 * Register a namespace to autoload
203 * @param string|array $namespace
204 * @return Zend_Loader_Autoloader
206 public function registerNamespace($namespace)
208 if (is_string($namespace)) {
209 $namespace = (array) $namespace;
210 } elseif (!is_array($namespace)) {
211 throw new Zend_Loader_Exception('Invalid namespace provided');
214 foreach ($namespace as $ns) {
215 if (!isset($this->_namespaces[$ns])) {
216 $this->_namespaces[$ns] = true;
219 return $this;
223 * Unload a registered autoload namespace
225 * @param string|array $namespace
226 * @return Zend_Loader_Autoloader
228 public function unregisterNamespace($namespace)
230 if (is_string($namespace)) {
231 $namespace = (array) $namespace;
232 } elseif (!is_array($namespace)) {
233 throw new Zend_Loader_Exception('Invalid namespace provided');
236 foreach ($namespace as $ns) {
237 if (isset($this->_namespaces[$ns])) {
238 unset($this->_namespaces[$ns]);
241 return $this;
245 * Get a list of registered autoload namespaces
247 * @return array
249 public function getRegisteredNamespaces()
251 return array_keys($this->_namespaces);
254 public function setZfPath($spec, $version = 'latest')
256 $path = $spec;
257 if (is_array($spec)) {
258 if (!isset($spec['path'])) {
259 throw new Zend_Loader_Exception('No path specified for ZF');
261 $path = $spec['path'];
262 if (isset($spec['version'])) {
263 $version = $spec['version'];
267 $this->_zfPath = $this->_getVersionPath($path, $version);
268 set_include_path(implode(PATH_SEPARATOR, array(
269 $this->_zfPath,
270 get_include_path(),
271 )));
272 return $this;
275 public function getZfPath()
277 return $this->_zfPath;
281 * Get or set the value of the "suppress not found warnings" flag
283 * @param null|bool $flag
284 * @return bool|Zend_Loader_Autoloader Returns boolean if no argument is passed, object instance otherwise
286 public function suppressNotFoundWarnings($flag = null)
288 if (null === $flag) {
289 return $this->_suppressNotFoundWarnings;
291 $this->_suppressNotFoundWarnings = (bool) $flag;
292 return $this;
296 * Indicate whether or not this autoloader should be a fallback autoloader
298 * @param bool $flag
299 * @return Zend_Loader_Autoloader
301 public function setFallbackAutoloader($flag)
303 $this->_fallbackAutoloader = (bool) $flag;
304 return $this;
308 * Is this instance acting as a fallback autoloader?
310 * @return bool
312 public function isFallbackAutoloader()
314 return $this->_fallbackAutoloader;
318 * Get autoloaders to use when matching class
320 * Determines if the class matches a registered namespace, and, if so,
321 * returns only the autoloaders for that namespace. Otherwise, it returns
322 * all non-namespaced autoloaders.
324 * @param string $class
325 * @return array Array of autoloaders to use
327 public function getClassAutoloaders($class)
329 $namespace = false;
330 $autoloaders = array();
332 // Add concrete namespaced autoloaders
333 foreach (array_keys($this->_namespaceAutoloaders) as $ns) {
334 if ('' == $ns) {
335 continue;
337 if (0 === strpos($class, $ns)) {
338 $namespace = $ns;
339 $autoloaders = $autoloaders + $this->getNamespaceAutoloaders($ns);
340 break;
344 // Add internal namespaced autoloader
345 foreach ($this->getRegisteredNamespaces() as $ns) {
346 if (0 === strpos($class, $ns)) {
347 $namespace = $ns;
348 $autoloaders[] = $this->_internalAutoloader;
349 break;
353 // Add non-namespaced autoloaders
354 $autoloaders = $autoloaders + $this->getNamespaceAutoloaders('');
356 // Add fallback autoloader
357 if (!$namespace && $this->isFallbackAutoloader()) {
358 $autoloaders[] = $this->_internalAutoloader;
361 return $autoloaders;
365 * Add an autoloader to the beginning of the stack
367 * @param object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
368 * @param string|array $namespace Specific namespace(s) under which to register callback
369 * @return Zend_Loader_Autoloader
371 public function unshiftAutoloader($callback, $namespace = '')
373 $autoloaders = $this->getAutoloaders();
374 array_unshift($autoloaders, $callback);
375 $this->setAutoloaders($autoloaders);
377 $namespace = (array) $namespace;
378 foreach ($namespace as $ns) {
379 $autoloaders = $this->getNamespaceAutoloaders($ns);
380 array_unshift($autoloaders, $callback);
381 $this->_setNamespaceAutoloaders($autoloaders, $ns);
384 return $this;
388 * Append an autoloader to the autoloader stack
390 * @param object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
391 * @param string|array $namespace Specific namespace(s) under which to register callback
392 * @return Zend_Loader_Autoloader
394 public function pushAutoloader($callback, $namespace = '')
396 $autoloaders = $this->getAutoloaders();
397 array_push($autoloaders, $callback);
398 $this->setAutoloaders($autoloaders);
400 $namespace = (array) $namespace;
401 foreach ($namespace as $ns) {
402 $autoloaders = $this->getNamespaceAutoloaders($ns);
403 array_push($autoloaders, $callback);
404 $this->_setNamespaceAutoloaders($autoloaders, $ns);
407 return $this;
411 * Remove an autoloader from the autoloader stack
413 * @param object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
414 * @param null|string|array $namespace Specific namespace(s) from which to remove autoloader
415 * @return Zend_Loader_Autoloader
417 public function removeAutoloader($callback, $namespace = null)
419 if (null === $namespace) {
420 $autoloaders = $this->getAutoloaders();
421 if (false !== ($index = array_search($callback, $autoloaders, true))) {
422 unset($autoloaders[$index]);
423 $this->setAutoloaders($autoloaders);
426 foreach ($this->_namespaceAutoloaders as $ns => $autoloaders) {
427 if (false !== ($index = array_search($callback, $autoloaders, true))) {
428 unset($autoloaders[$index]);
429 $this->_setNamespaceAutoloaders($autoloaders, $ns);
432 } else {
433 $namespace = (array) $namespace;
434 foreach ($namespace as $ns) {
435 $autoloaders = $this->getNamespaceAutoloaders($ns);
436 if (false !== ($index = array_search($callback, $autoloaders, true))) {
437 unset($autoloaders[$index]);
438 $this->_setNamespaceAutoloaders($autoloaders, $ns);
443 return $this;
447 * Constructor
449 * Registers instance with spl_autoload stack
451 * @return void
453 protected function __construct()
455 spl_autoload_register(array(__CLASS__, 'autoload'));
456 $this->_internalAutoloader = array($this, '_autoload');
460 * Internal autoloader implementation
462 * @param string $class
463 * @return bool
465 protected function _autoload($class)
467 $callback = $this->getDefaultAutoloader();
468 try {
469 if ($this->suppressNotFoundWarnings()) {
470 @call_user_func($callback, $class);
471 } else {
472 call_user_func($callback, $class);
474 return $class;
475 } catch (Zend_Exception $e) {
476 return false;
481 * Set autoloaders for a specific namespace
483 * @param array $autoloaders
484 * @param string $namespace
485 * @return Zend_Loader_Autoloader
487 protected function _setNamespaceAutoloaders(array $autoloaders, $namespace = '')
489 $namespace = (string) $namespace;
490 $this->_namespaceAutoloaders[$namespace] = $autoloaders;
491 return $this;
495 * Retrieve the filesystem path for the requested ZF version
497 * @param string $path
498 * @param string $version
499 * @return void
501 protected function _getVersionPath($path, $version)
503 $type = $this->_getVersionType($version);
505 if ($type == 'latest') {
506 $version = 'latest';
509 $availableVersions = $this->_getAvailableVersions($path, $version);
510 if (empty($availableVersions)) {
511 throw new Zend_Loader_Exception('No valid ZF installations discovered');
514 $matchedVersion = array_pop($availableVersions);
515 return $matchedVersion;
519 * Retrieve the ZF version type
521 * @param string $version
522 * @return string "latest", "major", "minor", or "specific"
523 * @throws Zend_Loader_Exception if version string contains too many dots
525 protected function _getVersionType($version)
527 if (strtolower($version) == 'latest') {
528 return 'latest';
531 $parts = explode('.', $version);
532 $count = count($parts);
533 if (1 == $count) {
534 return 'major';
536 if (2 == $count) {
537 return 'minor';
539 if (3 < $count) {
540 throw new Zend_Loader_Exception('Invalid version string provided');
542 return 'specific';
546 * Get available versions for the version type requested
548 * @param string $path
549 * @param string $version
550 * @return array
552 protected function _getAvailableVersions($path, $version)
554 if (!is_dir($path)) {
555 throw new Zend_Loader_Exception('Invalid ZF path provided');
558 $path = rtrim($path, '/');
559 $path = rtrim($path, '\\');
560 $versionLen = strlen($version);
561 $versions = array();
562 $dirs = glob("$path/*", GLOB_ONLYDIR);
563 foreach ($dirs as $dir) {
564 $dirName = substr($dir, strlen($path) + 1);
565 if (!preg_match('/^(?:ZendFramework-)?(\d+\.\d+\.\d+((a|b|pl|pr|p|rc)\d+)?)(?:-minimal)?$/i', $dirName, $matches)) {
566 continue;
569 $matchedVersion = $matches[1];
571 if (('latest' == $version)
572 || ((strlen($matchedVersion) >= $versionLen)
573 && (0 === strpos($matchedVersion, $version)))
575 $versions[$matchedVersion] = $dir . '/library';
579 uksort($versions, 'version_compare');
580 return $versions;