1: <?php
2:
3: namespace ModHelper;
4:
5: /**
6: * @package ModHelper
7: * @since 1.0
8: * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md#class-example
9: */
10: class Psr4AutoloaderClass
11: {
12: /**
13: * An associative array where the key is a namespace prefix and the value
14: * is an array of base directories for classes in that namespace.
15: *
16: * @var array
17: */
18: protected $prefixes = array();
19:
20: /**
21: * Register loader with SPL autoloader stack.
22: *
23: * @return void
24: */
25: public function register()
26: {
27: spl_autoload_register(array($this, 'loadClass'));
28:
29: return $this;
30: }
31:
32: /**
33: * Adds a base directory for a namespace prefix.
34: *
35: * @param string $prefix The namespace prefix.
36: * @param string $base_dir A base directory for class files in the
37: * namespace.
38: * @param bool $prepend If true, prepend the base directory to the stack
39: * instead of appending it; this causes it to be searched first rather
40: * than last.
41: * @return void
42: */
43: public function addNamespace($prefix, $base_dir, $prepend = false)
44: {
45: // normalize namespace prefix
46: $prefix = trim($prefix, '\\');
47: // normalize the base directory with a trailing separator
48: $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
49: // initialize the namespace prefix array
50: if (isset($this->prefixes[$prefix]) === false) {
51: $this->prefixes[$prefix] = array();
52: }
53: // retain the base directory for the namespace prefix
54: if ($prepend) {
55: array_unshift($this->prefixes[$prefix], $base_dir);
56: } else {
57: array_push($this->prefixes[$prefix], $base_dir);
58: }
59:
60: return $this;
61: }
62:
63: /**
64: * Loads the class file for a given class name.
65: *
66: * @param string $class The fully-qualified class name.
67: * @return mixed The mapped file name on success, or boolean false on
68: * failure.
69: */
70: public function loadClass($class)
71: {
72: // the current namespace prefix
73: $prefix = $class;
74: // work backwards through the namespace names of the fully-qualified
75: // class name to find a mapped file name
76: while (false !== $pos = strrpos($prefix, '\\')) {
77: // retain the trailing namespace separator in the prefix
78: $prefix = substr($class, 0, $pos);
79: // the rest is the relative class name
80: $relative_class = substr($class, $pos + 1);
81: // try to load a mapped file for the prefix and relative class
82: $mapped_file = $this->loadMappedFile($prefix, $relative_class);
83: if ($mapped_file) {
84: return $mapped_file;
85: }
86: }
87:
88: // never found a mapped file
89: return false;
90: }
91:
92: /**
93: * Load the mapped file for a namespace prefix and relative class.
94: *
95: * @param string $prefix The namespace prefix.
96: * @param string $relative_class The relative class name.
97: * @return mixed Boolean false if no mapped file can be loaded, or the
98: * name of the mapped file that was loaded.
99: */
100: protected function loadMappedFile($prefix, $relative_class)
101: {
102: // are there any base directories for this namespace prefix?
103: if (isset($this->prefixes[$prefix]) === false) {
104: return false;
105: }
106: // look through base directories for this namespace prefix
107: foreach ($this->prefixes[$prefix] as $base_dir) {
108: // replace the namespace prefix with the base directory,
109: // replace namespace separators with directory separators
110: // in the relative class name, append with .php
111: $file = $base_dir
112: . str_replace('\\', '/', $relative_class)
113: . '.php';
114: // if the mapped file exists, require it
115: if ($this->requireFile($file)) {
116: // yes, we're done
117: return $file;
118: }
119: }
120:
121: // never found it
122: return false;
123: }
124:
125: /**
126: * If a file exists, require it from the file system.
127: *
128: * @param string $file The file to require.
129: * @return bool True if the file exists, false if not.
130: */
131: protected function requireFile($file)
132: {
133: if (file_exists($file)) {
134: require $file;
135:
136: return true;
137: }
138:
139: return false;
140: }
141: }
142: