These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace Robo; |
||
3 | |||
4 | use Composer\Autoload\ClassLoader; |
||
5 | use Symfony\Component\Console\Input\ArgvInput; |
||
6 | use Symfony\Component\Console\Input\StringInput; |
||
7 | use Robo\Contract\BuilderAwareInterface; |
||
8 | use Robo\Collection\CollectionBuilder; |
||
9 | use Robo\Common\IO; |
||
10 | use Robo\Exception\TaskExitException; |
||
11 | use League\Container\ContainerAwareInterface; |
||
12 | use League\Container\ContainerAwareTrait; |
||
13 | use Consolidation\Config\Util\EnvConfig; |
||
14 | |||
15 | class Runner implements ContainerAwareInterface |
||
16 | { |
||
17 | const ROBOCLASS = 'RoboFile'; |
||
18 | const ROBOFILE = 'RoboFile.php'; |
||
19 | |||
20 | use IO; |
||
21 | use ContainerAwareTrait; |
||
22 | |||
23 | /** |
||
24 | * @var string |
||
25 | */ |
||
26 | protected $roboClass; |
||
27 | |||
28 | /** |
||
29 | * @var string |
||
30 | */ |
||
31 | protected $roboFile; |
||
32 | |||
33 | /** |
||
34 | * @var string working dir of Robo |
||
35 | */ |
||
36 | protected $dir; |
||
37 | |||
38 | /** |
||
39 | * @var string[] |
||
40 | */ |
||
41 | protected $errorConditions = []; |
||
42 | |||
43 | /** |
||
44 | * @var string GitHub Repo for SelfUpdate |
||
45 | */ |
||
46 | protected $selfUpdateRepository = null; |
||
47 | |||
48 | /** |
||
49 | * @var string filename to load configuration from (set to 'robo.yml' for RoboFiles) |
||
50 | */ |
||
51 | protected $configFilename = 'conf.yml'; |
||
52 | |||
53 | /** |
||
54 | * @var string prefix for environment variable configuration overrides |
||
55 | */ |
||
56 | protected $envConfigPrefix = false; |
||
57 | |||
58 | /** |
||
59 | * @var \Composer\Autoload\ClassLoader |
||
60 | */ |
||
61 | protected $classLoader = null; |
||
62 | |||
63 | /** |
||
64 | * @var string |
||
65 | */ |
||
66 | protected $relativePluginNamespace; |
||
67 | |||
68 | /** |
||
69 | * Class Constructor |
||
70 | * |
||
71 | * @param null|string $roboClass |
||
72 | * @param null|string $roboFile |
||
73 | */ |
||
74 | public function __construct($roboClass = null, $roboFile = null) |
||
75 | { |
||
76 | // set the const as class properties to allow overwriting in child classes |
||
77 | $this->roboClass = $roboClass ? $roboClass : self::ROBOCLASS ; |
||
78 | $this->roboFile = $roboFile ? $roboFile : self::ROBOFILE; |
||
79 | $this->dir = getcwd(); |
||
80 | } |
||
81 | |||
82 | protected function errorCondition($msg, $errorType) |
||
83 | { |
||
84 | $this->errorConditions[$msg] = $errorType; |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * @param \Symfony\Component\Console\Output\OutputInterface $output |
||
89 | * |
||
90 | * @return bool |
||
91 | */ |
||
92 | protected function loadRoboFile($output) |
||
93 | { |
||
94 | // If we have not been provided an output object, make a temporary one. |
||
95 | if (!$output) { |
||
96 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); |
||
97 | } |
||
98 | |||
99 | // If $this->roboClass is a single class that has not already |
||
100 | // been loaded, then we will try to obtain it from $this->roboFile. |
||
101 | // If $this->roboClass is an array, we presume all classes requested |
||
102 | // are available via the autoloader. |
||
103 | if (is_array($this->roboClass) || class_exists($this->roboClass)) { |
||
104 | return true; |
||
105 | } |
||
106 | if (!file_exists($this->dir)) { |
||
107 | $this->errorCondition("Path `{$this->dir}` is invalid; please provide a valid absolute path to the Robofile to load.", 'red'); |
||
108 | return false; |
||
109 | } |
||
110 | |||
111 | $realDir = realpath($this->dir); |
||
112 | |||
113 | $roboFilePath = $realDir . DIRECTORY_SEPARATOR . $this->roboFile; |
||
114 | if (!file_exists($roboFilePath)) { |
||
115 | $requestedRoboFilePath = $this->dir . DIRECTORY_SEPARATOR . $this->roboFile; |
||
116 | $this->errorCondition("Requested RoboFile `$requestedRoboFilePath` is invalid, please provide valid absolute path to load Robofile.", 'red'); |
||
117 | return false; |
||
118 | } |
||
119 | require_once $roboFilePath; |
||
120 | |||
121 | if (!class_exists($this->roboClass)) { |
||
122 | $this->errorCondition("Class {$this->roboClass} was not loaded.", 'red'); |
||
123 | return false; |
||
124 | } |
||
125 | return true; |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * @param array $argv |
||
130 | * @param null|string $appName |
||
131 | * @param null|string $appVersion |
||
132 | * @param null|\Symfony\Component\Console\Output\OutputInterface $output |
||
133 | * |
||
134 | * @return int |
||
135 | */ |
||
136 | public function execute($argv, $appName = null, $appVersion = null, $output = null) |
||
137 | { |
||
138 | $argv = $this->shebang($argv); |
||
139 | $argv = $this->processRoboOptions($argv); |
||
140 | $app = null; |
||
141 | if ($appName && $appVersion) { |
||
0 ignored issues
–
show
The expression
$appVersion of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||
142 | $app = Robo::createDefaultApplication($appName, $appVersion); |
||
143 | } |
||
144 | $commandFiles = $this->getRoboFileCommands($output); |
||
145 | return $this->run($argv, $output, $app, $commandFiles, $this->classLoader); |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Get a list of locations where config files may be loaded |
||
150 | * @return string[] |
||
151 | */ |
||
152 | protected function getConfigFilePaths($userConfig) |
||
153 | { |
||
154 | $roboAppConfig = dirname(__DIR__) . '/' . basename($userConfig); |
||
155 | $configFiles = [$userConfig, $roboAppConfig]; |
||
156 | if (dirname($userConfig) != '.') { |
||
157 | array_unshift($configFiles, basename($userConfig)); |
||
158 | } |
||
159 | return $configFiles; |
||
160 | } |
||
161 | /** |
||
162 | * @param null|\Symfony\Component\Console\Input\InputInterface $input |
||
163 | * @param null|\Symfony\Component\Console\Output\OutputInterface $output |
||
164 | * @param null|\Robo\Application $app |
||
165 | * @param array[] $commandFiles |
||
166 | * @param null|ClassLoader $classLoader |
||
167 | * |
||
168 | * @return int |
||
169 | */ |
||
170 | public function run($input = null, $output = null, $app = null, $commandFiles = [], $classLoader = null) |
||
171 | { |
||
172 | // Create default input and output objects if they were not provided |
||
173 | if (!$input) { |
||
174 | $input = new StringInput(''); |
||
175 | } |
||
176 | if (is_array($input)) { |
||
177 | $input = new ArgvInput($input); |
||
178 | } |
||
179 | if (!$output) { |
||
180 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); |
||
181 | } |
||
182 | $this->setInput($input); |
||
183 | $this->setOutput($output); |
||
184 | |||
185 | // If we were not provided a container, then create one |
||
186 | if (!$this->getContainer()) { |
||
187 | $configFiles = $this->getConfigFilePaths($this->configFilename); |
||
188 | $config = Robo::createConfiguration($configFiles); |
||
189 | if ($this->envConfigPrefix) { |
||
190 | $envConfig = new EnvConfig($this->envConfigPrefix); |
||
191 | $config->addContext('env', $envConfig); |
||
192 | } |
||
193 | $container = Robo::createDefaultContainer($input, $output, $app, $config, $classLoader); |
||
194 | $this->setContainer($container); |
||
195 | // Automatically register a shutdown function and |
||
196 | // an error handler when we provide the container. |
||
197 | $this->installRoboHandlers(); |
||
198 | } |
||
199 | |||
200 | if (!$app) { |
||
201 | $app = Robo::application(); |
||
202 | } |
||
203 | if ($app instanceof \Robo\Application) { |
||
204 | $app->addSelfUpdateCommand($this->getSelfUpdateRepository()); |
||
205 | if (!isset($commandFiles)) { |
||
206 | $this->errorCondition("Robo is not initialized here. Please run `robo init` to create a new RoboFile.", 'yellow'); |
||
207 | $app->addInitRoboFileCommand($this->roboFile, $this->roboClass); |
||
208 | $commandFiles = []; |
||
209 | } |
||
210 | } |
||
211 | |||
212 | if (!empty($this->relativePluginNamespace)) { |
||
213 | $commandClasses = $this->discoverCommandClasses($this->relativePluginNamespace); |
||
214 | $commandFiles = array_merge((array)$commandFiles, $commandClasses); |
||
215 | } |
||
216 | |||
217 | $this->registerCommandClasses($app, $commandFiles); |
||
218 | |||
219 | try { |
||
220 | $statusCode = $app->run($input, $output); |
||
221 | } catch (TaskExitException $e) { |
||
222 | $statusCode = $e->getCode() ?: 1; |
||
223 | } |
||
224 | |||
225 | // If there were any error conditions in bootstrapping Robo, |
||
226 | // print them only if the requested command did not complete |
||
227 | // successfully. |
||
228 | if ($statusCode) { |
||
229 | foreach ($this->errorConditions as $msg => $color) { |
||
230 | $this->yell($msg, 40, $color); |
||
231 | } |
||
232 | } |
||
233 | return $statusCode; |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * @param \Symfony\Component\Console\Output\OutputInterface $output |
||
238 | * |
||
239 | * @return null|string |
||
240 | */ |
||
241 | protected function getRoboFileCommands($output) |
||
242 | { |
||
243 | if (!$this->loadRoboFile($output)) { |
||
244 | return; |
||
245 | } |
||
246 | return $this->roboClass; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * @param \Robo\Application $app |
||
251 | * @param array $commandClasses |
||
252 | */ |
||
253 | public function registerCommandClasses($app, $commandClasses) |
||
254 | { |
||
255 | foreach ((array)$commandClasses as $commandClass) { |
||
256 | $this->registerCommandClass($app, $commandClass); |
||
257 | } |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * @param $relativeNamespace |
||
262 | * |
||
263 | * @return array|string[] |
||
264 | */ |
||
265 | protected function discoverCommandClasses($relativeNamespace) |
||
266 | { |
||
267 | /** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */ |
||
268 | $discovery = Robo::service('relativeNamespaceDiscovery'); |
||
269 | $discovery->setRelativeNamespace($relativeNamespace.'\Commands') |
||
270 | ->setSearchPattern('*Commands.php'); |
||
271 | return $discovery->getClasses(); |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * @param \Robo\Application $app |
||
276 | * @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass |
||
277 | * |
||
278 | * @return mixed|void |
||
279 | */ |
||
280 | public function registerCommandClass($app, $commandClass) |
||
281 | { |
||
282 | $container = Robo::getContainer(); |
||
283 | $roboCommandFileInstance = $this->instantiateCommandClass($commandClass); |
||
284 | if (!$roboCommandFileInstance) { |
||
285 | return; |
||
286 | } |
||
287 | |||
288 | // Register commands for all of the public methods in the RoboFile. |
||
289 | $commandFactory = $container->get('commandFactory'); |
||
290 | $commandList = $commandFactory->createCommandsFromClass($roboCommandFileInstance); |
||
291 | foreach ($commandList as $command) { |
||
292 | $app->add($command); |
||
293 | } |
||
294 | return $roboCommandFileInstance; |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass |
||
299 | * |
||
300 | * @return null|object |
||
301 | */ |
||
302 | protected function instantiateCommandClass($commandClass) |
||
303 | { |
||
304 | $container = Robo::getContainer(); |
||
305 | |||
306 | // Register the RoboFile with the container and then immediately |
||
307 | // fetch it; this ensures that all of the inflectors will run. |
||
308 | // If the command class is already an instantiated object, then |
||
309 | // just use it exactly as it was provided to us. |
||
310 | if (is_string($commandClass)) { |
||
311 | if (!class_exists($commandClass)) { |
||
312 | return; |
||
313 | } |
||
314 | $reflectionClass = new \ReflectionClass($commandClass); |
||
315 | if ($reflectionClass->isAbstract()) { |
||
316 | return; |
||
317 | } |
||
318 | |||
319 | $commandFileName = "{$commandClass}Commands"; |
||
320 | $container->share($commandFileName, $commandClass); |
||
321 | $commandClass = $container->get($commandFileName); |
||
322 | } |
||
323 | // If the command class is a Builder Aware Interface, then |
||
324 | // ensure that it has a builder. Every command class needs |
||
325 | // its own collection builder, as they have references to each other. |
||
326 | if ($commandClass instanceof BuilderAwareInterface) { |
||
327 | $builder = CollectionBuilder::create($container, $commandClass); |
||
328 | $commandClass->setBuilder($builder); |
||
329 | } |
||
330 | if ($commandClass instanceof ContainerAwareInterface) { |
||
331 | $commandClass->setContainer($container); |
||
332 | } |
||
333 | return $commandClass; |
||
334 | } |
||
335 | |||
336 | public function installRoboHandlers() |
||
337 | { |
||
338 | register_shutdown_function(array($this, 'shutdown')); |
||
339 | set_error_handler(array($this, 'handleError')); |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Process a shebang script, if one was used to launch this Runner. |
||
344 | * |
||
345 | * @param array $args |
||
346 | * |
||
347 | * @return array $args with shebang script removed |
||
348 | */ |
||
349 | protected function shebang($args) |
||
350 | { |
||
351 | // Option 1: Shebang line names Robo, but includes no parameters. |
||
352 | // #!/bin/env robo |
||
353 | // The robo class may contain multiple commands; the user may |
||
354 | // select which one to run, or even get a list of commands or |
||
355 | // run 'help' on any of the available commands as usual. |
||
356 | View Code Duplication | if ((count($args) > 1) && $this->isShebangFile($args[1])) { |
|
357 | return array_merge([$args[0]], array_slice($args, 2)); |
||
358 | } |
||
359 | // Option 2: Shebang line stipulates which command to run. |
||
360 | // #!/bin/env robo mycommand |
||
361 | // The robo class must contain a public method named 'mycommand'. |
||
362 | // This command will be executed every time. Arguments and options |
||
363 | // may be provided on the commandline as usual. |
||
364 | View Code Duplication | if ((count($args) > 2) && $this->isShebangFile($args[2])) { |
|
365 | return array_merge([$args[0]], explode(' ', $args[1]), array_slice($args, 3)); |
||
366 | } |
||
367 | return $args; |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * Determine if the specified argument is a path to a shebang script. |
||
372 | * If so, load it. |
||
373 | * |
||
374 | * @param string $filepath file to check |
||
375 | * |
||
376 | * @return bool Returns TRUE if shebang script was processed |
||
377 | */ |
||
378 | protected function isShebangFile($filepath) |
||
379 | { |
||
380 | // Avoid trying to call $filepath on remote URLs |
||
381 | if ((strpos($filepath, '://') !== false) && (substr($filepath, 0, 7) != 'file://')) { |
||
382 | return false; |
||
383 | } |
||
384 | if (!is_file($filepath)) { |
||
385 | return false; |
||
386 | } |
||
387 | $fp = fopen($filepath, "r"); |
||
388 | if ($fp === false) { |
||
389 | return false; |
||
390 | } |
||
391 | $line = fgets($fp); |
||
392 | $result = $this->isShebangLine($line); |
||
393 | if ($result) { |
||
394 | while ($line = fgets($fp)) { |
||
395 | $line = trim($line); |
||
396 | if ($line == '<?php') { |
||
397 | $script = stream_get_contents($fp); |
||
398 | if (preg_match('#^class *([^ ]+)#m', $script, $matches)) { |
||
399 | $this->roboClass = $matches[1]; |
||
400 | eval($script); |
||
401 | $result = true; |
||
402 | } |
||
403 | } |
||
404 | } |
||
405 | } |
||
406 | fclose($fp); |
||
407 | |||
408 | return $result; |
||
409 | } |
||
410 | |||
411 | /** |
||
412 | * Test to see if the provided line is a robo 'shebang' line. |
||
413 | * |
||
414 | * @param string $line |
||
415 | * |
||
416 | * @return bool |
||
417 | */ |
||
418 | protected function isShebangLine($line) |
||
419 | { |
||
420 | return ((substr($line, 0, 2) == '#!') && (strstr($line, 'robo') !== false)); |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * Check for Robo-specific arguments such as --load-from, process them, |
||
425 | * and remove them from the array. We have to process --load-from before |
||
426 | * we set up Symfony Console. |
||
427 | * |
||
428 | * @param array $argv |
||
429 | * |
||
430 | * @return array |
||
431 | */ |
||
432 | protected function processRoboOptions($argv) |
||
433 | { |
||
434 | // loading from other directory |
||
435 | $pos = $this->arraySearchBeginsWith('--load-from', $argv) ?: array_search('-f', $argv); |
||
436 | if ($pos === false) { |
||
437 | return $argv; |
||
438 | } |
||
439 | |||
440 | $passThru = array_search('--', $argv); |
||
441 | if (($passThru !== false) && ($passThru < $pos)) { |
||
442 | return $argv; |
||
443 | } |
||
444 | |||
445 | if (substr($argv[$pos], 0, 12) == '--load-from=') { |
||
446 | $this->dir = substr($argv[$pos], 12); |
||
447 | } elseif (isset($argv[$pos +1])) { |
||
448 | $this->dir = $argv[$pos +1]; |
||
449 | unset($argv[$pos +1]); |
||
450 | } |
||
451 | unset($argv[$pos]); |
||
452 | // Make adjustments if '--load-from' points at a file. |
||
453 | if (is_file($this->dir) || (substr($this->dir, -4) == '.php')) { |
||
454 | $this->roboFile = basename($this->dir); |
||
455 | $this->dir = dirname($this->dir); |
||
456 | $className = basename($this->roboFile, '.php'); |
||
457 | if ($className != $this->roboFile) { |
||
458 | $this->roboClass = $className; |
||
459 | } |
||
460 | } |
||
461 | // Convert directory to a real path, but only if the |
||
462 | // path exists. We do not want to lose the original |
||
463 | // directory if the user supplied a bad value. |
||
464 | $realDir = realpath($this->dir); |
||
465 | if ($realDir) { |
||
466 | chdir($realDir); |
||
467 | $this->dir = $realDir; |
||
468 | } |
||
469 | |||
470 | return $argv; |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * @param string $needle |
||
475 | * @param string[] $haystack |
||
476 | * |
||
477 | * @return bool|int |
||
478 | */ |
||
479 | protected function arraySearchBeginsWith($needle, $haystack) |
||
480 | { |
||
481 | for ($i = 0; $i < count($haystack); ++$i) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
|
|||
482 | if (substr($haystack[$i], 0, strlen($needle)) == $needle) { |
||
483 | return $i; |
||
484 | } |
||
485 | } |
||
486 | return false; |
||
487 | } |
||
488 | |||
489 | public function shutdown() |
||
490 | { |
||
491 | $error = error_get_last(); |
||
492 | if (!is_array($error)) { |
||
493 | return; |
||
494 | } |
||
495 | $this->writeln(sprintf("<error>ERROR: %s \nin %s:%d\n</error>", $error['message'], $error['file'], $error['line'])); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * This is just a proxy error handler that checks the current error_reporting level. |
||
500 | * In case error_reporting is disabled the error is marked as handled, otherwise |
||
501 | * the normal internal error handling resumes. |
||
502 | * |
||
503 | * @return bool |
||
504 | */ |
||
505 | public function handleError() |
||
506 | { |
||
507 | if (error_reporting() === 0) { |
||
508 | return true; |
||
509 | } |
||
510 | return false; |
||
511 | } |
||
512 | |||
513 | /** |
||
514 | * @return string |
||
515 | */ |
||
516 | public function getSelfUpdateRepository() |
||
517 | { |
||
518 | return $this->selfUpdateRepository; |
||
519 | } |
||
520 | |||
521 | /** |
||
522 | * @param $selfUpdateRepository |
||
523 | * |
||
524 | * @return $this |
||
525 | */ |
||
526 | public function setSelfUpdateRepository($selfUpdateRepository) |
||
527 | { |
||
528 | $this->selfUpdateRepository = $selfUpdateRepository; |
||
529 | return $this; |
||
530 | } |
||
531 | |||
532 | /** |
||
533 | * @param string $configFilename |
||
534 | * |
||
535 | * @return $this |
||
536 | */ |
||
537 | public function setConfigurationFilename($configFilename) |
||
538 | { |
||
539 | $this->configFilename = $configFilename; |
||
540 | return $this; |
||
541 | } |
||
542 | |||
543 | /** |
||
544 | * @param string $envConfigPrefix |
||
545 | * |
||
546 | * @return $this |
||
547 | */ |
||
548 | public function setEnvConfigPrefix($envConfigPrefix) |
||
549 | { |
||
550 | $this->envConfigPrefix = $envConfigPrefix; |
||
551 | return $this; |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * @param \Composer\Autoload\ClassLoader $classLoader |
||
556 | * |
||
557 | * @return $this |
||
558 | */ |
||
559 | public function setClassLoader(ClassLoader $classLoader) |
||
560 | { |
||
561 | $this->classLoader = $classLoader; |
||
562 | return $this; |
||
563 | } |
||
564 | |||
565 | /** |
||
566 | * @param string $relativeNamespace |
||
567 | * |
||
568 | * @return $this |
||
569 | */ |
||
570 | public function setRelativePluginNamespace($relativeNamespace) |
||
571 | { |
||
572 | $this->relativePluginNamespace = $relativeNamespace; |
||
573 | return $this; |
||
574 | } |
||
575 | } |
||
576 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: