Skip to content

Commit 9ae2181

Browse files
authored
Merge pull request #12165 from HypeMC/debug-events-commands
Add commands for inspecting configured listeners
2 parents 609e616 + 3e25efd commit 9ae2181

File tree

9 files changed

+641
-0
lines changed

9 files changed

+641
-0
lines changed

docs/en/reference/tools.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ The following Commands are currently available:
9696
- ``orm:schema-tool:update`` Processes the schema and either
9797
update the database schema of EntityManager Storage Connection or
9898
generate the SQL output.
99+
- ``orm:debug:event-manager`` Lists event listeners for an entity
100+
manager, optionally filtered by event name.
101+
- ``orm:debug:entity-listeners`` Lists entity listeners for a given
102+
entity, optionally filtered by event name.
99103

100104
The following alias is defined:
101105

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ORM\Tools\Console\Command\Debug;
6+
7+
use Doctrine\ORM\EntityManagerInterface;
8+
use Doctrine\Persistence\ManagerRegistry;
9+
use Symfony\Component\Console\Command\Command;
10+
11+
use function assert;
12+
13+
/** @internal */
14+
abstract class AbstractCommand extends Command
15+
{
16+
public function __construct(private readonly ManagerRegistry $managerRegistry)
17+
{
18+
parent::__construct();
19+
}
20+
21+
final protected function getEntityManager(string $name): EntityManagerInterface
22+
{
23+
$manager = $this->getManagerRegistry()->getManager($name);
24+
25+
assert($manager instanceof EntityManagerInterface);
26+
27+
return $manager;
28+
}
29+
30+
final protected function getManagerRegistry(): ManagerRegistry
31+
{
32+
return $this->managerRegistry;
33+
}
34+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ORM\Tools\Console\Command\Debug;
6+
7+
use Doctrine\ORM\Mapping\ClassMetadata;
8+
use Symfony\Component\Console\Completion\CompletionInput;
9+
use Symfony\Component\Console\Completion\CompletionSuggestions;
10+
use Symfony\Component\Console\Helper\TableSeparator;
11+
use Symfony\Component\Console\Input\InputArgument;
12+
use Symfony\Component\Console\Input\InputInterface;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
16+
use function array_keys;
17+
use function array_merge;
18+
use function array_unique;
19+
use function array_values;
20+
use function assert;
21+
use function class_exists;
22+
use function ksort;
23+
use function ltrim;
24+
use function sort;
25+
use function sprintf;
26+
27+
final class DebugEntityListenersDoctrineCommand extends AbstractCommand
28+
{
29+
protected function configure(): void
30+
{
31+
$this
32+
->setName('orm:debug:entity-listeners')
33+
->setDescription('Lists entity listeners for a given entity')
34+
->addArgument('entity', InputArgument::OPTIONAL, 'The fully-qualified entity class name')
35+
->addArgument('event', InputArgument::OPTIONAL, 'The event name to filter by (e.g. postPersist)')
36+
->setHelp(<<<'EOT'
37+
The <info>%command.name%</info> command lists all entity listeners for a given entity:
38+
39+
<info>php %command.full_name% 'App\Entity\User'</info>
40+
41+
To show only listeners for a specific event, pass the event name:
42+
43+
<info>php %command.full_name% 'App\Entity\User' postPersist</info>
44+
EOT);
45+
}
46+
47+
protected function execute(InputInterface $input, OutputInterface $output): int
48+
{
49+
$io = new SymfonyStyle($input, $output);
50+
51+
/** @var class-string|null $entityName */
52+
$entityName = $input->getArgument('entity');
53+
54+
if ($entityName === null) {
55+
$choices = $this->listAllEntities();
56+
57+
if ($choices === []) {
58+
$io->error('No entities are configured.');
59+
60+
return self::FAILURE;
61+
}
62+
63+
/** @var class-string $entityName */
64+
$entityName = $io->choice('Which entity do you want to list listeners for?', $choices);
65+
}
66+
67+
$entityName = ltrim($entityName, '\\');
68+
$entityManager = $this->getManagerRegistry()->getManagerForClass($entityName);
69+
70+
if ($entityManager === null) {
71+
$io->error(sprintf('No entity manager found for class "%s".', $entityName));
72+
73+
return self::FAILURE;
74+
}
75+
76+
$classMetadata = $entityManager->getClassMetadata($entityName);
77+
assert($classMetadata instanceof ClassMetadata);
78+
79+
$eventName = $input->getArgument('event');
80+
81+
if ($eventName === null) {
82+
$allListeners = $classMetadata->entityListeners;
83+
if (! $allListeners) {
84+
$io->info(sprintf('No listeners are configured for the "%s" entity.', $entityName));
85+
86+
return self::SUCCESS;
87+
}
88+
89+
ksort($allListeners);
90+
} else {
91+
if (! isset($classMetadata->entityListeners[$eventName])) {
92+
$io->info(sprintf('No listeners are configured for the "%s" event.', $eventName));
93+
94+
return self::SUCCESS;
95+
}
96+
97+
$allListeners = [$eventName => $classMetadata->entityListeners[$eventName]];
98+
}
99+
100+
$io->title(sprintf('Entity listeners for <info>%s</info>', $entityName));
101+
102+
$rows = [];
103+
foreach ($allListeners as $event => $listeners) {
104+
if ($rows) {
105+
$rows[] = new TableSeparator();
106+
}
107+
108+
foreach ($listeners as $order => $listener) {
109+
$rows[] = [$order === 0 ? $event : '', sprintf('#%d', ++$order), sprintf('%s::%s()', $listener['class'], $listener['method'])];
110+
}
111+
}
112+
113+
$io->table(['Event', 'Order', 'Listener'], $rows);
114+
115+
return self::SUCCESS;
116+
}
117+
118+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
119+
{
120+
if ($input->mustSuggestArgumentValuesFor('entity')) {
121+
$suggestions->suggestValues($this->listAllEntities());
122+
123+
return;
124+
}
125+
126+
if ($input->mustSuggestArgumentValuesFor('event')) {
127+
$entityName = ltrim($input->getArgument('entity'), '\\');
128+
129+
if (! class_exists($entityName)) {
130+
return;
131+
}
132+
133+
$entityManager = $this->getManagerRegistry()->getManagerForClass($entityName);
134+
135+
if ($entityManager === null) {
136+
return;
137+
}
138+
139+
$classMetadata = $entityManager->getClassMetadata($entityName);
140+
assert($classMetadata instanceof ClassMetadata);
141+
142+
$suggestions->suggestValues(array_keys($classMetadata->entityListeners));
143+
144+
return;
145+
}
146+
}
147+
148+
/** @return list<class-string> */
149+
private function listAllEntities(): array
150+
{
151+
$entities = [];
152+
foreach (array_keys($this->getManagerRegistry()->getManagerNames()) as $managerName) {
153+
$entities[] = $this->getEntityManager($managerName)->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
154+
}
155+
156+
$entities = array_values(array_unique(array_merge(...$entities)));
157+
158+
sort($entities);
159+
160+
return $entities;
161+
}
162+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ORM\Tools\Console\Command\Debug;
6+
7+
use Symfony\Component\Console\Completion\CompletionInput;
8+
use Symfony\Component\Console\Completion\CompletionSuggestions;
9+
use Symfony\Component\Console\Helper\TableSeparator;
10+
use Symfony\Component\Console\Input\InputArgument;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
16+
use function array_keys;
17+
use function array_values;
18+
use function ksort;
19+
use function method_exists;
20+
use function sprintf;
21+
22+
final class DebugEventManagerDoctrineCommand extends AbstractCommand
23+
{
24+
protected function configure(): void
25+
{
26+
$this
27+
->setName('orm:debug:event-manager')
28+
->setDescription('Lists event listeners for an entity manager')
29+
->addArgument('event', InputArgument::OPTIONAL, 'The event name to filter by (e.g. postPersist)')
30+
->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command')
31+
->setHelp(<<<'EOT'
32+
The <info>%command.name%</info> command lists all event listeners for the default entity manager:
33+
34+
<info>php %command.full_name%</info>
35+
36+
You can also specify an entity manager:
37+
38+
<info>php %command.full_name% --em=default</info>
39+
40+
To show only listeners for a specific event, pass the event name as an argument:
41+
42+
<info>php %command.full_name% postPersist</info>
43+
EOT);
44+
}
45+
46+
protected function execute(InputInterface $input, OutputInterface $output): int
47+
{
48+
$io = new SymfonyStyle($input, $output);
49+
50+
$entityManagerName = $input->getOption('em') ?: $this->getManagerRegistry()->getDefaultManagerName();
51+
$eventManager = $this->getEntityManager($entityManagerName)->getEventManager();
52+
53+
$eventName = $input->getArgument('event');
54+
55+
if ($eventName === null) {
56+
$allListeners = $eventManager->getAllListeners();
57+
if (! $allListeners) {
58+
$io->info(sprintf('No listeners are configured for the "%s" entity manager.', $entityManagerName));
59+
60+
return self::SUCCESS;
61+
}
62+
63+
ksort($allListeners);
64+
} else {
65+
$listeners = $eventManager->hasListeners($eventName) ? $eventManager->getListeners($eventName) : [];
66+
if (! $listeners) {
67+
$io->info(sprintf('No listeners are configured for the "%s" event.', $eventName));
68+
69+
return self::SUCCESS;
70+
}
71+
72+
$allListeners = [$eventName => $listeners];
73+
}
74+
75+
$io->title(sprintf('Event listeners for <info>%s</info> entity manager', $entityManagerName));
76+
77+
$rows = [];
78+
foreach ($allListeners as $event => $listeners) {
79+
if ($rows) {
80+
$rows[] = new TableSeparator();
81+
}
82+
83+
foreach (array_values($listeners) as $order => $listener) {
84+
$method = method_exists($listener, '__invoke') ? '__invoke' : $event;
85+
$rows[] = [$order === 0 ? $event : '', sprintf('#%d', ++$order), sprintf('%s::%s()', $listener::class, $method)];
86+
}
87+
}
88+
89+
$io->table(['Event', 'Order', 'Listener'], $rows);
90+
91+
return self::SUCCESS;
92+
}
93+
94+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
95+
{
96+
if ($input->mustSuggestArgumentValuesFor('event')) {
97+
$entityManagerName = $input->getOption('em') ?: $this->getManagerRegistry()->getDefaultManagerName();
98+
$eventManager = $this->getEntityManager($entityManagerName)->getEventManager();
99+
100+
$suggestions->suggestValues(array_keys($eventManager->getAllListeners()));
101+
102+
return;
103+
}
104+
105+
if ($input->mustSuggestOptionValuesFor('em')) {
106+
$suggestions->suggestValues(array_keys($this->getManagerRegistry()->getManagerNames()));
107+
108+
return;
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)