Skip to content

Commit

Permalink
[FrameworkBundle] Add Event Dispatcher debug command
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthieu Auger authored and matthieuauger committed Aug 18, 2014
1 parent 8725243 commit ce53c8a
Show file tree
Hide file tree
Showing 44 changed files with 778 additions and 0 deletions.
@@ -0,0 +1,84 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Command;

use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
* A console command for retrieving information about event dispatcher
*
* @author Matthieu Auger <mail@matthieuauger.com>
*/
class EventDispatcherDebugCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('debug:event-dispatcher')
->setDefinition(array(
new InputArgument('event', InputArgument::OPTIONAL, 'An event name (foo)'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output description in other formats', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
))
->setDescription('Displays configured listeners for an application')
->setHelp(<<<EOF
The <info>%command.name%</info> command displays all configured listeners:
<info>php %command.full_name%</info>
To get specific listeners for an event, specify its name:
<info>php %command.full_name% kernel.request</info>
EOF
)
;
}

/**
* {@inheritdoc}
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($event = $input->getArgument('event')) {
$options = array('event' => $event);
} else {
$options = array();
}

$dispatcher = $this->getEventDispatcher();

$helper = new DescriptorHelper();
$options['format'] = $input->getOption('format');
$options['raw_text'] = $input->getOption('raw');
$helper->describe($output, $dispatcher, $options);
}

/**
* Loads the Event Dispatcher from the container
*
* @return EventDispatcherInterface
*/
protected function getEventDispatcher()
{
return $this->getContainer()->get('event_dispatcher');
}
}
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

Expand Down Expand Up @@ -66,6 +67,12 @@ public function describe(OutputInterface $output, $object, array $options = arra
case $object instanceof Alias:
$this->describeContainerAlias($object, $options);
break;
case $object instanceof EventDispatcherInterface:
$this->describeEventDispatcherListeners($object, $options);
break;
case is_callable($object):
$this->describeCallable($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
}
Expand Down Expand Up @@ -176,6 +183,25 @@ abstract protected function describeContainerAlias(Alias $alias, array $options
*/
abstract protected function describeContainerParameter($parameter, array $options = array());

/**
* Describes event dispatcher listeners.
*
* Common options are:
* * name: name of listened event
*
* @param EventDispatcherInterface $eventDispatcher
* @param array $options
*/
abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array());

/**
* Describes a callable.
*
* @param callable $callable
* @param array $options
*/
abstract protected function describeCallable($callable, array $options = array());

/**
* Formats a value as string.
*
Expand Down
Expand Up @@ -19,6 +19,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

Expand Down Expand Up @@ -134,6 +135,22 @@ protected function describeContainerAlias(Alias $alias, array $options = array()
$this->writeData($this->getContainerAliasData($alias), $options);
}

/**
* {@inheritdoc}
*/
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array())
{
$this->writeData($this->getEventDispatcherListenersData($eventDispatcher, array_key_exists('event', $options) ? $options['event'] : null), $options);
}

/**
* {@inheritdoc}
*/
protected function describeCallable($callable, array $options = array())
{
$this->writeData($this->getCallableData($callable, $options), $options);
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -222,4 +239,96 @@ private function getContainerAliasData(Alias $alias)
'public' => $alias->isPublic(),
);
}

/**
* @param EventDispatcherInterface $eventDispatcher
* @param string|null $event
*
* @return array
*/
private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, $event = null)
{
$data = array();

$registeredListeners = $eventDispatcher->getListeners($event);
if (null !== $event) {
foreach ($registeredListeners as $listener) {
$data[] = $this->getCallableData($listener);
}
} else {
ksort($registeredListeners);

foreach ($registeredListeners as $eventListened => $eventListeners) {
foreach ($eventListeners as $eventListener) {
$data[$eventListened][] = $this->getCallableData($eventListener);
}
}
}

return $data;
}

