Completed
Branch master (92cc23)
by Christian
03:27
created

Autoloader::addNamespace()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 4
nop 3
dl 0
loc 17
rs 9.4285
1
<?php
2
/**
3
 * @author    WPStore.io <[email protected]>
4
 * @copyright Copyright (c) 2017, WPStore.io
5
 * @license   https://spdx.org/licenses/MIT.html MIT
6
 * @package   WPStore\WPUtils\Autoloader
7
 * @version   1.0.0
8
 */
9
10
/**
11
 * MIT License
12
 *
13
 * Copyright (c) 2017 WPStore.io
14
 *
15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
16
 * of this software and associated documentation files (the "Software"), to deal
17
 * in the Software without restriction, including without limitation the rights
18
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
 * copies of the Software, and to permit persons to whom the Software is
20
 * furnished to do so, subject to the following conditions:
21
 *
22
 * The above copyright notice and this permission notice shall be included in all
23
 * copies or substantial portions of the Software.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
 * SOFTWARE.
32
 */
33
34
namespace WPUtils;
35
36
/**
37
 * An WPUtilities of a general-purpose implementation that includes the optional
38
 * functionality of allowing multiple base directories for a single namespace
39
 * prefix.
40
 *
41
 * Given a foo-bar package of classes in the file system at the following
42
 * paths ...
43
 *
44
 *     /path/to/packages/foo-bar/
45
 *         src/
46
 *             Baz.php             # Foo\Bar\Baz
47
 *             Qux/
48
 *                 Quux.php        # Foo\Bar\Qux\Quux
49
 *         tests/
50
 *             BazTest.php         # Foo\Bar\BazTest
51
 *             Qux/
52
 *                 QuuxTest.php    # Foo\Bar\Qux\QuuxTest
53
 *
54
 * ... add the path to the class files for the \Foo\Bar\ namespace prefix
55
 * as follows:
56
 *
57
 *      <?php
58
 *      // instantiate the loader
59
 *      $loader = new \WPUtils\Autoloader;
60
 *
61
 *      // register the autoloader
62
 *      $loader->register();
63
 *
64
 *      // register the base directories for the namespace prefix
65
 *      $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src');
66
 *      $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests');
67
 *
68
 * The following line would cause the autoloader to attempt to load the
69
 * \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php:
70
 *
71
 *      <?php
72
 *      new \Foo\Bar\Qux\Quux;
73
 *
74
 * The following line would cause the autoloader to attempt to load the
75
 * \Foo\Bar\Qux\QuuxTest class from /path/to/packages/foo-bar/tests/Qux/QuuxTest.php:
76
 *
77
 *      <?php
78
 *      new \Foo\Bar\Qux\QuuxTest;
79
 */
80
81
/**
82
 * Class Autoloader
83
 * @package WPStoreUtils
84
 * @See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md
85
 */
