PluginProbe
Depicter — Popup & Slider Builder / trunk
Depicter — Popup & Slider Builder vtrunk
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / vendor / symfony / console / Command / Command.php

Command.php in Depicter — Popup & Slider Builder trunk, at vendor/symfony/console/Command/Command.php

711 lines 20.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Console\Command;
13
14 use Symfony\Component\Console\Application;
15 use Symfony\Component\Console\Attribute\AsCommand;
16 use Symfony\Component\Console\Completion\CompletionInput;
17 use Symfony\Component\Console\Completion\CompletionSuggestions;
18 use Symfony\Component\Console\Exception\ExceptionInterface;
19 use Symfony\Component\Console\Exception\InvalidArgumentException;
20 use Symfony\Component\Console\Exception\LogicException;
21 use Symfony\Component\Console\Helper\HelperSet;
22 use Symfony\Component\Console\Input\InputArgument;
23 use Symfony\Component\Console\Input\InputDefinition;
24 use Symfony\Component\Console\Input\InputInterface;
25 use Symfony\Component\Console\Input\InputOption;
26 use Symfony\Component\Console\Output\OutputInterface;
27
28 /**
29 * Base class for all commands.
30 *
31 * @author Fabien Potencier <fabien@symfony.com>
32 */
33 class Command
34 {
35 // see https://tldp.org/LDP/abs/html/exitcodes.html
36 public const SUCCESS = 0;
37 public const FAILURE = 1;
38 public const INVALID = 2;
39
40 /**
41 * @var string|null The default command name
42 */
43 protected static $defaultName;
44
45 /**
46 * @var string|null The default command description
47 */
48 protected static $defaultDescription;
49
50 private $application;
51 private $name;
52 private $processTitle;
53 private $aliases = [];
54 private $definition;
55 private $hidden = false;
56 private $help = '';
57 private $description = '';
58 private $fullDefinition;
59 private $ignoreValidationErrors = false;
60 private $code;
61 private $synopsis = [];
62 private $usages = [];
63 private $helperSet;
64
65 /**
66 * @return string|null
67 */
68 public static function getDefaultName()
69 {
70 $class = static::class;
71
72 if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
73 return $attribute[0]->newInstance()->name;
74 }
75
76 $r = new \ReflectionProperty($class, 'defaultName');
77
78 return $class === $r->class ? static::$defaultName : null;
79 }
80
81 public static function getDefaultDescription(): ?string
82 {
83 $class = static::class;
84
85 if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
86 return $attribute[0]->newInstance()->description;
87 }
88
89 $r = new \ReflectionProperty($class, 'defaultDescription');
90
91 return $class === $r->class ? static::$defaultDescription : null;
92 }
93
94 /**
95 * @param string|null $name The name of the command; passing null means it must be set in configure()
96 *
97 * @throws LogicException When the command name is empty
98 */
99 public function __construct(?string $name = null)
100 {
101 $this->definition = new InputDefinition();
102
103 if (null === $name && null !== $name = static::getDefaultName()) {
104 $aliases = explode('|', $name);
105
106 if ('' === $name = array_shift($aliases)) {
107 $this->setHidden(true);
108 $name = array_shift($aliases);
109 }
110
111 $this->setAliases($aliases);
112 }
113
114 if (null !== $name) {
115 $this->setName($name);
116 }
117
118 if ('' === $this->description) {
119 $this->setDescription(static::getDefaultDescription() ?? '');
120 }
121
122 $this->configure();
123 }
124
125 /**
126 * Ignores validation errors.
127 *
128 * This is mainly useful for the help command.
129 */
130 public function ignoreValidationErrors()
131 {
132 $this->ignoreValidationErrors = true;
133 }
134
135 public function setApplication(?Application $application = null)
136 {
137 $this->application = $application;
138 if ($application) {
139 $this->setHelperSet($application->getHelperSet());
140 } else {
141 $this->helperSet = null;
142 }
143
144 $this->fullDefinition = null;
145 }
146
147 public function setHelperSet(HelperSet $helperSet)
148 {
149 $this->helperSet = $helperSet;
150 }
151
152 /**
153 * Gets the helper set.
154 *
155 * @return HelperSet|null
156 */
157 public function getHelperSet()
158 {
159 return $this->helperSet;
160 }
161
162 /**
163 * Gets the application instance for this command.
164 *
165 * @return Application|null
166 */
167 public function getApplication()
168 {
169 return $this->application;
170 }
171
172 /**
173 * Checks whether the command is enabled or not in the current environment.
174 *
175 * Override this to check for x or y and return false if the command cannot
176 * run properly under the current conditions.
177 *
178 * @return bool
179 */
180 public function isEnabled()
181 {
182 return true;
183 }
184
185 /**
186 * Configures the current command.
187 */
188 protected function configure()
189 {
190 }
191
192 /**
193 * Executes the current command.
194 *
195 * This method is not abstract because you can use this class
196 * as a concrete class. In this case, instead of defining the
197 * execute() method, you set the code to execute by passing
198 * a Closure to the setCode() method.
199 *
200 * @return int 0 if everything went fine, or an exit code
201 *
202 * @throws LogicException When this abstract method is not implemented
203 *
204 * @see setCode()
205 */
206 protected function execute(InputInterface $input, OutputInterface $output)
207 {
208 throw new LogicException('You must override the execute() method in the concrete command class.');
209 }
210
211 /**
212 * Interacts with the user.
213 *
214 * This method is executed before the InputDefinition is validated.
215 * This means that this is the only place where the command can
216 * interactively ask for values of missing required arguments.
217 */
218 protected function interact(InputInterface $input, OutputInterface $output)
219 {
220 }
221
222 /**
223 * Initializes the command after the input has been bound and before the input
224 * is validated.
225 *
226 * This is mainly useful when a lot of commands extends one main command
227 * where some things need to be initialized based on the input arguments and options.
228 *
229 * @see InputInterface::bind()
230 * @see InputInterface::validate()
231 */
232 protected function initialize(InputInterface $input, OutputInterface $output)
233 {
234 }
235
236 /**
237 * Runs the command.
238 *
239 * The code to execute is either defined directly with the
240 * setCode() method or by overriding the execute() method
241 * in a sub-class.
242 *
243 * @return int The command exit code
244 *
245 * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
246 *
247 * @see setCode()
248 * @see execute()
249 */
250 public function run(InputInterface $input, OutputInterface $output)
251 {
252 // add the application arguments and options
253 $this->mergeApplicationDefinition();
254
255 // bind the input against the command specific arguments/options
256 try {
257 $input->bind($this->getDefinition());
258 } catch (ExceptionInterface $e) {
259 if (!$this->ignoreValidationErrors) {
260 throw $e;
261 }
262 }
263
264 $this->initialize($input, $output);
265
266 if (null !== $this->processTitle) {
267 if (\function_exists('cli_set_process_title')) {
268 if (!@cli_set_process_title($this->processTitle)) {
269 if ('Darwin' === \PHP_OS) {
270 $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
271 } else {
272 cli_set_process_title($this->processTitle);
273 }
274 }
275 } elseif (\function_exists('setproctitle')) {
276 setproctitle($this->processTitle);
277 } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
278 $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
279 }
280 }
281
282 if ($input->isInteractive()) {
283 $this->interact($input, $output);
284 }
285
286 // The command name argument is often omitted when a command is executed directly with its run() method.
287 // It would fail the validation if we didn't make sure the command argument is present,
288 // since it's required by the application.
289 if ($input->hasArgument('command') && null === $input->getArgument('command')) {
290 $input->setArgument('command', $this->getName());
291 }
292
293 $input->validate();
294
295 if ($this->code) {
296 $statusCode = ($this->code)($input, $output);
297 } else {
298 $statusCode = $this->execute($input, $output);
299
300 if (!\is_int($statusCode)) {
301 throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
302 }
303 }
304
305 return is_numeric($statusCode) ? (int) $statusCode : 0;
306 }
307
308 /**
309 * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
310 */
311 public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
312 {
313 }
314
315 /**
316 * Sets the code to execute when running this command.
317 *
318 * If this method is used, it overrides the code defined
319 * in the execute() method.
320 *
321 * @param callable $code A callable(InputInterface $input, OutputInterface $output)
322 *
323 * @return $this
324 *
325 * @throws InvalidArgumentException
326 *
327 * @see execute()
328 */
329 public function setCode(callable $code)
330 {
331 if ($code instanceof \Closure) {
332 $r = new \ReflectionFunction($code);
333 if (null === $r->getClosureThis()) {
334 set_error_handler(static function () {});
335 try {
336 if ($c = \Closure::bind($code, $this)) {
337 $code = $c;
338 }
339 } finally {
340 restore_error_handler();
341 }
342 }
343 }
344
345 $this->code = $code;
346
347 return $this;
348 }
349
350 /**
351 * Merges the application definition with the command definition.
352 *
353 * This method is not part of public API and should not be used directly.
354 *
355 * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
356 *
357 * @internal
358 */
359 public function mergeApplicationDefinition(bool $mergeArgs = true)
360 {
361 if (null === $this->application) {
362 return;
363 }
364
365 $this->fullDefinition = new InputDefinition();
366 $this->fullDefinition->setOptions($this->definition->getOptions());
367 $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());
368
369 if ($mergeArgs) {
370 $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
371 $this->fullDefinition->addArguments($this->definition->getArguments());
372 } else {
373 $this->fullDefinition->setArguments($this->definition->getArguments());
374 }
375 }
376
377 /**
378 * Sets an array of argument and option instances.
379 *
380 * @param array|InputDefinition $definition An array of argument and option instances or a definition instance
381 *
382 * @return $this
383 */
384 public function setDefinition($definition)
385 {
386 if ($definition instanceof InputDefinition) {
387 $this->definition = $definition;
388 } else {
389 $this->definition->setDefinition($definition);
390 }
391
392 $this->fullDefinition = null;
393
394 return $this;
395 }
396
397 /**
398 * Gets the InputDefinition attached to this Command.
399 *
400 * @return InputDefinition
401 */
402 public function getDefinition()
403 {
404 return $this->fullDefinition ?? $this->getNativeDefinition();
405 }
406
407 /**
408 * Gets the InputDefinition to be used to create representations of this Command.
409 *
410 * Can be overridden to provide the original command representation when it would otherwise
411 * be changed by merging with the application InputDefinition.
412 *
413 * This method is not part of public API and should not be used directly.
414 *
415 * @return InputDefinition
416 */
417 public function getNativeDefinition()
418 {
419 if (null === $this->definition) {
420 throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
421 }
422
423 return $this->definition;
424 }
425
426 /**
427 * Adds an argument.
428 *
429 * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
430 * @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
431 *
432 * @return $this
433 *
434 * @throws InvalidArgumentException When argument mode is not valid
435 */
436 public function addArgument(string $name, ?int $mode = null, string $description = '', $default = null)
437 {
438 $this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
439 if (null !== $this->fullDefinition) {
440 $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default));
441 }
442
443 return $this;
444 }
445
446 /**
447 * Adds an option.
448 *
449 * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
450 * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants
451 * @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
452 *
453 * @return $this
454 *
455 * @throws InvalidArgumentException If option mode is invalid or incompatible
456 */
457 public function addOption(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null)
458 {
459 $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
460 if (null !== $this->fullDefinition) {
461 $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
462 }
463
464 return $this;
465 }
466
467 /**
468 * Sets the name of the command.
469 *
470 * This method can set both the namespace and the name if
471 * you separate them by a colon (:)
472 *
473 * $command->setName('foo:bar');
474 *
475 * @return $this
476 *
477 * @throws InvalidArgumentException When the name is invalid
478 */
479 public function setName(string $name)
480 {
481 $this->validateName($name);
482
483 $this->name = $name;
484
485 return $this;
486 }
487
488 /**
489 * Sets the process title of the command.
490 *
491 * This feature should be used only when creating a long process command,
492 * like a daemon.
493 *
494 * @return $this
495 */
496 public function setProcessTitle(string $title)
497 {
498 $this->processTitle = $title;
499
500 return $this;
501 }
502
503 /**
504 * Returns the command name.
505 *
506 * @return string|null
507 */
508 public function getName()
509 {
510 return $this->name;
511 }
512
513 /**
514 * @param bool $hidden Whether or not the command should be hidden from the list of commands
515 * The default value will be true in Symfony 6.0
516 *
517 * @return $this
518 *
519 * @final since Symfony 5.1
520 */
521 public function setHidden(bool $hidden /* = true */)
522 {
523 $this->hidden = $hidden;
524
525 return $this;
526 }
527
528 /**
529 * @return bool whether the command should be publicly shown or not
530 */
531 public function isHidden()
532 {
533 return $this->hidden;
534 }
535
536 /**
537 * Sets the description for the command.
538 *
539 * @return $this
540 */
541 public function setDescription(string $description)
542 {
543 $this->description = $description;
544
545 return $this;
546 }
547
548 /**
549 * Returns the description for the command.
550 *
551 * @return string
552 */
553 public function getDescription()
554 {
555 return $this->description;
556 }
557
558 /**
559 * Sets the help for the command.
560 *
561 * @return $this
562 */
563 public function setHelp(string $help)
564 {
565 $this->help = $help;
566
567 return $this;
568 }
569
570 /**
571 * Returns the help for the command.
572 *
573 * @return string
574 */
575 public function getHelp()
576 {
577 return $this->help;
578 }
579
580 /**
581 * Returns the processed help for the command replacing the %command.name% and
582 * %command.full_name% patterns with the real values dynamically.
583 *
584 * @return string
585 */
586 public function getProcessedHelp()
587 {
588 $name = $this->name;
589 $isSingleCommand = $this->application && $this->application->isSingleCommand();
590
591 $placeholders = [
592 '%command.name%',
593 '%command.full_name%',
594 ];
595 $replacements = [
596 $name,
597 $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
598 ];
599
600 return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
601 }
602
603 /**
604 * Sets the aliases for the command.
605 *
606 * @param string[] $aliases An array of aliases for the command
607 *
608 * @return $this
609 *
610 * @throws InvalidArgumentException When an alias is invalid
611 */
612 public function setAliases(iterable $aliases)
613 {
614 $list = [];
615
616 foreach ($aliases as $alias) {
617 $this->validateName($alias);
618 $list[] = $alias;
619 }
620
621 $this->aliases = \is_array($aliases) ? $aliases : $list;
622
623 return $this;
624 }
625
626 /**
627 * Returns the aliases for the command.
628 *
629 * @return array
630 */
631 public function getAliases()
632 {
633 return $this->aliases;
634 }
635
636 /**
637 * Returns the synopsis for the command.
638 *
639 * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
640 *
641 * @return string
642 */
643 public function getSynopsis(bool $short = false)
644 {
645 $key = $short ? 'short' : 'long';
646
647 if (!isset($this->synopsis[$key])) {
648 $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
649 }
650
651 return $this->synopsis[$key];
652 }
653
654 /**
655 * Add a command usage example, it'll be prefixed with the command name.
656 *
657 * @return $this
658 */
659 public function addUsage(string $usage)
660 {
661 if (!str_starts_with($usage, $this->name)) {
662 $usage = sprintf('%s %s', $this->name, $usage);
663 }
664
665 $this->usages[] = $usage;
666
667 return $this;
668 }
669
670 /**
671 * Returns alternative usages of the command.
672 *
673 * @return array
674 */
675 public function getUsages()
676 {
677 return $this->usages;
678 }
679
680 /**
681 * Gets a helper instance by name.
682 *
683 * @return mixed
684 *
685 * @throws LogicException if no HelperSet is defined
686 * @throws InvalidArgumentException if the helper is not defined
687 */
688 public function getHelper(string $name)
689 {
690 if (null === $this->helperSet) {
691 throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
692 }
693
694 return $this->helperSet->get($name);
695 }
696
697 /**
698 * Validates a command name.
699 *
700 * It must be non-empty and parts can optionally be separated by ":".
701 *
702 * @throws InvalidArgumentException When the name is invalid
703 */
704 private function validateName(string $name)
705 {
706 if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
707 throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
708 }
709 }
710 }
711