Completed
Pull Request — master (#520)
by Richard
07:44
created

Psr4ClassLoader::requireFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 5
cp 0
crap 6
1
<?php
2
/**
3
 * Copyright (c) 2013 PHP Framework Interop Group
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy
6
 * of this software and associated documentation files (the "Software"), to deal
7
 * in the Software without restriction, including without limitation the rights
8
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
 * copies of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in
13
 * all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
 * THE SOFTWARE.
22
 */
23
24
namespace Xoops\Core;
25
26
/**
27
 * A Class Loader that implements the technical interoperability
28
 * standards for PHP 5.3 namespaces and class names set forth in PSR-4.
29
 * This general-purpose implementation includes the optional functionality
30
 * of allowing multiple base directories for a single namespace prefix.
31
 *
32
 * From the PHP Framework Interop Group
33
 *
34
 * Examples
35
 *
36
 * Given a foo-bar package of classes in the file system at the following
37
 * paths ...
38
 *
39
 *     /path/to/packages/foo-bar/
40
 *         src/
41
 *             Baz.php             # Foo\Bar\Baz
42
 *             Qux/
43
 *                 Quux.php        # Foo\Bar\Qux\Quux
44
 *         tests/
45
 *             BazTest.php         # Foo\Bar\BazTest
46
 *             Qux/
47
 *                 QuuxTest.php    # Foo\Bar\Qux\QuuxTest
48
 *
49
 * ... add the path to the class files for the \Foo\Bar\ namespace prefix
50
 * as follows:
51
 *
52
 * @code
53
 *      <?php
54
 *      // instantiate the loader
55
 *      $loader = new \Xoops\Core\Psr4ClassLoader;
56
 *
57
 *      // register the autoloader
58
 *      $loader->register();
59
 *
60
 *      // register the base directories for the namespace prefix
61
 *      $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src');
62
 *      $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests');
63
 * @endcode
64
 *
65
 * The following line would cause the autoloader to attempt to load the
66
 * \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php:
67
 *
68
 * @code
69
 *      <?php
70
 *      new \Foo\Bar\Qux\Quux;
71
 * @endcode
72
 *
73
 * The following line would cause the autoloader to attempt to load the
74
 * \Foo\Bar\Qux\QuuxTest class from /path/to/packages/foo-bar/tests/Qux/QuuxTest.php:
75
 *
76
 * @code
77
 *      <?php
78
 *      new \Foo\Bar\Qux\QuuxTest;
79
 * @endcode
80
 *
81
 * @category  Xoops\Core\Psr4ClassLoader
82
 * @package   Xoops
83
 * @author    https://github.com/php-fig/fig-standards/
84
 * @author    Richard Griffith <[email protected]>
85
 * @copyright 2013 PHP Framework Interop Group
86
 * @copyright 2013-2014 XOOPS Project (http://xoops.org)
87
 * @license   MIT (see above)
88
 * @link      https://github.com/php-fig/fig-standards/wiki/PSR-4-Example-Implementations
89
 * @see       http://www.php-fig.org/
90
 */
91
class Psr4ClassLoader
92
{
93
    /**
94
     * An associative array where the key is a namespace prefix and the value
95
     * is an array of base directories for classes in that namespace.
96
     *
97
     * @var array
98
     */
99
    protected $prefixes = array();
100
101
    /**
102
     * addLoader sets all basic options and registers the autoloader
103
     *
104
     * @param type  $namespace namespace
105
     * @param mixed $path      path(s) to the namespace's directories
106
     *                         Can be string - only one directory
107
     *                         or array of strings - multiple directories
108
     *
109
     * @return SplClassLoader
110
     */
111
    public static function addLoader($namespace, $path)
112
    {
113
        $loaderClass = get_called_class();
114
        $loader = new $loaderClass($namespace, $path);
115
        if (is_array($path)) {
116
            foreach ($path as $pathdir) {
117
                $loader->addNamespace($namespace, $pathdir);
118
            }
119
        } else {
120
            $loader->addNamespace($namespace, $path);
121
        }
122
        $loader->register();
123
        return $loader;
124
    }
125
126
    /**
127
     * Register loader with SPL autoloader stack.
128
     *
129
     * @return null
130
     */
131
    public function register()
132
    {
133
        spl_autoload_register(array($this, 'loadClass'));
134
    }
135
136
    /**
137
     * Adds a base directory for a namespace prefix.
138
     *
139
     * @param string $prefix   The namespace prefix.
140
     * @param string $base_dir Base directory for class files in namespace.
141
     * @param bool   $prepend  If true, prepend the base directory to the
142
     *                         stack instead of appending it; this causes
143
     *                         it to be searched first rather than last.
144
     *
145
     * @return null
146
     */
147
    public function addNamespace($prefix, $base_dir, $prepend = false)
148
    {
149
        // normalize namespace prefix
150
        $prefix = trim($prefix, '\\') . '\\';
151
152
        // normalize the base directory with a trailing separator
153
        $base_dir = rtrim($base_dir, '/') . DIRECTORY_SEPARATOR;
154
        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
155
156
        // initialize the namespace prefix array
157
        if (isset($this->prefixes[$prefix]) === false) {
158
            $this->prefixes[$prefix] = array();
159
        }
160
161
        // retain the base directory for the namespace prefix
162
        if ($prepend) {
163
            array_unshift($this->prefixes[$prefix], $base_dir);
164
        } else {
165
            array_push($this->prefixes[$prefix], $base_dir);
166
        }
167
    }
168
169
    /**
170
     * Loads the class file for a given class name.
171
     *
172
     * @param string $class The fully-qualified class name.
173
     *
174
     * @return string|false  The mapped file name on success, or boolean false on
175
     * failure.
176
     */
177 8
    public function loadClass($class)
178
    {
179
        // the current namespace prefix
180 8
        $prefix = $class;
181
182
        // work backwards through the namespace names of the fully-qualified
183
        // class name to find a mapped file name
184 8
        while (false !== $pos = strrpos($prefix, '\\')) {
185
            // retain the trailing namespace separator in the prefix
186 4
            $prefix = substr($class, 0, $pos + 1);
187
188
            // the rest is the relative class name
189 4
            $relative_class = substr($class, $pos + 1);
190
191
            // try to load a mapped file for the prefix and relative class
192 4
            $mapped_file = $this->loadMappedFile($prefix, $relative_class);
193 4
            if ($mapped_file !== false) {
194
                return $mapped_file;
195
            }
196
197
            // remove the trailing namespace separator for the next iteration
198
            // of strrpos()
199 4
            $prefix = rtrim($prefix, '\\');
200
        }
201
202
        // never found a mapped file
203 8
        return false;
204
    }
205
206
    /**
207
     * Load the mapped file for a namespace prefix and relative class.
208
     *
209
     * @param string $prefix         The namespace prefix.
210
     * @param string $relative_class The relative class name.
211
     *
212
     * @return false|string  Boolean false if no mapped file can be loaded, or the
213
     * name of the mapped file that was loaded.
214
     */
215 4
    protected function loadMappedFile($prefix, $relative_class)
216
    {
217
        // are there any base directories for this namespace prefix?
218 4
        if (isset($this->prefixes[$prefix]) === false) {
219 4
            return false;
220
        }
221
222
        // look through base directories for this namespace prefix
223
        foreach ($this->prefixes[$prefix] as $base_dir) {
224
            // replace the namespace prefix with the base directory,
225
            // replace namespace separators with directory separators
226
            // in the relative class name, append with .php
227
            $file = $base_dir
228
                  . str_replace('\\', '/', $relative_class)
229
                  . '.php';
230
231
            // if the mapped file exists, require it
232
            if ($this->requireFile($file)) {
233
                // yes, we're done
234
                return $file;
235
            }
236
        }
237
238
        // never found it
239
        return false;
240
    }
241
242
    /**
243
     * If a file exists, require it from the file system.
244
     *
245
     * @param string $file The file to require.
246
     *
247
     * @return bool   True if the file exists, false if not.
248
     */
249
    protected function requireFile($file)
250
    {
251
        if (file_exists($file)) {
252
            require $file;
253
254
            return true;
255
        }
256
257
        return false;
258
    }
259
}
260