Skip to content

Commit 2d7c2ce

Browse files
committed
feature #10388 [FrameworkBundle] [Command] Event Dispatcher Debug - Display registered listeners (matthieuauger)
This PR was merged into the 2.6-dev branch. Discussion ---------- [FrameworkBundle] [Command] Event Dispatcher Debug - Display registered listeners | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT ------------------------------------------ [Update] The PR has been updated in order to comply with @stof comments. Current status : - [x] New event dispatcher Descriptor - [x] Manage all callables - [x] Unit tests - [x] Text description - [x] XML description - [x] Json description - [x] Markdown description ----------------------------------------- Hi. In some big applications with lots of events, it's often hard to debug which classes listen to which events, and what is the order of theses listeners. This PR allows to run - *event-dispatcher:debug* which displays all configured listeners + the events they listen to ![capture d cran de 2014-03-07 20 13 56](https://f.cloud.github.com/assets/1172099/2361104/40a86a62-a62d-11e3-9ccd-360a8d75b2a4.png) - *event-dispatcher:debug* **event** which displays configured listeners for this specific event (order by priority desc) ![capture d cran de 2014-03-07 20 14 31](https://f.cloud.github.com/assets/1172099/2361100/31e0d12c-a62d-11e3-963b-87623d05642c.png) The output is similar to *container:debug* command and is available in all supported formats (txt, xml, json and markdown). I found another PR with same goal (#8234), but the approach looks too complicated to me plus I think we should fetch the listeners directly with the event_dispatcher. Commits ------- ce53c8a [FrameworkBundle] Add Event Dispatcher debug command
2 parents 72a5bfe + 731e863 commit 2d7c2ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+778
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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\Bundle\FrameworkBundle\Command;
13+
14+
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
15+
use Symfony\Component\Console\Input\InputArgument;
16+
use Symfony\Component\Console\Input\InputOption;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
20+
21+
/**
22+
* A console command for retrieving information about event dispatcher
23+
*
24+
* @author Matthieu Auger <mail@matthieuauger.com>
25+
*/
26+
class EventDispatcherDebugCommand extends ContainerAwareCommand
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
protected function configure()
32+
{
33+
$this
34+
->setName('debug:event-dispatcher')
35+
->setDefinition(array(
36+
new InputArgument('event', InputArgument::OPTIONAL, 'An event name (foo)'),
37+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output description in other formats', 'txt'),
38+
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
39+
))
40+
->setDescription('Displays configured listeners for an application')
41+
->setHelp(<<<EOF
42+
The <info>%command.name%</info> command displays all configured listeners:
43+
44+
<info>php %command.full_name%</info>
45+
46+
To get specific listeners for an event, specify its name:
47+
48+
<info>php %command.full_name% kernel.request</info>
49+
EOF
50+
)
51+
;
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*
57+
* @throws \LogicException
58+
*/
59+
protected function execute(InputInterface $input, OutputInterface $output)
60+
{
61+
if ($event = $input->getArgument('event')) {
62+
$options = array('event' => $event);
63+
} else {
64+
$options = array();
65+
}
66+
67+
$dispatcher = $this->getEventDispatcher();
68+
69+
$helper = new DescriptorHelper();
70+
$options['format'] = $input->getOption('format');
71+
$options['raw_text'] = $input->getOption('raw');
72+
$helper->describe($output, $dispatcher, $options);
73+
}
74+
75+
/**
76+
* Loads the Event Dispatcher from the container
77+
*
78+
* @return EventDispatcherInterface
79+
*/
80+
protected function getEventDispatcher()
81+
{
82+
return $this->getContainer()->get('event_dispatcher');
83+
}
84+
}

Console/Descriptor/Descriptor.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Definition;
2020
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
21+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2122
use Symfony\Component\Routing\Route;
2223
use Symfony\Component\Routing\RouteCollection;
2324

