Completed
Push — master ( 6052f6...d731be )
by Chris
02:50
created

Autoloader::attempt()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
namespace Darya\Foundation;
3
4
/**
5
 * Darya's class autoloader.
6
 * 
7
 * TODO: Simplify Autoloader::load().
8
 * TODO: Maybe make attempt() a generator!
9
 * 
10
 * @author Chris Andrew <[email protected]>
11
 */
12
class Autoloader
13
{
14
	/**
15
	 * Common subdirectories used as a last resort when autoloading.
16
	 * 
17
	 * @var array
18
	 */
19
	private $commonSubdirs = array(
20
		'Common', 'Classes', 'Controllers', 'Models', 'Tests'
21
	);
22
	
23
	/**
24
	 * A map of namespaces to paths to use when autoloading.
25
	 * 
26
	 * @var array
27
	 */
28
	private $namespaces = array();
29
	
30
	/**
31
	 * Base path to use when autoloading.
32
	 * 
33
	 * @var string
34
	 */
35
	private $basePath;
36
	
37
	/**
38
	 * Instantiate an autoloader.
39
	 * 
40
	 * @param string $basePath   Base directory path to load from
41
	 * @param array  $namespaces Namespace to directory mappings to register with the autoloader
42
	 */
43
	public function __construct($basePath = null, array $namespaces = array())
44
	{
45
		$this->basePath($basePath);
46
		$this->namespaces($namespaces);
47
	}
48
	
49
	/**
50
	 * Get and optionally set the base directory to load classes from.
51
	 * 
52
	 * @param string $basePath
53
	 * @return string
54
	 */
55
	public function basePath($basePath = null)
56
	{
57
		$this->basePath = $basePath ?: realpath(__DIR__ . '/../../');
58
		
59
		return $this->basePath;
60
	}
61
	
62
	/**
63
	 * Register this autoloader.
64
	 * 
65
	 * @return bool
66
	 */
67
	public function register()
68
	{
69
		return spl_autoload_register(array($this, 'load'));
70
	}
71
	
72
	/**
73
	 * Register namespace to directory mappings to attempt before the
74
	 * autoloader's default behaviour.
75
	 * 
76
	 * Duplicate namespaces are permitted. Returns the autoloader's currently
77
	 * set namespaces after registering any that are given.
78
	 * 
79
	 * @param array $namespaces Namespace keys and directory values
80
	 * @return array
81
	 */
82
	public function namespaces(array $namespaces = array())
83
	{
84
		foreach ($namespaces as $ns => $paths) {
85
			foreach ((array) $paths as $path) {
86
				$this->namespaces[] = array($ns, $path);
87
			}
88
		}
89
		
90
		return $this->namespaces;
91
	}
92
93
	/**
94
	 * Attempt to load the class at the given path.
95
	 * 
96
	 * @param string $path
97
	 * @return bool
98
	 */
99
	public function attempt($path)
100
	{
101
		if (is_file($path)) {
102
			require_once $path;
103
			return true;
104
		}
105
		
106
		return false;
107
	}
108
109
	/**
110
	 * Load a class assuming the namespace is a path.
111
	 * 
112
	 * Checks common subdirectory names as a last resort if nothing is found.
113
	 * 
114
	 * @param string $class Class name
115
	 * @return bool
116
	 */
117
	public function load($class)
118
	{
119
		// Separate the class name and its namespace
120
		$parts = explode('\\', $class);
121
		$className = array_pop($parts);
122
		$dir = implode('/', $parts);
123
		$paths = array();
124
		
125
		// Test for potential registered namespace to directory mappings
126
		foreach ($this->namespaces as $registered) {
127
			list($ns, $nsPaths) = $registered;
128
			
129
			foreach ((array) $nsPaths as $nsPath) {
130
				// Try without and with the autoloader's base path
131
				$nsBasePaths = array('');
132
				
133
				if ($this->basePath) {
134
					$nsBasePaths[] = $this->basePath . '/';
135
				}
136
				
137
				foreach ($nsBasePaths as $nsBasePath) {
138
					if ($class === $ns) {
139
						array_push($paths, "$nsBasePath$nsPath");
140
						array_push($paths, "$nsBasePath$nsPath/$className.php");
141
						array_push($paths, "$nsBasePath" . strtolower($nsPath) . "/$className.php");
142
					}
143
					
144
					if (strpos($class, $ns) === 0) {
145
						array_push($paths, "$nsBasePath$nsPath/$dir/$className.php");
146
						array_push($paths, "$nsBasePath" . strtolower("$nsPath/$dir") . "/$className.php");
147
						
148
						$nsRemain = str_replace('\\', '/', substr($class, strlen($ns)));
149
						array_push($paths, "$nsBasePath$nsPath/$nsRemain.php");
150
						array_push($paths, "$nsPath/$nsRemain.php");
151
						
152
						$nsRemainDir = dirname($nsRemain);
153
						$nsRemainFile = basename($nsRemain);
154
						array_push($paths, "$nsBasePath$nsPath/" . strtolower($nsRemainDir) . "/$nsRemainFile.php");
155
					}
156
				}
157
			}
158
		}
159
		
160
		// Try using the namespace as an exact directory mapping
161
		array_push($paths, $this->basePath . "/$dir/$className.php");
162
		
163
		// Try using the namespace in lowercase as a directory mapping, with
164
		// only the class name in its original case
165
		$dirLowercase = strtolower($dir);
166
		array_push($paths, $this->basePath . "/$dirLowercase/$className.php");
167
		
168
		// Last try using the last part of the namespace as a subdirectory, with
169
		// and without a trailing 's', as well as any common subdirectory names
170
		$subdirs = array_merge($this->commonSubdirs, array(
171
			$className,
172
			$className . 's',
173
		));
174
		
175
		foreach ($subdirs as $subdir) {
176
			array_push($paths, $this->basePath . "/$dir/$subdir/$className.php");
177
			
178
			$subdirLowercase = strtolower($subdir);
179
			array_push($paths, $this->basePath . "/$dirLowercase/$subdirLowercase/$className.php");
180
		}
181
		
182
		// Finally, attempt to find the class
183
		foreach ($paths as $path) {
184
			if ($this->attempt($path)) {
185
				return true;
186
			}
187
		}
188
		
189
		return false;
190
	}
191
	
192
}
193