86
class Autoloader {
87
	/**
88
	 * An associative array where the key is a namespace prefix and the value
89
	 * is an array of base directories for classes in that namespace.
90
	 *
91
	 * @var array
92
	 */
93
	protected $prefixes = array();
94
95
	/**
96
	 * Register loader with SPL autoloader stack.
97
	 *
98
	 * @return void
99
	 */
100
	public function register() {
101
		spl_autoload_register( array( $this, 'loadClass' ) );
102
	}
103
104
	/**
105
	 * Adds a base directory for a namespace prefix.
106
	 *
107
	 * @param string $prefix The namespace prefix.
108
	 * @param string $base_dir A base directory for class files in the
109
	 * namespace.
110
	 * @param bool $prepend If true, prepend the base directory to the stack
111
	 * instead of appending it; this causes it to be searched first rather
112
	 * than last.
113
	 *
114
	 * @return void
115
	 */
116
	public function addNamespace( $prefix, $base_dir, $prepend = false ) {
0 ignored issues
show
Coding Style introduced by
The function name addNamespace is in camel caps, but expected add_namespace instead as per the coding standard.
Loading history...
117
		// normalize namespace prefix
118
		$prefix = trim( $prefix, '\\' ) . '\\';
119
120
		// normalize the base directory with a trailing separator
121
		$base_dir = rtrim( $base_dir, DIRECTORY_SEPARATOR ) . '/';
122
123
		// initialize the namespace prefix array
124
		if ( isset( $this->prefixes[ $prefix ] ) === false ) {
0 ignored issues
show
introduced by
Found "=== false". Use Yoda Condition checks, you must
Loading history...
125
			$this->prefixes[ $prefix ] = array();
126
		}
127
128
		// retain the base directory for the namespace prefix
129
		if ( $prepend ) {
130
			array_unshift( $this->prefixes[ $prefix ], $base_dir );
131
		} else {
132
			array_push( $this->prefixes[ $prefix ], $base_dir );
133
		}
134
	}
135
136
	/**
137
	 * Loads the class file for a given class name.
138
	 *
139
	 * @param string $class The fully-qualified class name.
140
	 *
141
	 * @return mixed The mapped file name on success, or boolean false on
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
142
	 * failure.
143
	 */
144
	public function loadClass( $class ) {
0 ignored issues
show
Coding Style introduced by
The function name loadClass is in camel caps, but expected load_class instead as per the coding standard.
Loading history...
145
		// the current namespace prefix
146
		$prefix = $class;
147
148
		// work backwards through the namespace names of the fully-qualified
149
		// class name to find a mapped file name
150
		while ( false !== $pos = strrpos( $prefix, '\\' ) ) {
151
152
			// retain the trailing namespace separator in the prefix
153
			$prefix = substr( $class, 0, $pos + 1 );
154
155
			// the rest is the relative class name
156
			$relative_class = substr( $class, $pos + 1 );
157
158
			// try to load a mapped file for the prefix and relative class
159
			$mapped_file = $this->loadMappedFile( $prefix, $relative_class );
160
			if ( $mapped_file ) {
161
				return $mapped_file;
162
			}
163
164
			// remove the trailing namespace separator for the next iteration
165
			// of strrpos()
166
			$prefix = rtrim( $prefix, '\\' );
167
		}
168
169
		// never found a mapped file
170
		return false;
171
	}
172
173
	/**
174
	 * Load the mapped file for a namespace prefix and relative class.
175
	 *
176
	 * @param string $prefix The namespace prefix.
177
	 * @param string $relative_class The relative class name.
178
	 *
179
	 * @return mixed Boolean false if no mapped file can be loaded, or the
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
180
	 * name of the mapped file that was loaded.
181
	 */
182
	protected function loadMappedFile( $prefix, $relative_class ) {
0 ignored issues
show
Coding Style introduced by
The function name loadMappedFile is in camel caps, but expected load_mapped_file instead as per the coding standard.
Loading history...
183
		// are there any base directories for this namespace prefix?
184
		if ( isset( $this->prefixes[ $prefix ] ) === false ) {
0 ignored issues
show
introduced by
Found "=== false". Use Yoda Condition checks, you must
Loading history...
185
			return false;
186
		}
187
188
		// look through base directories for this namespace prefix
189
		foreach ( $this->prefixes[ $prefix ] as $base_dir ) {
190
191
			// replace the namespace prefix with the base directory,
192
			// replace namespace separators with directory separators
193
			// in the relative class name, append with .php
194
			$file = $base_dir . str_replace( '\\', '/', $relative_class ) . '.php';
195
196
			// if the mapped file exists, require it
197
			if ( $this->requireFile( $file ) ) {
198
				// yes, we're done
199
				return $file;
200
			}
201
		}
202
203
		// never found it
204
		return false;
205
	}
206
207
	/**
208
	 * If a file exists, require it from the file system.
209
	 *
210
	 * @param string $file The file to require.
211
	 *
212
	 * @return bool True if the file exists, false if not.
213
	 */
214
	protected function requireFile( $file ) {
0 ignored issues
show
Coding Style introduced by
The function name requireFile is in camel caps, but expected require_file instead as per the coding standard.
Loading history...
215
		if ( file_exists( $file ) ) {
216
			require $file;
217
218
			return true;
219
		}
220
221
		return false;
222
	}
223
}
224