1: <?php
2: /**
3: * Copyright (c) 2013 PHP Framework Interop Group
4: *
5: * Permission is hereby granted, free of charge, to any person obtaining a copy
6: * of this software and associated documentation files (the "Software"), to deal
7: * in the Software without restriction, including without limitation the rights
8: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9: * copies of the Software, and to permit persons to whom the Software is
10: * furnished to do so, subject to the following conditions:
11: *
12: * The above copyright notice and this permission notice shall be included in
13: * all copies or substantial portions of the Software.
14: *
15: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21: * THE SOFTWARE.
22: */
23:
24: namespace Xoops\Core;
25:
26: /**
27: * A Class Loader that implements the technical interoperability
28: * standards for PHP 5.3 namespaces and class names set forth in PSR-4.
29: * This general-purpose implementation includes the optional functionality
30: * of allowing multiple base directories for a single namespace prefix.
31: *
32: * From the PHP Framework Interop Group
33: *
34: * Examples
35: *
36: * Given a foo-bar package of classes in the file system at the following
37: * paths ...
38: *
39: * /path/to/packages/foo-bar/
40: * src/
41: * Baz.php # Foo\Bar\Baz
42: * Qux/
43: * Quux.php # Foo\Bar\Qux\Quux
44: * tests/
45: * BazTest.php # Foo\Bar\BazTest
46: * Qux/
47: * QuuxTest.php # Foo\Bar\Qux\QuuxTest
48: *
49: * ... add the path to the class files for the \Foo\Bar\ namespace prefix
50: * as follows:
51: *
52: * @code
53: * <?php
54: * // instantiate the loader
55: * $loader = new \Xoops\Core\Psr4ClassLoader;
56: *
57: * // register the autoloader
58: * $loader->register();
59: *
60: * // register the base directories for the namespace prefix
61: * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src');
62: * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests');
63: * @endcode
64: *
65: * The following line would cause the autoloader to attempt to load the
66: * \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php:
67: *
68: * @code
69: * <?php
70: * new \Foo\Bar\Qux\Quux;
71: * @endcode
72: *
73: * The following line would cause the autoloader to attempt to load the
74: * \Foo\Bar\Qux\QuuxTest class from /path/to/packages/foo-bar/tests/Qux/QuuxTest.php:
75: *
76: * @code
77: * <?php
78: * new \Foo\Bar\Qux\QuuxTest;
79: * @endcode
80: *
81: * @category Xoops\Core\Psr4ClassLoader
82: * @package Xoops
83: * @author https://github.com/php-fig/fig-standards/
84: * @author Richard Griffith <richard@geekwright.com>
85: * @copyright 2013 PHP Framework Interop Group
86: * @copyright 2013-2014 XOOPS Project (http://xoops.org)
87: * @license MIT (see above)
88: * @link https://github.com/php-fig/fig-standards/wiki/PSR-4-Example-Implementations
89: * @see http://www.php-fig.org/
90: */
91: class Psr4ClassLoader
92: {
93: /**
94: * An associative array where the key is a namespace prefix and the value
95: * is an array of base directories for classes in that namespace.
96: *
97: * @var array
98: */
99: protected $prefixes = array();
100:
101: /**
102: * addLoader sets all basic options and registers the autoloader
103: *
104: * @param type $namespace namespace
105: * @param mixed $path path(s) to the namespace's directories
106: * Can be string - only one directory
107: * or array of strings - multiple directories
108: *
109: * @return SplClassLoader
110: */
111: public static function addLoader($namespace, $path)
112: {
113: $loaderClass = get_called_class();
114: $loader = new $loaderClass($namespace, $path);
115: if (is_array($path)) {
116: foreach ($path as $pathdir) {
117: $loader->addNamespace($namespace, $pathdir);
118: }
119: } else {
120: $loader->addNamespace($namespace, $path);
121: }
122: $loader->register();
123: return $loader;
124: }
125:
126: /**
127: * Register loader with SPL autoloader stack.
128: *
129: * @return null
130: */
131: public function register()
132: {
133: spl_autoload_register(array($this, 'loadClass'));
134: }
135:
136: /**
137: * Adds a base directory for a namespace prefix.
138: *
139: * @param string $prefix The namespace prefix.
140: * @param string $base_dir Base directory for class files in namespace.
141: * @param bool $prepend If true, prepend the base directory to the
142: * stack instead of appending it; this causes
143: * it to be searched first rather than last.
144: *
145: * @return null
146: */
147: public function addNamespace($prefix, $base_dir, $prepend = false)
148: {
149: // normalize namespace prefix
150: $prefix = trim($prefix, '\\') . '\\';
151:
152: // normalize the base directory with a trailing separator
153: $base_dir = rtrim($base_dir, '/') . DIRECTORY_SEPARATOR;
154: $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
155:
156: // initialize the namespace prefix array
157: if (isset($this->prefixes[$prefix]) === false) {
158: $this->prefixes[$prefix] = array();
159: }
160:
161: // retain the base directory for the namespace prefix
162: if ($prepend) {
163: array_unshift($this->prefixes[$prefix], $base_dir);
164: } else {
165: array_push($this->prefixes[$prefix], $base_dir);
166: }
167: }
168:
169: /**
170: * Loads the class file for a given class name.
171: *
172: * @param string $class The fully-qualified class name.
173: *
174: * @return string|false The mapped file name on success, or boolean false on
175: * failure.
176: */
177: public function loadClass($class)
178: {
179: // the current namespace prefix
180: $prefix = $class;
181:
182: // work backwards through the namespace names of the fully-qualified
183: // class name to find a mapped file name
184: while (false !== $pos = strrpos($prefix, '\\')) {
185: // retain the trailing namespace separator in the prefix
186: $prefix = substr($class, 0, $pos + 1);
187:
188: // the rest is the relative class name
189: $relative_class = substr($class, $pos + 1);
190:
191: // try to load a mapped file for the prefix and relative class
192: $mapped_file = $this->loadMappedFile($prefix, $relative_class);
193: if ($mapped_file !== false) {
194: return $mapped_file;
195: }
196:
197: // remove the trailing namespace separator for the next iteration
198: // of strrpos()
199: $prefix = rtrim($prefix, '\\');
200: }
201:
202: // never found a mapped file
203: return false;
204: }
205:
206: /**
207: * Load the mapped file for a namespace prefix and relative class.
208: *
209: * @param string $prefix The namespace prefix.
210: * @param string $relative_class The relative class name.
211: *
212: * @return false|string Boolean false if no mapped file can be loaded, or the
213: * name of the mapped file that was loaded.
214: */
215: protected function loadMappedFile($prefix, $relative_class)
216: {
217: // are there any base directories for this namespace prefix?
218: if (isset($this->prefixes[$prefix]) === false) {
219: return false;
220: }
221:
222: // look through base directories for this namespace prefix
223: foreach ($this->prefixes[$prefix] as $base_dir) {
224: // replace the namespace prefix with the base directory,
225: // replace namespace separators with directory separators
226: // in the relative class name, append with .php
227: $file = $base_dir
228: . str_replace('\\', '/', $relative_class)
229: . '.php';
230:
231: // if the mapped file exists, require it
232: if ($this->requireFile($file)) {
233: // yes, we're done
234: return $file;
235: }
236: }
237:
238: // never found it
239: return false;
240: }
241:
242: /**
243: * If a file exists, require it from the file system.
244: *
245: * @param string $file The file to require.
246: *
247: * @return bool True if the file exists, false if not.
248: */
249: protected function requireFile($file)
250: {
251: if (file_exists($file)) {
252: require $file;
253:
254: return true;
255: }
256:
257: return false;
258: }
259: }
260: