|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace N98\Magento\Command\Script\Repository; |
|
4
|
|
|
|
|
5
|
|
|
use N98\Util\OperatingSystem; |
|
6
|
|
|
use Symfony\Component\Finder\Finder; |
|
7
|
|
|
use Symfony\Component\Finder\SplFileInfo; |
|
8
|
|
|
|
|
9
|
|
|
final class ScriptLoader |
|
10
|
|
|
{ |
|
11
|
|
|
const LOCATION_PROJECT = 'project'; |
|
12
|
|
|
const LOCATION_PERSONAL = 'personal'; |
|
13
|
|
|
const LOCATION_MODULE = 'module'; |
|
14
|
|
|
const LOCATION_SYSTEM = 'system'; |
|
15
|
|
|
|
|
16
|
|
|
const BASENAME_MODULES = 'modules'; |
|
17
|
|
|
const BASENAME_SCRIPTS = 'scripts'; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* @var string basename, e.g. "n98-magerun2" |
|
21
|
|
|
*/ |
|
22
|
|
|
private $basename; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* @var string magento root folder |
|
26
|
|
|
*/ |
|
27
|
|
|
private $magentoRootFolder; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* @var array collection of folders, key is the folder (normalized), value is the type of it |
|
31
|
|
|
*/ |
|
32
|
|
|
private $folders; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* @var array |
|
36
|
|
|
*/ |
|
37
|
|
|
private $scriptFolders; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* @var array |
|
41
|
|
|
*/ |
|
42
|
|
|
private $scriptFiles; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* @param array $scriptFolders provided from the config file (config: script.folders, see YAML for details) |
|
46
|
|
|
* @param string $basename |
|
47
|
|
|
* @param string $magentoRootFolder |
|
48
|
|
|
*/ |
|
49
|
|
|
public function __construct(array $scriptFolders, $basename, $magentoRootFolder) |
|
50
|
|
|
{ |
|
51
|
|
|
$this->basename = $basename; |
|
52
|
|
|
$this->magentoRootFolder = $magentoRootFolder; |
|
53
|
|
|
$this->scriptFolders = $scriptFolders; |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* initialize folders by type. folders is the main concept of this class |
|
58
|
|
|
*/ |
|
59
|
|
|
private function init() |
|
60
|
|
|
{ |
|
61
|
|
|
// add Magento root folder |
|
62
|
|
|
$this->addFolder(self::LOCATION_PROJECT, $this->magentoRootFolder); |
|
63
|
|
|
|
|
64
|
|
|
// add home-dir folder |
|
65
|
|
|
$this->addPersonalFolder(); |
|
66
|
|
|
|
|
67
|
|
|
// add Magerun config folders |
|
68
|
|
|
$this->addFolders(self::LOCATION_SYSTEM, $this->scriptFolders); |
|
69
|
|
|
|
|
70
|
|
|
// remove folders again which do not exist |
|
71
|
|
|
$this->filterInvalidFolders(); |
|
72
|
|
|
|
|
73
|
|
|
// finally find all *.magerun scripts in so far initialized folders |
|
74
|
|
|
$this->scriptFiles = $this->findScriptFiles(); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* @param string $location |
|
79
|
|
|
* @param string $path |
|
80
|
|
|
*/ |
|
81
|
|
|
private function addFolder($location, $path) |
|
82
|
|
|
{ |
|
83
|
|
|
$normalized = rtrim($path, '/'); |
|
84
|
|
|
$this->folders[$normalized] = $location; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
/** |
|
88
|
|
|
* Add home-dir folder(s), these are multiple on windows due to backwards compatibility |
|
89
|
|
|
*/ |
|
90
|
|
|
private function addPersonalFolder() |
|
91
|
|
|
{ |
|
92
|
|
|
$basename = $this->basename; |
|
93
|
|
|
$homeDir = OperatingSystem::getHomeDir(); |
|
94
|
|
|
|
|
95
|
|
|
if (false !== $homeDir) { |
|
96
|
|
|
if (OperatingSystem::isWindows()) { |
|
97
|
|
|
$this->addFolder(self::LOCATION_PERSONAL, $homeDir . '/' . $basename . '/' . self::BASENAME_SCRIPTS); |
|
98
|
|
|
} |
|
99
|
|
|
$this->addFolder(self::LOCATION_PERSONAL, $homeDir . '/.' . $basename . '/' . self::BASENAME_SCRIPTS); |
|
100
|
|
|
} |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* @param string $location |
|
105
|
|
|
* @param array $paths |
|
106
|
|
|
*/ |
|
107
|
|
|
private function addFolders($location, array $paths) |
|
108
|
|
|
{ |
|
109
|
|
|
foreach ($paths as $path) { |
|
110
|
|
|
$this->addFolder($location, $path); |
|
111
|
|
|
} |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
private function filterInvalidFolders() |
|
115
|
|
|
{ |
|
116
|
|
|
foreach ($this->folders as $path => $type) { |
|
117
|
|
|
if (!is_dir($path)) { |
|
118
|
|
|
unset($this->folders[$path]); |
|
119
|
|
|
} |
|
120
|
|
|
} |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
private function getFolderPaths() |
|
124
|
|
|
{ |
|
125
|
|
|
return array_keys($this->folders); |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* @return array |
|
130
|
|
|
*/ |
|
131
|
|
|
private function findScriptFiles() |
|
132
|
|
|
{ |
|
133
|
|
|
$scriptFiles = array(); |
|
134
|
|
|
|
|
135
|
|
|
$folders = $this->getFolderPaths(); |
|
136
|
|
|
|
|
137
|
|
|
if (!$folders) { |
|
138
|
|
|
return $scriptFiles; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
$finder = $this->createScriptFilesFinder($folders); |
|
142
|
|
|
|
|
143
|
|
|
foreach ($finder as $file) { |
|
144
|
|
|
$filename = $file->getFilename(); |
|
145
|
|
|
$scriptFiles[$filename] = $this->createScriptFile($file); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
ksort($scriptFiles, SORT_STRING); |
|
149
|
|
|
|
|
150
|
|
|
return $scriptFiles; |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* @param array $folders to search for magerun scripts in |
|
155
|
|
|
* @return Finder|\Symfony\Component\Finder\SplFileInfo[] |
|
156
|
|
|
*/ |
|
157
|
|
|
private function createScriptFilesFinder(array $folders) |
|
158
|
|
|
{ |
|
159
|
|
|
/* @var $finder Finder */ |
|
160
|
|
|
$finder = Finder::create() |
|
161
|
|
|
->files() |
|
162
|
|
|
->followLinks(true) |
|
|
|
|
|
|
163
|
|
|
->ignoreUnreadableDirs(true) |
|
164
|
|
|
->name('*' . AbstractRepositoryCommand::MAGERUN_EXTENSION) |
|
165
|
|
|
->in($folders); |
|
166
|
|
|
|
|
167
|
|
|
return $finder; |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
private function createScriptFile(SplFileInfo $file) |
|
171
|
|
|
{ |
|
172
|
|
|
$pathname = $file->getPathname(); |
|
173
|
|
|
|
|
174
|
|
|
$scriptFile = array( |
|
175
|
|
|
'fileinfo' => $file, |
|
176
|
|
|
'description' => $this->readDescriptionFromFile($pathname), |
|
177
|
|
|
'location' => $this->getLocation($pathname), |
|
178
|
|
|
); |
|
179
|
|
|
|
|
180
|
|
|
return $scriptFile; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* Reads the first line. If it's a comment return it. |
|
185
|
|
|
* |
|
186
|
|
|
* @param string $file |
|
187
|
|
|
* |
|
188
|
|
|
* @return string comment text or zero-length string if no comment was given |
|
189
|
|
|
*/ |
|
190
|
|
|
private function readDescriptionFromFile($file) |
|
191
|
|
|
{ |
|
192
|
|
|
$line = $this->readFirstLineOfFile($file); |
|
193
|
|
|
if (null === $line) { |
|
194
|
|
|
return ''; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
if (isset($line[0]) && $line[0] != '#') { |
|
198
|
|
|
return ''; |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
return trim(substr($line, 1)); |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* Read first line of a file w/o line-separators and end-of-line whitespace |
|
206
|
|
|
* |
|
207
|
|
|
* @param string $file |
|
208
|
|
|
* |
|
209
|
|
|
* @return string|null first line or null, if it was not possible to obtain a first line |
|
210
|
|
|
*/ |
|
211
|
|
|
private function readFirstLineOfFile($file) |
|
212
|
|
|
{ |
|
213
|
|
|
$handle = @fopen($file, 'r'); |
|
214
|
|
|
if (!$handle) { |
|
215
|
|
|
return null; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
$buffer = fgets($handle); |
|
219
|
|
|
fclose($handle); |
|
220
|
|
|
|
|
221
|
|
|
if (false === $file) { |
|
222
|
|
|
return null; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
return rtrim($buffer); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* @param string $pathname |
|
230
|
|
|
* |
|
231
|
|
|
* @return string |
|
232
|
|
|
*/ |
|
233
|
|
|
private function getLocation($pathname) |
|
234
|
|
|
{ |
|
235
|
|
|
if (null !== $location = $this->detectLocationViaFolderByPathname($pathname)) { |
|
236
|
|
|
return $location; |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
if (null !== $location = $this->detecLocationModuleByPathname($pathname)) { |
|
240
|
|
|
return $location; |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
return self::LOCATION_SYSTEM; |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
/** |
|
247
|
|
|
* private helper function to detect type by pathname with the help of initialized folders |
|
248
|
|
|
* |
|
249
|
|
|
* @see init() |
|
250
|
|
|
* |
|
251
|
|
|
* @param string $pathname |
|
252
|
|
|
* @return mixed|null |
|
253
|
|
|
*/ |
|
254
|
|
|
private function detectLocationViaFolderByPathname($pathname) |
|
255
|
|
|
{ |
|
256
|
|
|
foreach ($this->folders as $path => $type) { |
|
257
|
|
|
if (0 === strpos($pathname, $path . '/')) { |
|
258
|
|
|
return $type; |
|
259
|
|
|
} |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
return null; |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
/** |
|
266
|
|
|
* private helper function to detect if a script is from within a module |
|
267
|
|
|
* |
|
268
|
|
|
* @param string $pathname |
|
269
|
|
|
* @return null|string |
|
270
|
|
|
*/ |
|
271
|
|
|
private function detecLocationModuleByPathname($pathname) |
|
272
|
|
|
{ |
|
273
|
|
|
$pos = strpos($pathname, '/' . $this->basename . '/' . self::BASENAME_MODULES . '/'); |
|
274
|
|
|
if (false !== $pos) { |
|
275
|
|
|
return $this::LOCATION_MODULE; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
return null; |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
/** |
|
282
|
|
|
* @return array |
|
283
|
|
|
*/ |
|
284
|
|
|
public function getFiles() |
|
285
|
|
|
{ |
|
286
|
|
|
if (null === $this->scriptFiles) { |
|
287
|
|
|
$this->init(); |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
return $this->scriptFiles; |
|
291
|
|
|
} |
|
292
|
|
|
} |
|
293
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.