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