Skip to content

Commit 731e863

Browse files
Matthieu Augermatthieuauger
Matthieu Auger
authored andcommitted
[FrameworkBundle] Add Event Dispatcher debug command
1 parent 4a3105c commit 731e863

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)