Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
77 / 77
100.00% covered (success)
100.00%
20 / 20
CRAP
100.00% covered (success)
100.00%
1 / 1
ClassLoader
100.00% covered (success)
100.00%
77 / 77
100.00% covered (success)
100.00%
20 / 20
41
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 register
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 unregister
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPsr0Prefixes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPsr4Prefixes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getClassMap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addClassMap
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 addClassMapFromFile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addClassMapFromDir
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setClassMapAuthoritative
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isClassMapAuthoritative
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 addPsr4
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 addPsr0
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPsr0
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPsr4
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 findFile
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
10
 loadClass
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 __invoke
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Loader;
15
16/**
17 * Loader class
18 *
19 * @category   Pop
20 * @package    Pop\Loader
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    3.1.0
25 */
26class ClassLoader
27{
28
29    /**
30     * PSR-0 prefixes
31     * @var array
32     */
33    protected $psr0 = [];
34
35    /**
36     * PSR-4 prefixes
37     * @var array
38     */
39    protected $psr4 = [];
40
41    /**
42     * Class map array
43     * @var array
44     */
45    protected $classMap = [];
46
47    /**
48     * Class map authoritative flag
49     * @var boolean
50     */
51    protected $classMapAuthoritative = false;
52
53    /**
54     * Constructor
55     *
56     * Instantiate the class loader object
57     *
58     * @param  boolean $self
59     * @param  boolean $prepend
60     * @param  boolean $throw
61     */
62    public function __construct($self = true, $prepend = false, $throw = true)
63    {
64        if ($self) {
65            $this->addPsr4('Pop\Loader\\', __DIR__);
66            $this->register($prepend, $throw);
67        }
68    }
69
70    /**
71     * Register this instance with the autoload stack
72     *
73     * @param  boolean $prepend
74     * @param  boolean $throw
75     * @return ClassLoader
76     */
77    public function register($prepend, $throw = true)
78    {
79        spl_autoload_register($this, (bool)$throw, (bool)$prepend);
80        return $this;
81    }
82
83    /**
84     * Unregister this instance with the autoload stack
85     *
86     * @return ClassLoader
87     */
88    public function unregister()
89    {
90        spl_autoload_unregister($this);
91        return $this;
92    }
93
94    /**
95     * Get the PSR-0 prefixes
96     *
97     * @return array
98     */
99    public function getPsr0Prefixes()
100    {
101        return array_keys($this->psr0);
102    }
103
104    /**
105     * Get the PSR-4 prefixes
106     *
107     * @return array
108     */
109    public function getPsr4Prefixes()
110    {
111        return array_keys($this->psr4);
112    }
113
114    /**
115     * Get the class map array
116     *
117     * @return array
118     */
119    public function getClassMap()
120    {
121        return $this->classMap;
122    }
123
124    /**
125     * Add a class map array
126     *
127     * @param  array|ClassMapper $map
128     * @throws Exception
129     * @return ClassLoader
130     */
131    public function addClassMap($map)
132    {
133        if (!is_array($map) && !($map instanceof ClassMapper)) {
134            throw new \InvalidArgumentException(
135                'Error: The $map parameter must be a class map array or an instance of Pop\Loader\ClassMapper.'
136            );
137        }
138
139        $this->classMap = ($map instanceof ClassMapper) ?
140            array_merge($this->classMap, $map->getClassMap()) : array_merge($this->classMap, $map);
141
142        return $this;
143    }
144
145    /**
146     * Add a class map from file
147     *
148     * @param  string $file
149     * @throws Exception
150     * @return ClassLoader
151     */
152    public function addClassMapFromFile($file)
153    {
154        if (!file_exists($file)) {
155            throw new Exception('That class map file does not exist.');
156        }
157
158        $classMap = include $file;
159
160        return $this->addClassMap($classMap);
161    }
162
163    /**
164     * Generate and add a class map from directory
165     *
166     * @param  string $dir
167     * @throws Exception
168     * @return ClassLoader
169     */
170    public function addClassMapFromDir($dir)
171    {
172        if (!file_exists($dir)) {
173            throw new Exception('That class map directory does not exist.');
174        }
175
176        $map      = new ClassMapper($dir);
177        $classMap = $map->getClassMap();
178
179        return $this->addClassMap($classMap);
180    }
181
182    /**
183     * Set the class map as the authoritative loader, halting any searches via prefixes
184     *
185     * @param  boolean $authoritative
186     * @return ClassLoader
187     */
188    public function setClassMapAuthoritative($authoritative)
189    {
190        $this->classMapAuthoritative = (bool)$authoritative;
191        return $this;
192    }
193
194    /**
195     * Determine if the class map is the authoritative loader
196     *
197     * @return boolean
198     */
199    public function isClassMapAuthoritative()
200    {
201        return $this->classMapAuthoritative;
202    }
203
204    /**
205     * Add a PSR-0 prefix and directory location to the autoloader instance
206     *
207     * @param  string  $prefix
208     * @param  string  $directory
209     * @param  boolean $prepend
210     * @throws Exception
211     * @return ClassLoader
212     */
213    public function add($prefix, $directory, $prepend = false)
214    {
215        $dir = realpath($directory);
216
217        if ($dir === false) {
218            throw new Exception('That directory does not exist.');
219        }
220
221        if ($prepend) {
222            $this->psr0 = array_merge([$prefix => $dir], $this->psr0);
223        } else {
224            $this->psr0[$prefix] = $dir;
225        }
226
227        return $this;
228    }
229
230    /**
231     * Add a PSR-4 prefix and directory location to the autoloader instance
232     *
233     * @param  string  $prefix
234     * @param  string  $directory
235     * @param  boolean $prepend
236     * @throws Exception
237     * @return ClassLoader
238     */
239    public function addPsr4($prefix, $directory, $prepend = false)
240    {
241        $dir = realpath($directory);
242
243        if ($dir === false) {
244            throw new Exception('That directory does not exist.');
245        }
246
247        if (substr($prefix, -1) != '\\') {
248            throw new Exception('The PSR-4 prefix must end with a namespace separator.');
249        }
250
251        if ($prepend) {
252            $this->psr4 = array_merge([$prefix => $dir], $this->psr4);
253        } else {
254            $this->psr4[$prefix] = $dir;
255        }
256
257        return $this;
258    }
259
260    /**
261     * Alias to add()
262     *
263     * @param  string  $prefix
264     * @param  string  $directory
265     * @param  boolean $prepend
266     * @return ClassLoader
267     */
268    public function addPsr0($prefix, $directory, $prepend = false)
269    {
270        return $this->add($prefix, $directory, $prepend);
271    }
272
273    /**
274     * Alias to add()
275     *
276     * @param  string  $prefix
277     * @param  string  $directory
278     * @param  boolean $prepend
279     * @return ClassLoader
280     */
281    public function set($prefix, $directory, $prepend = false)
282    {
283        return $this->add($prefix, $directory, $prepend);
284    }
285
286    /**
287     * Alias to add()
288     *
289     * @param  string  $prefix
290     * @param  string  $directory
291     * @param  boolean $prepend
292     * @return ClassLoader
293     */
294    public function setPsr0($prefix, $directory, $prepend = false)
295    {
296        return $this->add($prefix, $directory, $prepend);
297    }
298
299    /**
300     * Alias to addPsr4()
301     *
302     * @param  string  $prefix
303     * @param  string  $directory
304     * @param  boolean $prepend
305     * @return ClassLoader
306     */
307    public function setPsr4($prefix, $directory, $prepend = false)
308    {
309        return $this->addPsr4($prefix, $directory, $prepend);
310    }
311
312    /**
313     * Find the class file
314     *
315     * @param  string $class
316     * @return mixed
317     */
318    public function findFile($class)
319    {
320        $psr4Prefix = null;
321        $psr0Prefix = null;
322        $separator  = (strpos($class, '\\') !== false) ? '\\' : '_';
323
324        // Check the class map for the class
325        if (array_key_exists($class, $this->classMap)) {
326            return realpath($this->classMap[$class]);
327        }
328
329        // If class map is the authoritative loader, stop searching
330        if ($this->classMapAuthoritative) {
331            return false;
332        }
333
334        // Sort array by key length, descending
335        array_multisort(array_map('strlen', array_keys($this->psr4)), SORT_DESC, $this->psr4);
336
337        // Try and detect a PSR-4 prefix
338        foreach ($this->psr4 as $key => $value) {
339            if (substr($class, 0, strlen($key)) == $key) {
340                $psr4Prefix = $key;
341                break;
342            }
343        }
344
345        if (null !== $psr4Prefix) {
346            $psr4ClassFile = str_replace($separator, DIRECTORY_SEPARATOR, substr($class, strlen($psr4Prefix))) . '.php';
347            return realpath($this->psr4[$psr4Prefix] . DIRECTORY_SEPARATOR . $psr4ClassFile);
348        }
349
350        // Sort array by key length, descending
351        array_multisort(array_map('strlen', array_keys($this->psr0)), SORT_DESC, $this->psr0);
352
353        // Try and detect a PSR-0 prefix
354        $psr0ClassFile = str_replace($separator, DIRECTORY_SEPARATOR, $class) . '.php';
355        foreach ($this->psr0 as $key => $value) {
356            if (substr($class, 0, strlen($key)) == $key) {
357                $psr0Prefix = $key;
358                break;
359            }
360        }
361        if (null !== $psr0Prefix) {
362            return realpath($this->psr0[$psr0Prefix] . DIRECTORY_SEPARATOR . $psr0ClassFile);
363        }
364
365        // Else, nothing found, return false
366        return false;
367    }
368
369    /**
370     * Find and load the class file
371     *
372     * @param  string $class
373     * @return boolean
374     */
375    public function loadClass($class)
376    {
377        $classFile = $this->findFile($class);
378        if ($classFile !== false) {
379            include $classFile;
380            return true;
381        } else {
382            return false;
383        }
384    }
385
386    /**
387     * Invoke the class
388     *
389     * @param  string $class
390     * @return void
391     */
392    public function __invoke($class)
393    {
394        $this->loadClass($class);
395    }
396
397}