@@ -66,6 +67,12 @@ public function describe(OutputInterface $output, $object, array $options = arra
6667
case $object instanceof Alias:
6768
$this->describeContainerAlias($object, $options);
6869
break;
70+
case $object instanceof EventDispatcherInterface:
71+
$this->describeEventDispatcherListeners($object, $options);
72+
break;
73+
case is_callable($object):
74+
$this->describeCallable($object, $options);
75+
break;
6976
default:
7077
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
7178
}
@@ -176,6 +183,25 @@ abstract protected function describeContainerAlias(Alias $alias, array $options
176183
*/
177184
abstract protected function describeContainerParameter($parameter, array $options = array());
178185

186+
/**
187+
* Describes event dispatcher listeners.
188+
*
189+
* Common options are:
190+
* * name: name of listened event
191+
*
192+
* @param EventDispatcherInterface $eventDispatcher
193+
* @param array $options
194+
*/
195+
abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array());
196+
197+
/**
198+
* Describes a callable.
199+
*
200+
* @param callable $callable
201+
* @param array $options
202+
*/
203+
abstract protected function describeCallable($callable, array $options = array());
204+
179205
/**
180206
* Formats a value as string.
181207
*

Console/Descriptor/JsonDescriptor.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\DependencyInjection\ContainerBuilder;
2020
use Symfony\Component\DependencyInjection\Definition;
2121
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
22+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2223
use Symfony\Component\Routing\Route;
2324
use Symfony\Component\Routing\RouteCollection;
2425

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

138+
/**
139+
* {@inheritdoc}
140+
*/
141+
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array())
142+
{
143+
$this->writeData($this->getEventDispatcherListenersData($eventDispatcher, array_key_exists('event', $options) ? $options['event'] : null), $options);
144+
}
145+
146+
/**
147+
* {@inheritdoc}
148+
*/
149+
protected function describeCallable($callable, array $options = array())
150+
{
151+
$this->writeData($this->getCallableData($callable, $options), $options);
152+
}
153+
137154
/**
138155
* {@inheritdoc}
139156
*/
@@ -222,4 +239,96 @@ private function getContainerAliasData(Alias $alias)
222239
'public' => $alias->isPublic(),
223240
);
224241
}
242+
243+
/**
244+
* @param EventDispatcherInterface $eventDispatcher
245+
* @param string|null $event
246+
*
247+
* @return array
248+
*/
249+
private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, $event = null)
250+
{
251+
$data = array();
252+
253+
$registeredListeners = $eventDispatcher->getListeners($event);
254+
if (null !== $event) {
255+
foreach ($registeredListeners as $listener) {
256+
$data[] = $this->getCallableData($listener);
257+
}
258+
} else {
259+
ksort($registeredListeners);
260+
261+
foreach ($registeredListeners as $eventListened => $eventListeners) {
262+
foreach ($eventListeners as $eventListener) {
263+
$data[$eventListened][] = $this->getCallableData($eventListener);
264+
}
265+
}
266+
}
267+
268+
return $data;
269+
}
270+
271+
/**
272+
* @param callable $callable
273+
* @param array $options
274+
*
275+
* @return array
276+
*/
277+
private function getCallableData($callable, array $options = array())
278+
{
279+
$data = array();
280+
281+
if (is_array($callable)) {
282+
$data['type'] = 'function';
283+
284+
if (is_object($callable[0])) {
285+
$data['name'] = $callable[1];
286+
$data['class'] = get_class($callable[0]);
287+
} else {
288+
if (0 !== strpos($callable[1], 'parent::')) {
289+
$data['name'] = $callable[1];
290+
$data['class'] = $callable[0];
291+
$data['static'] = true;
292+
} else {
293+
$data['name'] = substr($callable[1], 8);
294+
$data['class'] = $callable[0];
295+
$data['static'] = true;
296+
$data['parent'] = true;
297+
}
298+
}
299+
300+
return $data;
301+
}
302+
303+
if (is_string($callable)) {
304+
$data['type'] = 'function';
305+
306+
if (false === strpos($callable, '::')) {
307+
$data['name'] = $callable;
308+
} else {
309+
$callableParts = explode('::', $callable);
310+
311+
$data['name'] = $callableParts[1];
312+
$data['class'] = $callableParts[0];
313+
$data['static'] = true;
314+
}
315+
316+
return $data;
317+
}
318+
319+
if ($callable instanceof \Closure) {
320+
$data['type'] = 'closure';
321+
322+
return $data;
323+
}
324+
325+
if (method_exists($callable, '__invoke')) {
326+
$data['type'] = 'object';
327+
$data['name'] = get_class($callable);
328+
329+
return $data;
330+
}
331+
332+
throw new \InvalidArgumentException('Callable is not describable.');
333+
}
225334
}

