Passed
Push — master ( d25cd5...5cf75b )
by Nikolaos
03:08
created

Loader::processFileNameSpaces()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.3731

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 2
dl 0
loc 18
ccs 10
cts 14
cp 0.7143
crap 4.3731
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Phalcon\Autoload;
15
16
use function array_merge;
17
use function array_unique;
18
use function array_values;
19
use function file_exists;
20
use function is_array;
21
use function is_string;
22
use function rtrim;
23
use function sha1;
24
use function spl_autoload_register;
25
use function spl_autoload_unregister;
26
use function str_replace;
27
use function strrpos;
28
use function substr;
29
use function trim;
30
31
use const DIRECTORY_SEPARATOR;
32
33
/**
34
 * Class Logger
35
 *
36
 * @package Phalcon\Autoload
37
 *
38
 * @property array $classes
39
 * @property array $debug
40
 * @property array $extensions
41
 * @property array $files
42
 * @property bool  $isRegistered
43
 * @property array $namespaces
44
 */
45
class Loader
46
{
47
    /**
48
     * @var array
49
     */
50
    protected $classes = [];
51
52
    /**
53
     * @var array
54
     */
55
    protected $debug = [];
56
57
    /**
58
     * @var array
59
     */
60
    protected $extensions = [];
61
62
    /**
63
     * @var array
64
     */
65
    protected $files = [];
66
67
    /**
68
     * @var bool
69
     */
70
    protected $isRegistered = false;
71
72
    /**
73
     * @var array
74
     */
75
    protected $namespaces = [];
76
77
    /**
78
     * Loader constructor.
79
     */
80 26
    public function __construct()
81
    {
82 26
        $this->extensions = ['php'];
83 26
    }
84
85
    /**
86
     * Adds a class to the internal collection for the mapping
87
     *
88
     * @param string $name
89
     * @param string $file
90
     *
91
     * @return Loader
92
     */
93 4
    public function addClass(string $name, string $file): Loader
94
    {
95 4
        $this->classes[$name] = $file;
96
97 4
        return $this;
98
    }
99
100
    /**
101
     * Adds an extension for the loaded files
102
     *
103
     * @param string $extension
104
     *
105
     * @return Loader
106
     */
107 4
    public function addExtension(string $extension): Loader
108
    {
109 4
        $extensions   = $this->extensions;
110 4
        $extensions[] = $extension;
111
112 4
        $this->extensions = array_unique($extensions);
113
114 4
        return $this;
115
    }
116
117
    /**
118
     * Adds a file to be added to the loader
119
     *
120
     * @param string $file
121
     *
122
     * @return Loader
123
     */
124 4
    public function addFile(string $file): Loader
125
    {
126 4
        $this->files[sha1($file)] = $file;
127
128 4
        return $this;
129
    }
130
131
    /**
132
     * @param string $namespace
133
     * @param mixed  $directories
134
     * @param bool   $prepend
135
     *
136
     * @return Loader
137
     * @throws Exception
138
     */
139 12
    public function addNamespace(
140
        string $namespace,
141
        $directories,
142
        bool $prepend = false
143
    ): Loader {
144 12
        $ns          = '\\';
145 12
        $ds          = DIRECTORY_SEPARATOR;
146 12
        $namespace   = trim($namespace, $ns) . $ns;
147 12
        $directories = $this->checkDirectories($directories);
148
149
        // initialize the namespace prefix array if needed
150 10
        if (!isset($this->namespaces[$namespace])) {
151 10
            $this->namespaces[$namespace] = [];
152
        }
153
154 10
        $directories = $this->processDirectories($directories, $ds);
155 10
        $source      = ($prepend) ? $directories : $this->namespaces[$namespace];
156 10
        $target      = ($prepend) ? $this->namespaces[$namespace] : $directories;
157
158 10
        $this->namespaces[$namespace] = array_unique(
159 10
            array_merge($source, $target)
160
        );
161
162 10
        return $this;
163
    }
164
165
    /**
166
     * Loads the class based on the class name
167
     *
168
     * @param string $name
169
     *
170
     * @return bool|mixed|string
171
     */
172 12
    public function autoload(string $name)
173
    {
174
        /**
175
         * Debug information
176
         */
177 12
        $this->debug = ['Loading: ' . $name];
178
179
        /**
180
         * Classes
181
         */
182 12
        if (isset($this->classes[$name])) {
183 2
            $file   = $this->classes[$name];
184 2
            $exists = $this->requireFile($file);
185 2
            if ($exists) {
186 2
                $this->debug[] = 'Class: load: ' . $file;
187 2
                return $file;
188
            }
189
        }
190
191 12
        $this->debug[] = 'Class: 404 : ' . $name;
192
193 12
        return $this->processLoadNameSpaces($name);
194
    }
195
196
    /**
197
     * Returns the registered classes array
198
     *
199
     * @return array
200
     */
201 2
    public function getClasses(): array
202
    {
203 2
        return $this->classes;
204
    }
205
206
    /**
207
     * Returns an array with debugging information after the last autoload
208
     *
209
     * @return array
210
     */
211 12
    public function getDebug()
212
    {
213 12
        return $this->debug;
214
    }
215
216
    /**
217
     * Returns the registered extensions array
218
     *
219
     * @return array
220
     */
221 2
    public function getExtensions(): array
222
    {
223 2
        return $this->extensions;
224
    }
225
226
    /**
227
     * Returns the registered files array
228
     *
229
     * @return array
230
     */
231 2
    public function getFiles(): array
232
    {
233 2
        return array_values($this->files);
234
    }
235
236
    /**
237
     * Returns the registered namespaces array
238
     *
239
     * @return array
240
     */
241 2
    public function getNamespaces(): array
242
    {
243 2
        return $this->namespaces;
244
    }
245
246
    /**
247
     * Checks if a file exists and then adds the file by doing virtual require
248
     */
249 4
    public function loadFiles(): void
250
    {
251 4
        foreach ($this->files as $file) {
252
            /**
253
             * Check if the file specified even exists
254
             */
255 2
            $this->requireFile($file);
256
        }
257 4
    }
258
259
    /**
260
     * Sets the classes for the loader. Overwrites existing entries
261
     *
262
     * @param array $classes
263
     *
264
     * @return Loader
265
     */
266 2
    public function setClasses(array $classes): Loader
267
    {
268 2
        $this->classes = [];
269 2
        foreach ($classes as $name => $file) {
270 2
            $this->addClass($name, $file);
271
        }
272
273 2
        return $this;
274
    }
275
276
    /**
277
     * Sets the extensions for the loader. Overwrites existing entries
278
     *
279
     * @param array $extensions
280
     *
281
     * @return Loader
282
     */
283 4
    public function setExtensions(array $extensions): Loader
284
    {
285 4
        $this->extensions = ['php'];
286 4
        foreach ($extensions as $extension) {
287 4
            $this->addExtension($extension);
288
        }
289
290 4
        return $this;
291
    }
292
293
    /**
294
     * Sets all the files that need to be loaded. Overwrites existing files
295
     *
296
     * @param array $files
297
     *
298
     * @return Loader
299
     */
300 2
    public function setFiles(array $files): Loader
301
    {
302 2
        $this->files = [];
303 2
        foreach ($files as $file) {
304 2
            $this->addFile($file);
305
        }
306
307 2
        return $this;
308
    }
309
310
    /**
311
     * Sets the namespaces for the loader. Overwrites existing entries
312
     *
313
     * @param array $namespaces
314
     *
315
     * @return Loader
316
     * @throws Exception
317
     */
318 6
    public function setNamespaces(array $namespaces): Loader
319
    {
320 6
        $this->namespaces = [];
321 6
        foreach ($namespaces as $namespace => $directories) {
322 6
            $this->addNamespace($namespace, $directories);
323
        }
324
325 6
        return $this;
326
    }
327
328
    /**
329
     * Registers this autoloader with SPL.
330
     *
331
     * @param bool $prepend True to prepend to the autoload stack.
332
     */
333 2
    public function register($prepend = false): void
334
    {
335 2
        if (!$this->isRegistered) {
336
337
            /**
338
             * Include all files that are registered
339
             */
340 2
            $this->loadFiles();
341
342 2
            spl_autoload_register(
343 2
                [$this, 'autoload'],
344 2
                true,
345 2
                (bool) $prepend
346
            );
347
348 2
            $this->isRegistered = true;
349
        }
350 2
    }
351
352
    /**
353
     * Unregisters this autoloader from SPL.
354
     */
355 2
    public function unregister(): void
356
    {
357 2
        if ($this->isRegistered) {
358 2
            spl_autoload_unregister([$this, 'autoload']);
359 2
            $this->isRegistered = false;
360
        }
361 2
    }
362
363
    /**
364
     * Search for the file corresponding to the namespaced class and load it
365
     *
366
     * @param string $namespace
367
     * @param string $class
368
     *
369
     * @return bool|string
370
     */
371 10
    protected function loadFile(string $namespace, string $class)
372
    {
373 10
        if (!isset($this->namespaces[$namespace])) {
374 6
            $this->debug[] = 'Load: No folders registered: ' . $namespace;
375
376 6
            return false;
377
        }
378
379 8
        return $this->processFileNameSpaces($namespace, $class);
380
    }
381
382
    /**
383
     * If the file exists, require it and return true; false otherwise
384
     *
385
     * @param string $file The file to require
386
     *
387
     * @return bool
388
     */
389 12
    protected function requireFile(string $file): bool
390
    {
391 12
        if (file_exists($file)) {
392 10
            require $file;
393
394 10
            return true;
395
        }
396
397 8
        return false;
398
    }
399
400
    /**
401
     * @param mixed $directories
402
     *
403
     * @return array
404
     * @throws Exception
405
     */
406 12
    private function checkDirectories($directories): array
407
    {
408 12
        if (!is_string($directories) && !is_array($directories)) {
409 2
            throw new Exception(
410 2
                'The directories parameter is not a string or array'
411
            );
412
        }
413
414 10
        if (is_string($directories)) {
415 10
            $directories = [$directories];
416
        }
417
418 10
        return $directories;
419
    }
420
421
    /**
422
     * @param array  $directories
423
     * @param string $ds
424
     *
425
     * @return array
426
     */
427 10
    private function processDirectories(array $directories, string $ds): array
428
    {
429 10
        foreach ($directories as $key => $directory) {
430 10
            $directories[$key] = rtrim($directory, $ds) . $ds;
431
        }
432
433 10
        return $directories;
434
    }
435
436
    /**
437
     * @param string $namespace
438
     * @param string $class
439
     *
440
     * @return bool|string
441
     */
442 8
    private function processFileNameSpaces(string $namespace, string $class)
443
    {
444 8
        $ns = '\\';
445 8
        $ds = DIRECTORY_SEPARATOR;
446
447 8
        foreach ($this->namespaces[$namespace] as $directory) {
448 8
            foreach ($this->extensions as $extension) {
449 8
                $file = $directory . str_replace($ns, $ds, $class) . '.' . $extension;
450
451 8
                if ($this->requireFile($file)) {
452 6
                    return $file;
453
                }
454
455 6
                $this->debug[] = "Load: 404 : " . $namespace . " - " . $file;
456
            }
457
        }
458
459 2
        return false;
460
    }
461
462
    /**
463
     * @param string $name
464
     *
465
     * @return bool|string
466
     */
467 12
    private function processLoadNameSpaces(string $name)
468
    {
469 12
        $ns = '\\';
470
471
        /**
472
         * Namespaces
473
         *
474
         * Matching in reverse the namespace names in relation to class names
475
         */
476 12
        $namespace = $name;
477 12
        while (false !== $pos = strrpos($namespace, $ns)) {
478
            // retain the trailing namespace separator in the prefix
479 10
            $namespace = substr($name, 0, $pos + 1);
480 10
            $remainder = substr($name, $pos + 1);
481
482 10
            $file = $this->loadFile($namespace, $remainder);
483 10
            if (false !== $file) {
484 6
                $this->debug[] = "Namespace: " . $namespace . " - " . $file;
485
486 6
                return $file;
487
            }
488
489 6
            $namespace = rtrim($namespace, $ns);
490
        }
491
492
        // 404
493 6
        $this->debug[] = "Namespace: 404 : " . $name;
494
495 6
        return false;
496
    }
497
}
498