Completed
Push — master ( 18a8ee...841d0f )
by
unknown
01:57
created

Autoloader::loadClass()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 1
crap 3
1
<?php
2
/**
3
 * Autoloader
4
 *
5
 * Load namespaces and classes following the WordPress naming convertions.
6
 * https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#naming-conventions
7
 */
8
namespace ClaudioSanches\WPAutoloader;
9
10
/**
11
 * Autoloader.
12
 */
13
class Autoloader
14
{
15
    /**
16
     * An associative array where the key is a namespace prefix and the value
17
     * is an array of base directories for classes in that namespace.
18
     *
19
     * @var array
20
     */
21
    protected $prefixes = [];
22
23
    /**
24
     * Register loader with SPL autoloader stack.
25
     *
26
     * @codeCoverageIgnore
27
     */
28
    public function register()
29
    {
30
        spl_autoload_register([$this, 'loadClass']);
31
    }
32
33
    /**
34
     * Adds a base directory for a namespace prefix.
35
     *
36
     * @param string $prefix  The namespace prefix.
37
     * @param string $baseDir A base directory for class files in the
38
     * namespace.
39
     */
40 5
    public function addNamespace(string $prefix, string $baseDir)
41
    {
42 5
        $prefix  = trim($prefix, '\\') . '\\';
43 5
        $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
44
45 5
        if (false === isset($this->prefixes[$prefix])) {
46 5
            $this->prefixes[$prefix] = [];
47
        }
48
49 5
        array_push($this->prefixes[$prefix], $baseDir);
50 5
    }
51
52
    /**
53
     * Loads the class file for a given class name.
54
     *
55
     * @param  string $class The fully-qualified class name.
56
     * @return string        The mapped file name on success, or emtpy string on
57
     * failure.
58
     */
59 5
    public function loadClass($class): string
60
    {
61 5
        $prefix = $class;
62
63 5
        while (false !== ($position = strrpos($prefix, '\\'))) {
64 5
            $prefix = substr($class, 0, $position + 1);
65 5
            $relativeClass = substr($class, $position + 1);
66
67 5
            if ($mappedFile = $this->loadMappedFile($prefix, $relativeClass)) {
68 3
                return $mappedFile;
69
            }
70
71
            // Remove the trailing namespace separator for the next iteration.
72 2
            $prefix = rtrim($prefix, '\\');
73
        }
74
75 2
        return '';
76
    }
77
78
    /**
79
     * Load the mapped file for a namespace prefix and relative class.
80
     *
81
     * @param string $prefix        The namespace prefix.
82
     * @param string $relativeClass The relative class name.
83
     *
84
     * @return string               Empty string if no mapped file can be
85
     * loaded, or the name of the mapped file that was loaded.
86
     */
87 5
    protected function loadMappedFile($prefix, $relativeClass)
88
    {
89 5
        if (false === isset($this->prefixes[$prefix])) {
90 2
            return '';
91
        }
92
93 4
        $relativeFile = $this->getRelativeFile($relativeClass);
94
95
        // Look through base directories for this namespace prefix.
96 4
        foreach ($this->prefixes[$prefix] as $baseDir) {
97 4
            $file = $baseDir . $relativeFile;
98
99
            // If the mapped file exists, require it.
100 4
            if ($this->requireFile($file)) {
101 4
                return $file;
102
            }
103
        }
104
105 1
        return '';
106
    }
107
108
    /**
109
     * Normalize path using WordPress naming convertions.
110
     *
111
     * @param  string $path Relative class path.
112
     * @return string
113
     */
114 4
    protected function normalizePath(string $path): string
115
    {
116 4
        return str_replace('_', '-', strtolower($path));
117
    }
118
119
    /**
120
     * Get relative file path.
121
     *
122
     * @param  string $relativeClass Relative class.
123
     * @return string
124
     */
125 4
    protected function getRelativeFile(string $relativeClass): string
126
    {
127
        // Class file names should be based on the class name with
128
        // "class-" prepended and the underscores in the class name
129
        // replaced with hyphens.
130 4
        $relative = $this->normalizePath($relativeClass);
131 4
        $pieces   = explode('\\', $relative);
132 4
        $last     = array_pop($pieces);
133 4
        $last     = 'class-' . $last . '.php';
134 4
        $pieces[] = $last;
135
136 4
        return implode(DIRECTORY_SEPARATOR, $pieces);
137
    }
138
139
    /**
140
     * If a file exists, require it from the file system.
141
     *
142
     * @codeCoverageIgnore
143
     *
144
     * @param  string $file The file to require.
145
     * @return bool         True if the file exists, false if not.
146
     */
147
    protected function requireFile(string $file): bool
148
    {
149
        if (file_exists($file)) {
150
            require $file;
151
            return true;
152
        }
153
        return false;
154
    }
155
}
156