1
|
|
|
<?php |
2
|
|
|
namespace Mouf\Composer; |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* The class maps a class name to one or many possible file names according to PSR-0 or PSR-4 rules. |
6
|
|
|
* |
7
|
|
|
* @author David Négrier <[email protected]> |
8
|
|
|
*/ |
9
|
|
|
class ClassNameMapper |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* |
13
|
|
|
* @var array<namespace, path[]> |
14
|
|
|
*/ |
15
|
|
|
private $psr0Namespaces = array(); |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* |
19
|
|
|
* @var array<namespace, path[]> |
20
|
|
|
*/ |
21
|
|
|
private $psr4Namespaces = array(); |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Registers a PSR-0 namespace. |
25
|
|
|
* |
26
|
|
|
* @param string $namespace The namespace to register |
27
|
|
|
* @param string|array $path The path on the filesystem (or an array of paths) |
28
|
|
|
*/ |
29
|
|
View Code Duplication |
public function registerPsr0Namespace($namespace, $path) { |
|
|
|
|
30
|
|
|
// A namespace always ends with a \ |
31
|
|
|
$namespace = trim($namespace, '\\').'\\'; |
32
|
|
|
if ($namespace === '\\') { |
33
|
|
|
$namespace = ''; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
if (!is_array($path)) { |
37
|
|
|
$path = [$path]; |
38
|
|
|
} |
39
|
|
|
// Paths always end with a / |
40
|
|
|
$paths = array_map([self::class, 'normalizeDirectory'], $path); |
41
|
|
|
|
42
|
|
|
if (!isset($this->psr0Namespaces[$namespace])) { |
43
|
|
|
$this->psr0Namespaces[$namespace] = $paths; |
44
|
|
|
} else { |
45
|
|
|
$this->psr0Namespaces[$namespace] = array_merge($this->psr0Namespaces[$namespace], $paths); |
46
|
|
|
} |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Registers a PSR-4 namespace. |
51
|
|
|
* |
52
|
|
|
* @param string $namespace The namespace to register |
53
|
|
|
* @param string|array $path The path on the filesystem (or an array of paths) |
54
|
|
|
*/ |
55
|
|
View Code Duplication |
public function registerPsr4Namespace($namespace, $path) { |
|
|
|
|
56
|
|
|
// A namespace always ends with a \ |
57
|
|
|
$namespace = trim($namespace, '\\').'\\'; |
58
|
|
|
if ($namespace === '\\') { |
59
|
|
|
$namespace = ''; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
if (!is_array($path)) { |
63
|
|
|
$path = [$path]; |
64
|
|
|
} |
65
|
|
|
// Paths always end with a / |
66
|
|
|
$paths = array_map([self::class, 'normalizeDirectory'], $path); |
67
|
|
|
|
68
|
|
|
if (!isset($this->psr4Namespaces[$namespace])) { |
69
|
|
|
$this->psr4Namespaces[$namespace] = $paths; |
70
|
|
|
} else { |
71
|
|
|
$this->psr4Namespaces[$namespace] = array_merge($this->psr4Namespaces[$namespace], $paths); |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @param string $composerJsonPath |
77
|
|
|
* @param string $rootPath |
78
|
|
|
* @param bool $useAutoloadDev |
79
|
|
|
* @return ClassNameMapper |
80
|
|
|
*/ |
81
|
|
|
public static function createFromComposerFile($composerJsonPath = null, $rootPath = null, $useAutoloadDev = false) { |
82
|
|
|
$classNameMapper = new ClassNameMapper(); |
83
|
|
|
|
84
|
|
|
if ($composerJsonPath === null) { |
85
|
|
|
$composerJsonPath = __DIR__.'/../../../../composer.json'; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$classNameMapper->loadComposerFile($composerJsonPath, $rootPath, $useAutoloadDev); |
89
|
|
|
|
90
|
|
|
return $classNameMapper; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* |
95
|
|
|
* @param string $composerJsonPath Path to the composer file |
96
|
|
|
* @param string $rootPath Root path of the project (or null) |
97
|
|
|
*/ |
98
|
|
|
public function loadComposerFile($composerJsonPath, $rootPath = null, $useAutoloadDev = false) { |
99
|
|
|
$composer = json_decode(file_get_contents($composerJsonPath), true); |
100
|
|
|
|
101
|
|
|
if ($rootPath) { |
|
|
|
|
102
|
|
|
$relativePath = self::makePathRelative(dirname($composerJsonPath), $rootPath); |
103
|
|
|
} else { |
104
|
|
|
$relativePath = null; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
View Code Duplication |
if (isset($composer["autoload"]["psr-0"])) { |
|
|
|
|
108
|
|
|
$psr0 = $composer["autoload"]["psr-0"]; |
109
|
|
|
foreach ($psr0 as $namespace => $paths) { |
110
|
|
|
if ($relativePath != null) { |
|
|
|
|
111
|
|
|
if (!is_array($paths)) { |
112
|
|
|
$paths = [$paths]; |
113
|
|
|
} |
114
|
|
|
$paths = array_map(function($path) use ($relativePath) { |
115
|
|
|
return rtrim($relativePath,'\\/').'/'.ltrim($path, '\\/'); |
116
|
|
|
}, $paths); |
117
|
|
|
} |
118
|
|
|
$this->registerPsr0Namespace($namespace, $paths); |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
|
122
|
|
View Code Duplication |
if (isset($composer["autoload"]["psr-4"])) { |
|
|
|
|
123
|
|
|
$psr4 = $composer["autoload"]["psr-4"]; |
124
|
|
|
foreach ($psr4 as $namespace => $paths) { |
125
|
|
|
if ($relativePath != null) { |
|
|
|
|
126
|
|
|
if (!is_array($paths)) { |
127
|
|
|
$paths = [$paths]; |
128
|
|
|
} |
129
|
|
|
$paths = array_map(function($path) use ($relativePath) { |
130
|
|
|
return rtrim($relativePath,'\\/').'/'.ltrim($path, '\\/'); |
131
|
|
|
}, $paths); |
132
|
|
|
} |
133
|
|
|
$this->registerPsr4Namespace($namespace, $paths); |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if ($useAutoloadDev) { |
138
|
|
View Code Duplication |
if (isset($composer["autoload-dev"]["psr-0"])) { |
|
|
|
|
139
|
|
|
$psr0 = $composer["autoload-dev"]["psr-0"]; |
140
|
|
|
foreach ($psr0 as $namespace => $paths) { |
141
|
|
|
if ($relativePath != null) { |
|
|
|
|
142
|
|
|
if (!is_array($paths)) { |
143
|
|
|
$paths = [$paths]; |
144
|
|
|
} |
145
|
|
|
$paths = array_map(function($path) use ($relativePath) { |
146
|
|
|
return rtrim($relativePath,'\\/').'/'.ltrim($path, '\\/'); |
147
|
|
|
}, $paths); |
148
|
|
|
} |
149
|
|
|
$this->registerPsr0Namespace($namespace, $paths); |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
View Code Duplication |
if (isset($composer["autoload-dev"]["psr-4"])) { |
|
|
|
|
154
|
|
|
$psr4 = $composer["autoload-dev"]["psr-4"]; |
155
|
|
|
foreach ($psr4 as $namespace => $paths) { |
156
|
|
|
if ($relativePath != null) { |
|
|
|
|
157
|
|
|
if (!is_array($paths)) { |
158
|
|
|
$paths = [$paths]; |
159
|
|
|
} |
160
|
|
|
$paths = array_map(function($path) use ($relativePath) { |
161
|
|
|
return rtrim($relativePath,'\\/').'/'.ltrim($path, '\\/'); |
162
|
|
|
}, $paths); |
163
|
|
|
} |
164
|
|
|
$this->registerPsr4Namespace($namespace, $paths); |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
return $this; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Given an existing path, convert it to a path relative to a given starting path. |
174
|
|
|
* Shamelessly borrowed to Symfony :). Thanks guys. |
175
|
|
|
* Note: we do not include Symfony's "FileSystem" component to avoid adding too many dependencies. |
176
|
|
|
* |
177
|
|
|
* @param string $endPath Absolute path of target |
178
|
|
|
* @param string $startPath Absolute path where traversal begins |
179
|
|
|
* |
180
|
|
|
* @return string Path of target relative to starting path |
181
|
|
|
*/ |
182
|
|
|
private static function makePathRelative($endPath, $startPath) |
183
|
|
|
{ |
184
|
|
|
// Normalize separators on Windows |
185
|
|
|
if ('\\' === DIRECTORY_SEPARATOR) { |
186
|
|
|
$endPath = strtr($endPath, '\\', '/'); |
187
|
|
|
$startPath = strtr($startPath, '\\', '/'); |
188
|
|
|
} |
189
|
|
|
// Split the paths into arrays |
190
|
|
|
$startPathArr = explode('/', trim($startPath, '/')); |
191
|
|
|
$endPathArr = explode('/', trim($endPath, '/')); |
192
|
|
|
// Find for which directory the common path stops |
193
|
|
|
$index = 0; |
194
|
|
|
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { |
195
|
|
|
$index++; |
196
|
|
|
} |
197
|
|
|
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) |
198
|
|
|
$depth = count($startPathArr) - $index; |
199
|
|
|
// Repeated "../" for each level need to reach the common path |
200
|
|
|
$traverser = str_repeat('../', $depth); |
201
|
|
|
$endPathRemainder = implode('/', array_slice($endPathArr, $index)); |
202
|
|
|
// Construct $endPath from traversing to the common path, then to the remaining $endPath |
203
|
|
|
$relativePath = $traverser.(strlen($endPathRemainder) > 0 ? $endPathRemainder.'/' : ''); |
204
|
|
|
return (strlen($relativePath) === 0) ? './' : $relativePath; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Returns a list of all namespaces that are managed by the ClassNameMapper. |
209
|
|
|
* |
210
|
|
|
* @return string[] |
211
|
|
|
*/ |
212
|
|
|
public function getManagedNamespaces() { |
213
|
|
|
return array_keys(array_merge($this->psr0Namespaces, $this->psr4Namespaces)); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Returns a list of paths that can be used to store $className. |
218
|
|
|
* |
219
|
|
|
* @param string $className |
220
|
|
|
* @return string[] |
221
|
|
|
*/ |
222
|
|
|
public function getPossibleFileNames($className) { |
223
|
|
|
$possibleFileNames = array(); |
224
|
|
|
$className = ltrim($className, '\\'); |
225
|
|
|
|
226
|
|
|
$psr0unfactorizedAutoload = $this->unfactorizeAutoload($this->psr0Namespaces); |
227
|
|
|
|
228
|
|
|
foreach ($psr0unfactorizedAutoload as $result) { |
229
|
|
|
$namespace = $result['namespace']; |
230
|
|
|
$directory = $result['directory']; |
231
|
|
|
|
232
|
|
|
if ($namespace === '') { |
233
|
|
|
$tmpClassName = $className; |
234
|
|
|
if ($lastNsPos = strripos($tmpClassName, '\\')) { |
235
|
|
|
$namespace = substr($tmpClassName, 0, $lastNsPos); |
236
|
|
|
$tmpClassName = substr($tmpClassName, $lastNsPos + 1); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$fileName = str_replace('\\', '/', $namespace) . '/' . str_replace('_', '/', $tmpClassName) . '.php'; |
240
|
|
|
$possibleFileNames[] = $directory.$fileName; |
241
|
|
|
} else { |
242
|
|
|
if (strpos($className, $namespace) === 0) { |
243
|
|
|
$tmpClassName = $className; |
244
|
|
|
$fileName = ''; |
245
|
|
|
if ($lastNsPos = strripos($tmpClassName, '\\')) { |
246
|
|
|
$namespace = substr($tmpClassName, 0, $lastNsPos); |
247
|
|
|
$tmpClassName = substr($tmpClassName, $lastNsPos + 1); |
248
|
|
|
$fileName = str_replace('\\', '/', $namespace) . '/'; |
249
|
|
|
} |
250
|
|
|
$fileName .= str_replace('_', '/', $tmpClassName) . '.php'; |
251
|
|
|
|
252
|
|
|
$possibleFileNames[] = $directory.$fileName; |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
$psr4unfactorizedAutoload = $this->unfactorizeAutoload($this->psr4Namespaces); |
258
|
|
|
|
259
|
|
|
foreach ($psr4unfactorizedAutoload as $result) { |
260
|
|
|
$namespace = $result['namespace']; |
261
|
|
|
$directory = $result['directory']; |
262
|
|
|
|
263
|
|
|
if ($namespace === '') { |
264
|
|
|
$fileName = str_replace('\\', '/', $className) . '.php'; |
265
|
|
|
$possibleFileNames[] = $directory.$fileName; |
266
|
|
|
} else { |
267
|
|
|
if (strpos($className, $namespace) === 0) { |
268
|
|
|
$shortenedClassName = substr($className, strlen($namespace)); |
269
|
|
|
|
270
|
|
|
if ($lastNsPos = strripos($shortenedClassName, '\\')) { |
271
|
|
|
$namespace = substr($shortenedClassName, 0, $lastNsPos); |
272
|
|
|
$shortenedClassName = substr($shortenedClassName, $lastNsPos + 1); |
273
|
|
|
$fileName = str_replace('\\', '/', $namespace) . '/' . $shortenedClassName; |
274
|
|
|
} else { |
275
|
|
|
$fileName = $shortenedClassName; |
276
|
|
|
} |
277
|
|
|
$fileName .= '.php'; |
278
|
|
|
|
279
|
|
|
$possibleFileNames[] = $directory . $fileName; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return $possibleFileNames; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Takes in parameter an array like |
289
|
|
|
* [{ "Mouf": "src/" }] or [{ "Mouf": ["src/", "src2/"] }] . |
290
|
|
|
* returns |
291
|
|
|
* [ |
292
|
|
|
* {"namespace"=> "Mouf", "directory"=>"src/"}, |
293
|
|
|
* {"namespace"=> "Mouf", "directory"=>"src2/"} |
294
|
|
|
* ] |
295
|
|
|
* |
296
|
|
|
* @param array $autoload |
297
|
|
|
* @return array<int, array<string, string>> |
|
|
|
|
298
|
|
|
*/ |
299
|
|
|
private static function unfactorizeAutoload(array $autoload) { |
300
|
|
|
$result = array(); |
301
|
|
|
foreach ($autoload as $namespace => $directories) { |
302
|
|
|
if (!is_array($directories)) { |
303
|
|
|
$result[] = array( |
304
|
|
|
"namespace" => $namespace, |
305
|
|
|
"directory" => self::normalizeDirectory($directories) |
306
|
|
|
); |
307
|
|
|
} else { |
308
|
|
|
foreach ($directories as $dir) { |
309
|
|
|
$result[] = array( |
310
|
|
|
"namespace" => $namespace, |
311
|
|
|
"directory" => self::normalizeDirectory($dir) |
312
|
|
|
); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
return $result; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Makes sure the directory ends with a / (unless the string is empty) |
321
|
|
|
* |
322
|
|
|
* @param string $dir |
323
|
|
|
* @return string |
324
|
|
|
*/ |
325
|
|
|
private static function normalizeDirectory($dir) { |
326
|
|
|
return $dir === '' ? '' : rtrim($dir, '\\/').'/'; |
327
|
|
|
} |
328
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.