/**
* @param callable $callable
* @param array $options
*
* @return array
*/
private function getCallableData($callable, array $options = array())
{
$data = array();

if (is_array($callable)) {
$data['type'] = 'function';

if (is_object($callable[0])) {
$data['name'] = $callable[1];
$data['class'] = get_class($callable[0]);
} else {
if (0 !== strpos($callable[1], 'parent::')) {
$data['name'] = $callable[1];
$data['class'] = $callable[0];
$data['static'] = true;
} else {
$data['name'] = substr($callable[1], 8);
$data['class'] = $callable[0];
$data['static'] = true;
$data['parent'] = true;
}
}

return $data;
}

if (is_string($callable)) {
$data['type'] = 'function';

if (false === strpos($callable, '::')) {
$data['name'] = $callable;
} else {
$callableParts = explode('::', $callable);

$data['name'] = $callableParts[1];
$data['class'] = $callableParts[0];
$data['static'] = true;
}

return $data;
}

if ($callable instanceof \Closure) {
$data['type'] = 'closure';

return $data;
}

if (method_exists($callable, '__invoke')) {
$data['type'] = 'object';
$data['name'] = get_class($callable);

return $data;
}

throw new \InvalidArgumentException('Callable is not describable.');
}
}
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

Expand Down Expand Up @@ -215,6 +216,106 @@ protected function describeContainerParameter($parameter, array $options = array
$this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', strlen($options['parameter'])), $this->formatParameter($parameter)): $parameter);
}

/**
* {@inheritdoc}
*/
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array())
{
$event = array_key_exists('event', $options) ? $options['event'] : null;

$title = 'Registered listeners';
if (null !== $event) {
$title .= sprintf(' for event `%s` ordered by descending priority', $event);
}

$this->write(sprintf('# %s', $title)."\n");

$registeredListeners = $eventDispatcher->getListeners($event);
if (null !== $event) {
foreach ($registeredListeners as $order => $listener) {
$this->write("\n".sprintf('## Listener %d', $order + 1)."\n");
$this->describeCallable($listener);
}
} else {
ksort($registeredListeners);

foreach ($registeredListeners as $eventListened => $eventListeners) {
$this->write("\n".sprintf('## %s', $eventListened)."\n");

foreach ($eventListeners as $order => $eventListener) {
$this->write("\n".sprintf('### Listener %d', $order + 1)."\n");
$this->describeCallable($eventListener);
}
}
}
}

/**
* {@inheritdoc}
*/
protected function describeCallable($callable, array $options = array())
{
$string = '';

if (is_array($callable)) {
$string .= "\n- Type: `function`";

if (is_object($callable[0])) {
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
$string .= "\n".sprintf('- Class: `%s`', get_class($callable[0]));
} else {
if (0 !== strpos($callable[1], 'parent::')) {
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
$string .= "\n".sprintf('- Class: `%s`', $callable[0]);
$string .= "\n- Static: yes";
} else {
$string .= "\n".sprintf('- Name: `%s`', substr($callable[1], 8));
$string .= "\n".sprintf('- Class: `%s`', $callable[0]);
$string .= "\n- Static: yes";
$string .= "\n- Parent: yes";
}
}

return $this->write($string."\n");
}

if (is_string($callable)) {
$string .= "\n- Type: `function`";

if (false === strpos($callable, '::')) {
$string .= "\n".sprintf('- Name: `%s`', $callable);
} else {
$callableParts = explode('::', $callable);

$string .= "\n".sprintf('- Name: `%s`', $callableParts[1]);
$string .= "\n".sprintf('- Class: `%s`', $callableParts[0]);
$string .= "\n- Static: yes";
}

return $this->write($string."\n");
}

if ($callable instanceof \Closure) {
$string .= "\n- Type: `closure`";

return $this->write($string."\n");
}

if (method_exists($callable, '__invoke')) {
$string .= "\n- Type: `object`";
$string .= "\n".sprintf('- Name: `%s`', get_class($callable));

return $this->write($string."\n");
}

throw new \InvalidArgumentException('Callable is not describable.');
}

/**
* @param array $array
*
* @return string
*/
private function formatRouterConfig(array $array)
{
if (!count($array)) {
Expand Down

0 comments on commit ce53c8a

Please sign in to comment.