<?php
/**
* Part of the Joomla Framework Event Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Event\Command;
use Joomla\Console\Command\AbstractCommand;
use Joomla\Event\DispatcherAwareInterface;
use Joomla\Event\DispatcherAwareTrait;
use Joomla\Event\DispatcherInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Command listing information about the application's event dispatcher.
*
* @since 2.0.0
*/
class DebugEventDispatcherCommand extends AbstractCommand implements DispatcherAwareInterface
{
use DispatcherAwareTrait;
/**
* The default command name
*
* @var string
* @since 2.0.0
*/
protected static $defaultName = 'debug:event-dispatcher';
/**
* Instantiate the command.
*
* @param DispatcherInterface $dispatcher The application event dispatcher.
*
* @since 2.0.0
*/
public function __construct(DispatcherInterface $dispatcher)
{
$this->setDispatcher($dispatcher);
parent::__construct();
}
/**
* Configure the command.
*
* @return void
*
* @since 2.0.0
*/
protected function configure(): void
{
$this->setDescription("Displays information about the application's event dispatcher");
$this->addArgument('event', InputArgument::OPTIONAL, 'Show the listeners for a specific event');
$this->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all of the registered event handlers in an application's event dispatcher:
<info>php %command.full_name%</info>
To get specific listeners for an event, specify its name:
<info>php %command.full_name% application.before_execute</info>
EOF
);
}
/**
* Internal function to execute the command.
*
* @param InputInterface $input The input to inject into the command.
* @param OutputInterface $output The output to inject into the command.
*
* @return integer The command exit code
*
* @since 2.0.0
*/
protected function doExecute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
if ($event = $input->getArgument('event')) {
$listeners = $this->dispatcher->getListeners($event);
if (empty($listeners)) {
$io->warning(sprintf('The event "%s" does not have any registered listeners.', $event));
return 0;
}
$io->title(sprintf('%s Registered Listeners for "%s" Event', $this->getApplication()->getName(), $event));
$this->renderEventListenerTable($listeners, $io);
return 0;
}
$listeners = $this->dispatcher->getListeners();
if (empty($listeners)) {
$io->comment('There are no listeners registered to the event dispatcher.');
return 0;
}
$io->title(sprintf('%s Registered Listeners Grouped By Event', $this->getApplication()->getName()));
ksort($listeners);
foreach ($listeners as $subscribedEvent => $eventListeners) {
$io->section(sprintf('"%s" event', $subscribedEvent));
$this->renderEventListenerTable($eventListeners, $io);
}
return 0;
}
/**
* Formats a callable resource to be displayed in the console output
*
* @param callable $callable A callable resource to format
*
* @return string
*
* @since 2.0.0
* @throws \ReflectionException
* @note This method is based on \Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor::formatCallable()
*/
private function formatCallable($callable): string
{
if (\is_array($callable)) {
if (\is_object($callable[0])) {
return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]);
}
return sprintf('%s::%s()', $callable[0], $callable[1]);
}
if (\is_string($callable)) {
return sprintf('%s()', $callable);
}
if ($callable instanceof \Closure) {
$r = new \ReflectionFunction($callable);
if (strpos($r->name, '{closure}') !== false) {
return 'Closure()';
}
if (null !== $class = $r->getClosureScopeClass()) {
return sprintf('%s::%s()', $class->name, $r->name);
}
return $r->name . '()';
}
if (method_exists($callable, '__invoke')) {
return sprintf('%s::__invoke()', \get_class($callable));
}
throw new \InvalidArgumentException('Callable is not describable.');
}
/**
* Renders the table of listeners for an event
*
* @param array $eventListeners The listeners for an event
* @param SymfonyStyle $io The I/O helper
*
* @return void
*
* @since 2.0.0
*/
private function renderEventListenerTable(array $eventListeners, SymfonyStyle $io): void
{
$tableHeaders = ['Order', 'Callable'];
$tableRows = [];
foreach ($eventListeners as $order => $listener) {
$tableRows[] = [
sprintf('#%d', $order + 1),
$this->formatCallable($listener),
];
}
$io->table($tableHeaders, $tableRows);
}
}