[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/console/src/ -> Application.php (source)

   1  <?php
   2  /**
   3   * Part of the Joomla Framework Console Package
   4   *
   5   * @copyright  Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
   6   * @license    GNU General Public License version 2 or later; see LICENSE
   7   */
   8  
   9  namespace Joomla\Console;
  10  
  11  use Joomla\Application\AbstractApplication;
  12  use Joomla\Application\ApplicationEvents;
  13  use Joomla\Console\Command\AbstractCommand;
  14  use Joomla\Console\Command\HelpCommand;
  15  use Joomla\Console\Event\ApplicationErrorEvent;
  16  use Joomla\Console\Event\BeforeCommandExecuteEvent;
  17  use Joomla\Console\Event\CommandErrorEvent;
  18  use Joomla\Console\Event\TerminateEvent;
  19  use Joomla\Console\Exception\NamespaceNotFoundException;
  20  use Joomla\Registry\Registry;
  21  use Joomla\String\StringHelper;
  22  use Symfony\Component\Console\Exception\CommandNotFoundException;
  23  use Symfony\Component\Console\Exception\ExceptionInterface;
  24  use Symfony\Component\Console\Exception\LogicException;
  25  use Symfony\Component\Console\Formatter\OutputFormatter;
  26  use Symfony\Component\Console\Helper\DebugFormatterHelper;
  27  use Symfony\Component\Console\Helper\FormatterHelper;
  28  use Symfony\Component\Console\Helper\HelperSet;
  29  use Symfony\Component\Console\Helper\ProcessHelper;
  30  use Symfony\Component\Console\Helper\QuestionHelper;
  31  use Symfony\Component\Console\Input\ArgvInput;
  32  use Symfony\Component\Console\Input\ArrayInput;
  33  use Symfony\Component\Console\Input\InputArgument;
  34  use Symfony\Component\Console\Input\InputAwareInterface;
  35  use Symfony\Component\Console\Input\InputDefinition;
  36  use Symfony\Component\Console\Input\InputInterface;
  37  use Symfony\Component\Console\Input\InputOption;
  38  use Symfony\Component\Console\Output\ConsoleOutput;
  39  use Symfony\Component\Console\Output\ConsoleOutputInterface;
  40  use Symfony\Component\Console\Output\OutputInterface;
  41  use Symfony\Component\Console\Style\SymfonyStyle;
  42  use Symfony\Component\Console\Terminal;
  43  use Symfony\Component\ErrorHandler\ErrorHandler;
  44  
  45  /**
  46   * Base application class for a Joomla! command line application.
  47   *
  48   * @since  2.0.0
  49   */
  50  class Application extends AbstractApplication
  51  {
  52      /**
  53       * Flag indicating the application should automatically exit after the command is run.
  54       *
  55       * @var    boolean
  56       * @since  2.0.0
  57       */
  58      private $autoExit = true;
  59  
  60      /**
  61       * Flag indicating the application should catch and handle Throwables.
  62       *
  63       * @var    boolean
  64       * @since  2.0.0
  65       */
  66      private $catchThrowables = true;
  67  
  68      /**
  69       * The available commands.
  70       *
  71       * @var    AbstractCommand[]
  72       * @since  2.0.0
  73       */
  74      private $commands = [];
  75  
  76      /**
  77       * The command loader.
  78       *
  79       * @var    Loader\LoaderInterface|null
  80       * @since  2.0.0
  81       */
  82      private $commandLoader;
  83  
  84      /**
  85       * Console input handler.
  86       *
  87       * @var    InputInterface
  88       * @since  2.0.0
  89       */
  90      private $consoleInput;
  91  
  92      /**
  93       * Console output handler.
  94       *
  95       * @var    OutputInterface
  96       * @since  2.0.0
  97       */
  98      private $consoleOutput;
  99  
 100      /**
 101       * The default command for the application.
 102       *
 103       * @var    string
 104       * @since  2.0.0
 105       */
 106      private $defaultCommand = 'list';
 107  
 108      /**
 109       * The application input definition.
 110       *
 111       * @var    InputDefinition|null
 112       * @since  2.0.0
 113       */
 114      private $definition;
 115  
 116      /**
 117       * The application helper set.
 118       *
 119       * @var    HelperSet|null
 120       * @since  2.0.0
 121       */
 122      private $helperSet;
 123  
 124      /**
 125       * Internal flag tracking if the command store has been initialised.
 126       *
 127       * @var    boolean
 128       * @since  2.0.0
 129       */
 130      private $initialised = false;
 131  
 132      /**
 133       * The name of the application.
 134       *
 135       * @var    string
 136       * @since  2.0.0
 137       */
 138      private $name = '';
 139  
 140      /**
 141       * Reference to the currently running command.
 142       *
 143       * @var    AbstractCommand|null
 144       * @since  2.0.0
 145       */
 146      private $runningCommand;
 147  
 148      /**
 149       * The console terminal helper.
 150       *
 151       * @var    Terminal
 152       * @since  2.0.0
 153       */
 154      private $terminal;
 155  
 156      /**
 157       * The version of the application.
 158       *
 159       * @var    string
 160       * @since  2.0.0
 161       */
 162      private $version = '';
 163  
 164      /**
 165       * Internal flag tracking if the user is seeking help for the given command.
 166       *
 167       * @var    boolean
 168       * @since  2.0.0
 169       */
 170      private $wantsHelp = false;
 171  
 172      /**
 173       * Class constructor.
 174       *
 175       * @param   InputInterface   $input   An optional argument to provide dependency injection for the application's input object.  If the argument is
 176       *                                    an InputInterface object that object will become the application's input object, otherwise a default input
 177       *                                    object is created.
 178       * @param   OutputInterface  $output  An optional argument to provide dependency injection for the application's output object.  If the argument
 179       *                                    is an OutputInterface object that object will become the application's output object, otherwise a default
 180       *                                    output object is created.
 181       * @param   Registry         $config  An optional argument to provide dependency injection for the application's config object.  If the argument
 182       *                                    is a Registry object that object will become the application's config object, otherwise a default config
 183       *                                    object is created.
 184       *
 185       * @since   2.0.0
 186       */
 187  	public function __construct(?InputInterface $input = null, ?OutputInterface $output = null, ?Registry $config = null)
 188      {
 189          // Close the application if we are not executed from the command line.
 190          if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv']))
 191          {
 192              $this->close();
 193          }
 194  
 195          $this->consoleInput  = $input ?: new ArgvInput;
 196          $this->consoleOutput = $output ?: new ConsoleOutput;
 197          $this->terminal      = new Terminal;
 198  
 199          // Call the constructor as late as possible (it runs `initialise`).
 200          parent::__construct($config);
 201      }
 202  
 203      /**
 204       * Adds a command object.
 205       *
 206       * If a command with the same name already exists, it will be overridden. If the command is not enabled it will not be added.
 207       *
 208       * @param   AbstractCommand  $command  The command to add to the application.
 209       *
 210       * @return  AbstractCommand
 211       *
 212       * @since   2.0.0
 213       * @throws  LogicException
 214       */
 215  	public function addCommand(AbstractCommand $command): AbstractCommand
 216      {
 217          $this->initCommands();
 218  
 219          if (!$command->isEnabled())
 220          {
 221              return $command;
 222          }
 223  
 224          $command->setApplication($this);
 225  
 226          try
 227          {
 228              $command->getDefinition();
 229          }
 230          catch (\TypeError $exception)
 231          {
 232              throw new LogicException(sprintf('Command class "%s" is not correctly initialised.', \get_class($command)), 0, $exception);
 233          }
 234  
 235          if (!$command->getName())
 236          {
 237              throw new LogicException(sprintf('The command class "%s" does not have a name.', \get_class($command)));
 238          }
 239  
 240          $this->commands[$command->getName()] = $command;
 241  
 242          foreach ($command->getAliases() as $alias)
 243          {
 244              $this->commands[$alias] = $command;
 245          }
 246  
 247          return $command;
 248      }
 249  
 250      /**
 251       * Configures the console input and output instances for the process.
 252       *
 253       * @return  void
 254       *
 255       * @since   2.0.0
 256       */
 257  	protected function configureIO(): void
 258      {
 259          if ($this->consoleInput->hasParameterOption(['--ansi'], true))
 260          {
 261              $this->consoleOutput->setDecorated(true);
 262          }
 263          elseif ($this->consoleInput->hasParameterOption(['--no-ansi'], true))
 264          {
 265              $this->consoleOutput->setDecorated(false);
 266          }
 267  
 268          if ($this->consoleInput->hasParameterOption(['--no-interaction', '-n'], true))
 269          {
 270              $this->consoleInput->setInteractive(false);
 271          }
 272  
 273          if ($this->consoleInput->hasParameterOption(['--quiet', '-q'], true))
 274          {
 275              $this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET);
 276              $this->consoleInput->setInteractive(false);
 277          }
 278          else
 279          {
 280              if ($this->consoleInput->hasParameterOption('-vvv', true)
 281                  || $this->consoleInput->hasParameterOption('--verbose=3', true)
 282                  || $this->consoleInput->getParameterOption('--verbose', false, true) === 3
 283              )
 284              {
 285                  $this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
 286              }
 287              elseif ($this->consoleInput->hasParameterOption('-vv', true)
 288                  || $this->consoleInput->hasParameterOption('--verbose=2', true)
 289                  || $this->consoleInput->getParameterOption('--verbose', false, true) === 2
 290              )
 291              {
 292                  $this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
 293              }
 294              elseif ($this->consoleInput->hasParameterOption('-v', true)
 295                  || $this->consoleInput->hasParameterOption('--verbose=1', true)
 296                  || $this->consoleInput->hasParameterOption('--verbose', true)
 297                  || $this->consoleInput->getParameterOption('--verbose', false, true)
 298              )
 299              {
 300                  $this->consoleOutput->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
 301              }
 302          }
 303      }
 304  
 305      /**
 306       * Method to run the application routines.
 307       *
 308       * @return  integer  The exit code for the application
 309       *
 310       * @since   2.0.0
 311       * @throws  \Throwable
 312       */
 313  	protected function doExecute(): int
 314      {
 315          $input  = $this->consoleInput;
 316          $output = $this->consoleOutput;
 317  
 318          // If requesting the version, short circuit the application and send the version data
 319          if ($input->hasParameterOption(['--version', '-V'], true))
 320          {
 321              $output->writeln($this->getLongVersion());
 322  
 323              return 0;
 324          }
 325  
 326          try
 327          {
 328              // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
 329              $input->bind($this->getDefinition());
 330          }
 331          catch (ExceptionInterface $e)
 332          {
 333              // Errors must be ignored, full binding/validation happens later when the command is known.
 334          }
 335  
 336          $name = $this->getCommandName($input);
 337  
 338          // Redirect to the help command if requested
 339          if ($input->hasParameterOption(['--help', '-h'], true))
 340          {
 341              // If no command name was given, use the help command with a minimal input; otherwise flag the request for processing later
 342              if (!$name)
 343              {
 344                  $name  = 'help';
 345                  $input = new ArrayInput(['command_name' => $this->defaultCommand]);
 346              }
 347              else
 348              {
 349                  $this->wantsHelp = true;
 350              }
 351          }
 352  
 353          // If we still do not have a command name, then the user has requested the application's default command
 354          if (!$name)
 355          {
 356              $name       = $this->defaultCommand;
 357              $definition = $this->getDefinition();
 358  
 359              // Overwrite the default value of the command argument with the default command name
 360              $definition->setArguments(
 361                  array_merge(
 362                      $definition->getArguments(),
 363                      [
 364                          'command' => new InputArgument(
 365                              'command',
 366                              InputArgument::OPTIONAL,
 367                              $definition->getArgument('command')->getDescription(),
 368                              $name
 369                          ),
 370                      ]
 371                  )
 372              );
 373          }
 374  
 375          try
 376          {
 377              $this->runningCommand = null;
 378  
 379              $command = $this->getCommand($name);
 380          }
 381          catch (\Throwable $e)
 382          {
 383              if ($e instanceof CommandNotFoundException && !($e instanceof NamespaceNotFoundException))
 384              {
 385                  (new SymfonyStyle($input, $output))->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
 386              }
 387  
 388              $event = new CommandErrorEvent($e, $this);
 389  
 390              $this->dispatchEvent(ConsoleEvents::COMMAND_ERROR, $event);
 391  
 392              if ($event->getExitCode() === 0)
 393              {
 394                  return 0;
 395              }
 396  
 397              $e = $event->getError();
 398  
 399              throw $e;
 400          }
 401  
 402          $this->runningCommand = $command;
 403          $exitCode             = $this->runCommand($command, $input, $output);
 404          $this->runningCommand = null;
 405  
 406          return $exitCode;
 407      }
 408  
 409      /**
 410       * Execute the application.
 411       *
 412       * @return  void
 413       *
 414       * @since   2.0.0
 415       * @throws  \Throwable
 416       */
 417  	public function execute()
 418      {
 419          putenv('LINES=' . $this->terminal->getHeight());
 420          putenv('COLUMNS=' . $this->terminal->getWidth());
 421  
 422          $this->configureIO();
 423  
 424          $renderThrowable = function (\Throwable $e)
 425          {
 426              $this->renderThrowable($e);
 427          };
 428  
 429          if ($phpHandler = set_exception_handler($renderThrowable))
 430          {
 431              restore_exception_handler();
 432  
 433              if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler)
 434              {
 435                  $errorHandler = true;
 436              }
 437              elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderThrowable))
 438              {
 439                  $phpHandler[0]->setExceptionHandler($errorHandler);
 440              }
 441          }
 442  
 443          try
 444          {
 445              $this->dispatchEvent(ApplicationEvents::BEFORE_EXECUTE);
 446  
 447              // Perform application routines.
 448              $exitCode = $this->doExecute();
 449  
 450              $this->dispatchEvent(ApplicationEvents::AFTER_EXECUTE);
 451          }
 452          catch (\Throwable $throwable)
 453          {
 454              if (!$this->shouldCatchThrowables())
 455              {
 456                  throw $throwable;
 457              }
 458  
 459              $renderThrowable($throwable);
 460  
 461              $event = new ApplicationErrorEvent($throwable, $this, $this->runningCommand);
 462  
 463              $this->dispatchEvent(ConsoleEvents::APPLICATION_ERROR, $event);
 464  
 465              $exitCode = $event->getExitCode();
 466  
 467              if (is_numeric($exitCode))
 468              {
 469                  $exitCode = (int) $exitCode;
 470  
 471                  if ($exitCode === 0)
 472                  {
 473                      $exitCode = 1;
 474                  }
 475              }
 476              else
 477              {
 478                  $exitCode = 1;
 479              }
 480          }
 481          finally
 482          {
 483              // If the exception handler changed, keep it; otherwise, unregister $renderThrowable
 484              if (!$phpHandler)
 485              {
 486                  if (set_exception_handler($renderThrowable) === $renderThrowable)
 487                  {
 488                      restore_exception_handler();
 489                  }
 490  
 491                  restore_exception_handler();
 492              }
 493              elseif (!$errorHandler)
 494              {
 495                  $finalHandler = $phpHandler[0]->setExceptionHandler(null);
 496  
 497                  if ($finalHandler !== $renderThrowable)
 498                  {
 499                      $phpHandler[0]->setExceptionHandler($finalHandler);
 500                  }
 501              }
 502  
 503              if ($this->shouldAutoExit() && isset($exitCode))
 504              {
 505                  $exitCode = $exitCode > 255 ? 255 : $exitCode;
 506                  $this->close($exitCode);
 507              }
 508          }
 509      }
 510  
 511      /**
 512       * Finds a registered namespace by a name.
 513       *
 514       * @param   string  $namespace  A namespace to search for
 515       *
 516       * @return  string
 517       *
 518       * @since   2.0.0
 519       * @throws  NamespaceNotFoundException When namespace is incorrect or ambiguous
 520       */
 521  	public function findNamespace(string $namespace): string
 522      {
 523          $allNamespaces = $this->getNamespaces();
 524  
 525          $expr = preg_replace_callback(
 526              '{([^:]+|)}',
 527              function ($matches)
 528              {
 529                  return preg_quote($matches[1]) . '[^:]*';
 530              },
 531              $namespace
 532          );
 533  
 534          $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
 535  
 536          if (empty($namespaces))
 537          {
 538              throw new NamespaceNotFoundException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
 539          }
 540  
 541          $exact = \in_array($namespace, $namespaces, true);
 542  
 543          if (\count($namespaces) > 1 && !$exact)
 544          {
 545              throw new NamespaceNotFoundException(sprintf('The namespace "%s" is ambiguous.', $namespace));
 546          }
 547  
 548          return $exact ? $namespace : reset($namespaces);
 549      }
 550  
 551      /**
 552       * Gets all commands, including those available through a command loader, optionally filtered on a command namespace.
 553       *
 554       * @param   string  $namespace  An optional command namespace to filter by.
 555       *
 556       * @return  AbstractCommand[]
 557       *
 558       * @since   2.0.0
 559       */
 560  	public function getAllCommands(string $namespace = ''): array
 561      {
 562          $this->initCommands();
 563  
 564          if ($namespace === '')
 565          {
 566              $commands = $this->commands;
 567  
 568              if (!$this->commandLoader)
 569              {
 570                  return $commands;
 571              }
 572  
 573              foreach ($this->commandLoader->getNames() as $name)
 574              {
 575                  if (!isset($commands[$name]))
 576                  {
 577                      $commands[$name] = $this->getCommand($name);
 578                  }
 579              }
 580  
 581              return $commands;
 582          }
 583  
 584          $commands = [];
 585  
 586          foreach ($this->commands as $name => $command)
 587          {
 588              if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1))
 589              {
 590                  $commands[$name] = $command;
 591              }
 592          }
 593  
 594          if ($this->commandLoader)
 595          {
 596              foreach ($this->commandLoader->getNames() as $name)
 597              {
 598                  if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1))
 599                  {
 600                      $commands[$name] = $this->getCommand($name);
 601                  }
 602              }
 603          }
 604  
 605          return $commands;
 606      }
 607  
 608      /**
 609       * Returns a registered command by name or alias.
 610       *
 611       * @param   string  $name  The command name or alias
 612       *
 613       * @return  AbstractCommand
 614       *
 615       * @since   2.0.0
 616       * @throws  CommandNotFoundException
 617       */
 618  	public function getCommand(string $name): AbstractCommand
 619      {
 620          $this->initCommands();
 621  
 622          if (!$this->hasCommand($name))
 623          {
 624              throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
 625          }
 626  
 627          // If the command isn't registered, pull it from the loader if registered
 628          if (!isset($this->commands[$name]) && $this->commandLoader)
 629          {
 630              $this->addCommand($this->commandLoader->get($name));
 631          }
 632  
 633          $command = $this->commands[$name];
 634  
 635          // If the user requested help, we'll fetch the help command now and inject the user's command into it
 636          if ($this->wantsHelp)
 637          {
 638              $this->wantsHelp = false;
 639  
 640              /** @var HelpCommand $helpCommand */
 641              $helpCommand = $this->getCommand('help');
 642              $helpCommand->setCommand($command);
 643  
 644              return $helpCommand;
 645          }
 646  
 647          return $command;
 648      }
 649  
 650      /**
 651       * Get the name of the command to run.
 652       *
 653       * @param   InputInterface  $input  The input to read the argument from
 654       *
 655       * @return  string|null
 656       *
 657       * @since   2.0.0
 658       */
 659  	protected function getCommandName(InputInterface $input): ?string
 660      {
 661          return $input->getFirstArgument();
 662      }
 663  
 664      /**
 665       * Get the registered commands.
 666       *
 667       * This method only retrieves commands which have been explicitly registered.  To get all commands including those from a
 668       * command loader, use the `getAllCommands()` method.
 669       *
 670       * @return  AbstractCommand[]
 671       *
 672       * @since   2.0.0
 673       */
 674  	public function getCommands(): array
 675      {
 676          return $this->commands;
 677      }
 678  
 679      /**
 680       * Get the console input handler.
 681       *
 682       * @return  InputInterface
 683       *
 684       * @since   2.0.0
 685       */
 686  	public function getConsoleInput(): InputInterface
 687      {
 688          return $this->consoleInput;
 689      }
 690  
 691      /**
 692       * Get the console output handler.
 693       *
 694       * @return  OutputInterface
 695       *
 696       * @since   2.0.0
 697       */
 698  	public function getConsoleOutput(): OutputInterface
 699      {
 700          return $this->consoleOutput;
 701      }
 702  
 703      /**
 704       * Get the commands which should be registered by default to the application.
 705       *
 706       * @return  AbstractCommand[]
 707       *
 708       * @since   2.0.0
 709       */
 710  	protected function getDefaultCommands(): array
 711      {
 712          return [
 713              new Command\ListCommand,
 714              new Command\HelpCommand,
 715          ];
 716      }
 717  
 718      /**
 719       * Builds the default input definition.
 720       *
 721       * @return  InputDefinition
 722       *
 723       * @since   2.0.0
 724       */
 725  	protected function getDefaultInputDefinition(): InputDefinition
 726      {
 727          return new InputDefinition(
 728              [
 729                  new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
 730                  new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display the help information'),
 731                  new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Flag indicating that all output should be silenced'),
 732                  new InputOption(
 733                      '--verbose',
 734                      '-v|vv|vvv',
 735                      InputOption::VALUE_NONE,
 736                      'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'
 737                  ),
 738                  new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Displays the application version'),
 739                  new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
 740                  new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
 741                  new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Flag to disable interacting with the user'),
 742              ]
 743          );
 744      }
 745  
 746      /**
 747       * Builds the default helper set.
 748       *
 749       * @return  HelperSet
 750       *
 751       * @since   2.0.0
 752       */
 753  	protected function getDefaultHelperSet(): HelperSet
 754      {
 755          return new HelperSet(
 756              [
 757                  new FormatterHelper,
 758                  new DebugFormatterHelper,
 759                  new ProcessHelper,
 760                  new QuestionHelper,
 761              ]
 762          );
 763      }
 764  
 765      /**
 766       * Gets the InputDefinition related to this Application.
 767       *
 768       * @return  InputDefinition
 769       *
 770       * @since   2.0.0
 771       */
 772  	public function getDefinition(): InputDefinition
 773      {
 774          if (!$this->definition)
 775          {
 776              $this->definition = $this->getDefaultInputDefinition();
 777          }
 778  
 779          return $this->definition;
 780      }
 781  
 782      /**
 783       * Get the helper set associated with the application.
 784       *
 785       * @return  HelperSet
 786       */
 787  	public function getHelperSet(): HelperSet
 788      {
 789          if (!$this->helperSet)
 790          {
 791              $this->helperSet = $this->getDefaultHelperSet();
 792          }
 793  
 794          return $this->helperSet;
 795      }
 796  
 797      /**
 798       * Get the long version string for the application.
 799       *
 800       * Typically, this is the application name and version and is used in the application help output.
 801       *
 802       * @return  string
 803       *
 804       * @since   2.0.0
 805       */
 806  	public function getLongVersion(): string
 807      {
 808          $name = $this->getName();
 809  
 810          if ($name === '')
 811          {
 812              $name = 'Joomla Console Application';
 813          }
 814  
 815          if ($this->getVersion() !== '')
 816          {
 817              return sprintf('%s <info>%s</info>', $name, $this->getVersion());
 818          }
 819  
 820          return $name;
 821      }
 822  
 823      /**
 824       * Get the name of the application.
 825       *
 826       * @return  string
 827       *
 828       * @since   2.0.0
 829       */
 830  	public function getName(): string
 831      {
 832          return $this->name;
 833      }
 834  
 835      /**
 836       * Returns an array of all unique namespaces used by currently registered commands.
 837       *
 838       * Note that this does not include the global namespace which always exists.
 839       *
 840       * @return  string[]
 841       *
 842       * @since   2.0.0
 843       */
 844  	public function getNamespaces(): array
 845      {
 846          $namespaces = [];
 847  
 848          foreach ($this->getAllCommands() as $command)
 849          {
 850              $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
 851  
 852              foreach ($command->getAliases() as $alias)
 853              {
 854                  $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
 855              }
 856          }
 857  
 858          return array_values(array_unique(array_filter($namespaces)));
 859      }
 860  
 861      /**
 862       * Get the version of the application.
 863       *
 864       * @return  string
 865       *
 866       * @since   2.0.0
 867       */
 868  	public function getVersion(): string
 869      {
 870          return $this->version;
 871      }
 872  
 873      /**
 874       * Check if the application has a command with the given name.
 875       *
 876       * @param   string  $name  The name of the command to check for existence.
 877       *
 878       * @return  boolean
 879       *
 880       * @since   2.0.0
 881       */
 882  	public function hasCommand(string $name): bool
 883      {
 884          $this->initCommands();
 885  
 886          // If command is already registered, we're good
 887          if (isset($this->commands[$name]))
 888          {
 889              return true;
 890          }
 891  
 892          // If there is no loader, we can't look for a command there
 893          if (!$this->commandLoader)
 894          {
 895              return false;
 896          }
 897  
 898          return $this->commandLoader->has($name);
 899      }
 900  
 901      /**
 902       * Custom initialisation method.
 903       *
 904       * @return  void
 905       *
 906       * @since   2.0.0
 907       */
 908  	protected function initialise(): void
 909      {
 910          // Set the current directory.
 911          $this->set('cwd', getcwd());
 912      }
 913  
 914      /**
 915       * Renders an error message for a Throwable object
 916       *
 917       * @param   \Throwable  $throwable  The Throwable object to render the message for.
 918       *
 919       * @return  void
 920       *
 921       * @since   2.0.0
 922       */
 923  	public function renderThrowable(\Throwable $throwable): void
 924      {
 925          $output = $this->consoleOutput instanceof ConsoleOutputInterface ? $this->consoleOutput->getErrorOutput() : $this->consoleOutput;
 926  
 927          $output->writeln('', OutputInterface::VERBOSITY_QUIET);
 928  
 929          $this->doRenderThrowable($throwable, $output);
 930  
 931          if (null !== $this->runningCommand)
 932          {
 933              $output->writeln(
 934                  sprintf(
 935                      '<info>%s</info>',
 936                      sprintf($this->runningCommand->getSynopsis(), $this->getName())
 937                  ),
 938                  OutputInterface::VERBOSITY_QUIET
 939              );
 940  
 941              $output->writeln('', OutputInterface::VERBOSITY_QUIET);
 942          }
 943      }
 944  
 945      /**
 946       * Handles recursively rendering error messages for a Throwable and all previous Throwables contained within.
 947       *
 948       * @param   \Throwable       $throwable  The Throwable object to render the message for.
 949       * @param   OutputInterface  $output     The output object to send the message to.
 950       *
 951       * @return  void
 952       *
 953       * @since   2.0.0
 954       */
 955  	protected function doRenderThrowable(\Throwable $throwable, OutputInterface $output): void
 956      {
 957          do
 958          {
 959              $message = trim($throwable->getMessage());
 960  
 961              if ($message === '' || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
 962              {
 963                  $class = \get_class($throwable);
 964  
 965                  if ($class[0] === 'c' && strpos($class, "class@anonymous\0") === 0)
 966                  {
 967                      $class = get_parent_class($class) ?: key(class_implements($class));
 968                  }
 969  
 970                  $title = sprintf('  [%s%s]  ', $class, ($code = $throwable->getCode()) !== 0 ? ' (' . $code . ')' : '');
 971                  $len   = StringHelper::strlen($title);
 972              }
 973              else
 974              {
 975                  $len = 0;
 976              }
 977  
 978              if (strpos($message, "class@anonymous\0") !== false)
 979              {
 980                  $message = preg_replace_callback(
 981                      '/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/',
 982                      function ($m)
 983                      {
 984                          return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0]))) . '@anonymous' : $m[0];
 985                      },
 986                      $message
 987                  );
 988              }
 989  
 990              $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
 991              $lines = [];
 992  
 993              foreach ($message !== '' ? preg_split('/\r?\n/', $message) : [] as $line)
 994              {
 995                  foreach ($this->splitStringByWidth($line, $width - 4) as $line)
 996                  {
 997                      // Pre-format lines to get the right string length
 998                      $lineLength = StringHelper::strlen($line) + 4;
 999                      $lines[]    = [$line, $lineLength];
1000                      $len        = max($lineLength, $len);
1001                  }
1002              }
1003  
1004              $messages = [];
1005  
1006              if (!$throwable instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
1007              {
1008                  $messages[] = sprintf(
1009                      '<comment>%s</comment>',
1010                      OutputFormatter::escape(
1011                          sprintf(
1012                              'In %s line %s:', basename($throwable->getFile()) ?: 'n/a', $throwable->getLine() ?: 'n/a'
1013                          )
1014                      )
1015                  );
1016              }
1017  
1018              $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
1019  
1020              if ($message === '' || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
1021              {
1022                  $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - StringHelper::strlen($title))));
1023              }
1024  
1025              foreach ($lines as $line)
1026              {
1027                  $messages[] = sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
1028              }
1029  
1030              $messages[] = $emptyLine;
1031              $messages[] = '';
1032  
1033              $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
1034  
1035              if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())
1036              {
1037                  $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
1038  
1039                  // Exception related properties
1040                  $trace = $throwable->getTrace();
1041                  array_unshift(
1042                      $trace,
1043                      [
1044                          'function' => '',
1045                          'file'     => $throwable->getFile() ?: 'n/a',
1046                          'line'     => $throwable->getLine() ?: 'n/a',
1047                          'args'     => [],
1048                      ]
1049                  );
1050  
1051                  for ($i = 0, $count = \count($trace); $i < $count; ++$i)
1052                  {
1053                      $class    = $trace[$i]['class'] ?? '';
1054                      $type     = $trace[$i]['type'] ?? '';
1055                      $function = $trace[$i]['function'] ?? '';
1056                      $file     = $trace[$i]['file'] ?? 'n/a';
1057                      $line     = $trace[$i]['line'] ?? 'n/a';
1058  
1059                      $output->writeln(
1060                          sprintf(
1061                              ' %s%s at <info>%s:%s</info>', $class, $function ? $type . $function . '()' : '', $file, $line
1062                          ),
1063                          OutputInterface::VERBOSITY_QUIET
1064                      );
1065                  }
1066  
1067                  $output->writeln('', OutputInterface::VERBOSITY_QUIET);
1068              }
1069          }
1070          while ($throwable = $throwable->getPrevious());
1071      }
1072  
1073      /**
1074       * Splits a string for a specified width for use in an output.
1075       *
1076       * @param   string   $string  The string to split.
1077       * @param   integer  $width   The maximum width of the output.
1078       *
1079       * @return  string[]
1080       *
1081       * @since   2.0.0
1082       */
1083  	private function splitStringByWidth(string $string, int $width): array
1084      {
1085          /*
1086           * The str_split function is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1087           * Additionally, array_slice() is not enough as some character has doubled width.
1088           * We need a function to split string not by character count but by string width
1089           */
1090          if (false === $encoding = mb_detect_encoding($string, null, true))
1091          {
1092              return str_split($string, $width);
1093          }
1094  
1095          $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1096          $lines      = [];
1097          $line       = '';
1098          $offset     = 0;
1099  
1100          while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset))
1101          {
1102              $offset += \strlen($m[0]);
1103  
1104              foreach (preg_split('//u', $m[0]) as $char)
1105              {
1106                  // Test if $char could be appended to current line
1107                  if (mb_strwidth($line . $char, 'utf8') <= $width)
1108                  {
1109                      $line .= $char;
1110  
1111                      continue;
1112                  }
1113  
1114                  // If not, push current line to array and make a new line
1115                  $lines[] = str_pad($line, $width);
1116                  $line    = $char;
1117              }
1118          }
1119  
1120          $lines[] = \count($lines) ? str_pad($line, $width) : $line;
1121          mb_convert_variables($encoding, 'utf8', $lines);
1122  
1123          return $lines;
1124      }
1125  
1126      /**
1127       * Run the given command.
1128       *
1129       * @param   AbstractCommand  $command  The command to run.
1130       * @param   InputInterface   $input    The input to inject into the command.
1131       * @param   OutputInterface  $output   The output to inject into the command.
1132       *
1133       * @return  integer
1134       *
1135       * @since   2.0.0
1136       * @throws  \Throwable
1137       */
1138  	protected function runCommand(AbstractCommand $command, InputInterface $input, OutputInterface $output): int
1139      {
1140          if ($command->getHelperSet() !== null)
1141          {
1142              foreach ($command->getHelperSet() as $helper)
1143              {
1144                  if ($helper instanceof InputAwareInterface)
1145                  {
1146                      $helper->setInput($input);
1147                  }
1148              }
1149          }
1150  
1151          // If the application doesn't have an event dispatcher, we can short circuit and just execute the command
1152          try
1153          {
1154              $this->getDispatcher();
1155          }
1156          catch (\UnexpectedValueException $exception)
1157          {
1158              return $command->execute($input, $output);
1159          }
1160  
1161          // Bind before dispatching the event so the listeners have access to input options/arguments
1162          try
1163          {
1164              $command->mergeApplicationDefinition();
1165              $input->bind($command->getDefinition());
1166          }
1167          catch (ExceptionInterface $e)
1168          {
1169              // Ignore invalid options/arguments for now
1170          }
1171  
1172          $event     = new BeforeCommandExecuteEvent($this, $command);
1173          $exception = null;
1174  
1175          try
1176          {
1177              $this->dispatchEvent(ConsoleEvents::BEFORE_COMMAND_EXECUTE, $event);
1178  
1179              if ($event->isCommandEnabled())
1180              {
1181                  $exitCode = $command->execute($input, $output);
1182              }
1183              else
1184              {
1185                  $exitCode = BeforeCommandExecuteEvent::RETURN_CODE_DISABLED;
1186              }
1187          }
1188          catch (\Throwable $exception)
1189          {
1190              $event = new CommandErrorEvent($exception, $this, $command);
1191  
1192              $this->dispatchEvent(ConsoleEvents::COMMAND_ERROR, $event);
1193  
1194              $exception = $event->getError();
1195              $exitCode  = $event->getExitCode();
1196  
1197              if ($exitCode === 0)
1198              {
1199                  $exception = null;
1200              }
1201          }
1202  
1203          $event = new TerminateEvent($exitCode, $this, $command);
1204  
1205          $this->dispatchEvent(ConsoleEvents::TERMINATE, $event);
1206  
1207          if ($exception !== null)
1208          {
1209              throw $exception;
1210          }
1211  
1212          return $event->getExitCode();
1213      }
1214  
1215      /**
1216       * Set whether the application should auto exit.
1217       *
1218       * @param   boolean  $autoExit  The auto exit state.
1219       *
1220       * @return  void
1221       *
1222       * @since   2.0.0
1223       */
1224  	public function setAutoExit(bool $autoExit): void
1225      {
1226          $this->autoExit = $autoExit;
1227      }
1228  
1229      /**
1230       * Set whether the application should catch Throwables.
1231       *
1232       * @param   boolean  $catchThrowables  The catch Throwables state.
1233       *
1234       * @return  void
1235       *
1236       * @since   2.0.0
1237       */
1238  	public function setCatchThrowables(bool $catchThrowables): void
1239      {
1240          $this->catchThrowables = $catchThrowables;
1241      }
1242  
1243      /**
1244       * Set the command loader.
1245       *
1246       * @param   Loader\LoaderInterface  $loader  The new command loader.
1247       *
1248       * @return  void
1249       *
1250       * @since   2.0.0
1251       */
1252  	public function setCommandLoader(Loader\LoaderInterface $loader): void
1253      {
1254          $this->commandLoader = $loader;
1255      }
1256  
1257      /**
1258       * Set the application's helper set.
1259       *
1260       * @param   HelperSet  $helperSet  The new HelperSet.
1261       *
1262       * @return  void
1263       *
1264       * @since   2.0.0
1265       */
1266  	public function setHelperSet(HelperSet $helperSet): void
1267      {
1268          $this->helperSet = $helperSet;
1269      }
1270  
1271      /**
1272       * Set the name of the application.
1273       *
1274       * @param   string  $name  The new application name.
1275       *
1276       * @return  void
1277       *
1278       * @since   2.0.0
1279       */
1280  	public function setName(string $name): void
1281      {
1282          $this->name = $name;
1283      }
1284  
1285      /**
1286       * Set the version of the application.
1287       *
1288       * @param   string  $version  The new application version.
1289       *
1290       * @return  void
1291       *
1292       * @since   2.0.0
1293       */
1294  	public function setVersion(string $version): void
1295      {
1296          $this->version = $version;
1297      }
1298  
1299      /**
1300       * Get the application's auto exit state.
1301       *
1302       * @return  boolean
1303       *
1304       * @since   2.0.0
1305       */
1306  	public function shouldAutoExit(): bool
1307      {
1308          return $this->autoExit;
1309      }
1310  
1311      /**
1312       * Get the application's catch Throwables state.
1313       *
1314       * @return  boolean
1315       *
1316       * @since   2.0.0
1317       */
1318  	public function shouldCatchThrowables(): bool
1319      {
1320          return $this->catchThrowables;
1321      }
1322  
1323      /**
1324       * Returns all namespaces of the command name.
1325       *
1326       * @param   string  $name  The full name of the command
1327       *
1328       * @return  string[]
1329       *
1330       * @since   2.0.0
1331       */
1332  	private function extractAllNamespaces(string $name): array
1333      {
1334          // -1 as third argument is needed to skip the command short name when exploding
1335          $parts      = explode(':', $name, -1);
1336          $namespaces = [];
1337  
1338          foreach ($parts as $part)
1339          {
1340              if (\count($namespaces))
1341              {
1342                  $namespaces[] = end($namespaces) . ':' . $part;
1343              }
1344              else
1345              {
1346                  $namespaces[] = $part;
1347              }
1348          }
1349  
1350          return $namespaces;
1351      }
1352  
1353      /**
1354       * Returns the namespace part of the command name.
1355       *
1356       * @param   string   $name   The command name to process
1357       * @param   integer  $limit  The maximum number of parts of the namespace
1358       *
1359       * @return  string
1360       *
1361       * @since   2.0.0
1362       */
1363  	private function extractNamespace(string $name, ?int $limit = null): string
1364      {
1365          $parts = explode(':', $name);
1366          array_pop($parts);
1367  
1368          return implode(':', $limit === null ? $parts : \array_slice($parts, 0, $limit));
1369      }
1370  
1371      /**
1372       * Internal function to initialise the command store, this allows the store to be lazy loaded only when needed.
1373       *
1374       * @return  void
1375       *
1376       * @since   2.0.0
1377       */
1378  	private function initCommands(): void
1379      {
1380          if ($this->initialised)
1381          {
1382              return;
1383          }
1384  
1385          $this->initialised = true;
1386  
1387          foreach ($this->getDefaultCommands() as $command)
1388          {
1389              $this->addCommand($command);
1390          }
1391      }
1392  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer