1: <?php
2:
3: /*
4: * This file is part of Composer.
5: *
6: * (c) Nils Adermann <naderman@naderman.de>
7: * Jordi Boggiano <j.boggiano@seld.be>
8: *
9: * For the full copyright and license information, please view the LICENSE
10: * file that was distributed with this source code.
11: */
12:
13: namespace Composer\Autoload;
14:
15: /**
16: * ClassLoader implements a PSR-0 class loader
17: *
18: * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
19: *
20: * $loader = new \Composer\Autoload\ClassLoader();
21: *
22: * // register classes with namespaces
23: * $loader->add('Symfony\Component', __DIR__.'/component');
24: * $loader->add('Symfony', __DIR__.'/framework');
25: *
26: * // activate the autoloader
27: * $loader->register();
28: *
29: * // to enable searching the include path (eg. for PEAR packages)
30: * $loader->setUseIncludePath(true);
31: *
32: * In this example, if you try to use a class in the Symfony\Component
33: * namespace or one of its children (Symfony\Component\Console for instance),
34: * the autoloader will first look for the class under the component/
35: * directory, and it will then fallback to the framework/ directory if not
36: * found before giving up.
37: *
38: * This class is loosely based on the Symfony UniversalClassLoader.
39: *
40: * @author Fabien Potencier <fabien@symfony.com>
41: * @author Jordi Boggiano <j.boggiano@seld.be>
42: */
43: class ClassLoader
44: {
45: private $prefixes = array();
46: private $fallbackDirs = array();
47: private $useIncludePath = false;
48: private $classMap = array();
49:
50: public function getPrefixes()
51: {
52: return call_user_func_array('array_merge', $this->prefixes);
53: }
54:
55: public function getFallbackDirs()
56: {
57: return $this->fallbackDirs;
58: }
59:
60: public function getClassMap()
61: {
62: return $this->classMap;
63: }
64:
65: /**
66: * @param array $classMap Class to filename map
67: */
68: public function addClassMap(array $classMap)
69: {
70: if ($this->classMap) {
71: $this->classMap = array_merge($this->classMap, $classMap);
72: } else {
73: $this->classMap = $classMap;
74: }
75: }
76:
77: /**
78: * Registers a set of classes, merging with any others previously set.
79: *
80: * @param string $prefix The classes prefix
81: * @param array|string $paths The location(s) of the classes
82: * @param bool $prepend Prepend the location(s)
83: */
84: public function add($prefix, $paths, $prepend = false)
85: {
86: if (!$prefix) {
87: if ($prepend) {
88: $this->fallbackDirs = array_merge(
89: (array) $paths,
90: $this->fallbackDirs
91: );
92: } else {
93: $this->fallbackDirs = array_merge(
94: $this->fallbackDirs,
95: (array) $paths
96: );
97: }
98:
99: return;
100: }
101:
102: $first = $prefix[0];
103: if (!isset($this->prefixes[$first][$prefix])) {
104: $this->prefixes[$first][$prefix] = (array) $paths;
105:
106: return;
107: }
108: if ($prepend) {
109: $this->prefixes[$first][$prefix] = array_merge(
110: (array) $paths,
111: $this->prefixes[$first][$prefix]
112: );
113: } else {
114: $this->prefixes[$first][$prefix] = array_merge(
115: $this->prefixes[$first][$prefix],
116: (array) $paths
117: );
118: }
119: }
120:
121: /**
122: * Registers a set of classes, replacing any others previously set.
123: *
124: * @param string $prefix The classes prefix
125: * @param array|string $paths The location(s) of the classes
126: */
127: public function set($prefix, $paths)
128: {
129: if (!$prefix) {
130: $this->fallbackDirs = (array) $paths;
131:
132: return;
133: }
134: $this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths;
135: }
136:
137: /**
138: * Turns on searching the include path for class files.
139: *
140: * @param bool $useIncludePath
141: */
142: public function setUseIncludePath($useIncludePath)
143: {
144: $this->useIncludePath = $useIncludePath;
145: }
146:
147: /**
148: * Can be used to check if the autoloader uses the include path to check
149: * for classes.
150: *
151: * @return bool
152: */
153: public function getUseIncludePath()
154: {
155: return $this->useIncludePath;
156: }
157:
158: /**
159: * Registers this instance as an autoloader.
160: *
161: * @param bool $prepend Whether to prepend the autoloader or not
162: */
163: public function register($prepend = false)
164: {
165: spl_autoload_register(array($this, 'loadClass'), true, $prepend);
166: }
167:
168: /**
169: * Unregisters this instance as an autoloader.
170: */
171: public function unregister()
172: {
173: spl_autoload_unregister(array($this, 'loadClass'));
174: }
175:
176: /**
177: * Loads the given class or interface.
178: *
179: * @param string $class The name of the class
180: * @return bool|null True if loaded, null otherwise
181: */
182: public function loadClass($class)
183: {
184: if ($file = $this->findFile($class)) {
185: include $file;
186:
187: return true;
188: }
189: }
190:
191: /**
192: * Finds the path to the file where the class is defined.
193: *
194: * @param string $class The name of the class
195: *
196: * @return string|false The path if found, false otherwise
197: */
198: public function findFile($class)
199: {
200: // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
201: if ('\\' == $class[0]) {
202: $class = substr($class, 1);
203: }
204:
205: if (isset($this->classMap[$class])) {
206: return $this->classMap[$class];
207: }
208:
209: if (false !== $pos = strrpos($class, '\\')) {
210: // namespaced class name
211: $classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
212: $className = substr($class, $pos + 1);
213: } else {
214: // PEAR-like class name
215: $classPath = null;
216: $className = $class;
217: }
218:
219: $classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
220:
221: $first = $class[0];
222: if (isset($this->prefixes[$first])) {
223: foreach ($this->prefixes[$first] as $prefix => $dirs) {
224: if (0 === strpos($class, $prefix)) {
225: foreach ($dirs as $dir) {
226: if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
227: return $dir . DIRECTORY_SEPARATOR . $classPath;
228: }
229: }
230: }
231: }
232: }
233:
234: foreach ($this->fallbackDirs as $dir) {
235: if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
236: return $dir . DIRECTORY_SEPARATOR . $classPath;
237: }
238: }
239:
240: if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
241: return $file;
242: }
243:
244: return $this->classMap[$class] = false;
245: }
246: }
247: