Completed
Push — master ( 31de97...c864e7 )
by
unknown
14:20 queued 04:28
created

Autoloader::loadClass()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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