Console/Descriptor/MarkdownDescriptor.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Definition;
1717
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
18+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1819
use Symfony\Component\Routing\Route;
1920
use Symfony\Component\Routing\RouteCollection;
2021

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

219+
/**
220+
* {@inheritdoc}
221+
*/
222+
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array())
223+
{
224+
$event = array_key_exists('event', $options) ? $options['event'] : null;
225+
226+
$title = 'Registered listeners';
227+
if (null !== $event) {
228+
$title .= sprintf(' for event `%s` ordered by descending priority', $event);
229+
}
230+
231+
$this->write(sprintf('# %s', $title)."\n");
232+
233+
$registeredListeners = $eventDispatcher->getListeners($event);
234+
if (null !== $event) {
235+
foreach ($registeredListeners as $order => $listener) {
236+
$this->write("\n".sprintf('## Listener %d', $order + 1)."\n");
237+
$this->describeCallable($listener);
238+
}
239+
} else {
240+
ksort($registeredListeners);
241+
242+
foreach ($registeredListeners as $eventListened => $eventListeners) {
243+
$this->write("\n".sprintf('## %s', $eventListened)."\n");
244+
245+
foreach ($eventListeners as $order => $eventListener) {
246+
$this->write("\n".sprintf('### Listener %d', $order + 1)."\n");
247+
$this->describeCallable($eventListener);
248+
}
249+
}
250+
}
251+
}
252+
253+
/**
254+
* {@inheritdoc}
255+
*/
256+
protected function describeCallable($callable, array $options = array())
257+
{
258+
$string = '';
259+
260+
if (is_array($callable)) {
261+
$string .= "\n- Type: `function`";
262+
263+
if (is_object($callable[0])) {
264+
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
265+
$string .= "\n".sprintf('- Class: `%s`', get_class($callable[0]));
266+
} else {
267+
if (0 !== strpos($callable[1], 'parent::')) {
268+
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
269+
$string .= "\n".sprintf('- Class: `%s`', $callable[0]);
270+
$string .= "\n- Static: yes";
271+
} else {
272+
$string .= "\n".sprintf('- Name: `%s`', substr($callable[1], 8));
273+
$string .= "\n".sprintf('- Class: `%s`', $callable[0]);
274+
$string .= "\n- Static: yes";
275+
$string .= "\n- Parent: yes";
276+
}
277+
}
278+
279+
return $this->write($string."\n");
280+
}
281+
282+
if (is_string($callable)) {
283+
$string .= "\n- Type: `function`";
284+
285+
if (false === strpos($callable, '::')) {
286+
$string .= "\n".sprintf('- Name: `%s`', $callable);
287+
} else {
288+
$callableParts = explode('::', $callable);
289+
290+
$string .= "\n".sprintf('- Name: `%s`', $callableParts[1]);
291+
$string .= "\n".sprintf('- Class: `%s`', $callableParts[0]);
292+
$string .= "\n- Static: yes";
293+
}
294+
295+
return $this->write($string."\n");
296+
}
297+
298+
if ($callable instanceof \Closure) {
299+
$string .= "\n- Type: `closure`";
300+
301+
return $this->write($string."\n");
302+
}
303+
304+
if (method_exists($callable, '__invoke')) {
305+
$string .= "\n- Type: `object`";
306+
$string .= "\n".sprintf('- Name: `%s`', get_class($callable));
307+
308+
return $this->write($string."\n");
309+
}
310+
311+
throw new \InvalidArgumentException('Callable is not describable.');
312+
}
313+
314+
/**
315+
* @param array $array
316+
*
317+
* @return string
318+
*/
218319
private function formatRouterConfig(array $array)
219320
{
220321
if (!count($array)) {

0 commit comments

Comments
 (0)