Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/en/reference/tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ The following Commands are currently available:
- ``orm:schema-tool:update`` Processes the schema and either
update the database schema of EntityManager Storage Connection or
generate the SQL output.
- ``orm:debug:event-manager`` Lists event listeners for an entity
manager, optionally filtered by event name.
- ``orm:debug:entity-listeners`` Lists entity listeners for a given
entity, optionally filtered by event name.

The following alias is defined:

Expand Down
34 changes: 34 additions & 0 deletions src/Tools/Console/Command/Debug/AbstractCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Tools\Console\Command\Debug;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Console\Command\Command;

use function assert;

/** @internal */
abstract class AbstractCommand extends Command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please flag this class as @internal. I don't want it to be used downstream.

{
public function __construct(private readonly ManagerRegistry $managerRegistry)
{
parent::__construct();
}

final protected function getEntityManager(string $name): EntityManagerInterface
{
$manager = $this->getManagerRegistry()->getManager($name);

assert($manager instanceof EntityManagerInterface);

return $manager;
}

final protected function getManagerRegistry(): ManagerRegistry
{
return $this->managerRegistry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Tools\Console\Command\Debug;

use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function array_keys;
use function array_merge;
use function array_unique;
use function array_values;
use function assert;
use function class_exists;
use function ksort;
use function ltrim;
use function sort;
use function sprintf;

final class DebugEntityListenersDoctrineCommand extends AbstractCommand
{
protected function configure(): void
{
$this
->setName('orm:debug:entity-listeners')
->setDescription('Lists entity listeners for a given entity')
->addArgument('entity', InputArgument::OPTIONAL, 'The fully-qualified entity class name')
->addArgument('event', InputArgument::OPTIONAL, 'The event name to filter by (e.g. postPersist)')
->setHelp(<<<'EOT'
The <info>%command.name%</info> command lists all entity listeners for a given entity:

<info>php %command.full_name% 'App\Entity\User'</info>

To show only listeners for a specific event, pass the event name:

<info>php %command.full_name% 'App\Entity\User' postPersist</info>
EOT);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

/** @var class-string|null $entityName */
$entityName = $input->getArgument('entity');

if ($entityName === null) {
$choices = $this->listAllEntities();

if ($choices === []) {
$io->error('No entities are configured.');

return self::FAILURE;
}

/** @var class-string $entityName */
$entityName = $io->choice('Which entity do you want to list listeners for?', $choices);
}

$entityName = ltrim($entityName, '\\');
$entityManager = $this->getManagerRegistry()->getManagerForClass($entityName);

if ($entityManager === null) {
$io->error(sprintf('No entity manager found for class "%s".', $entityName));

return self::FAILURE;
}

$classMetadata = $entityManager->getClassMetadata($entityName);
assert($classMetadata instanceof ClassMetadata);

$eventName = $input->getArgument('event');

if ($eventName === null) {
$allListeners = $classMetadata->entityListeners;
if (! $allListeners) {
$io->info(sprintf('No listeners are configured for the "%s" entity.', $entityName));

return self::SUCCESS;
}

ksort($allListeners);
} else {
if (! isset($classMetadata->entityListeners[$eventName])) {
$io->info(sprintf('No listeners are configured for the "%s" event.', $eventName));

return self::SUCCESS;
}

$allListeners = [$eventName => $classMetadata->entityListeners[$eventName]];
}

$io->title(sprintf('Entity listeners for <info>%s</info>', $entityName));

$rows = [];
foreach ($allListeners as $event => $listeners) {
if ($rows) {
$rows[] = new TableSeparator();
}

foreach ($listeners as $order => $listener) {
$rows[] = [$order === 0 ? $event : '', sprintf('#%d', ++$order), sprintf('%s::%s()', $listener['class'], $listener['method'])];
}
}

$io->table(['Event', 'Order', 'Listener'], $rows);

return self::SUCCESS;
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('entity')) {
$suggestions->suggestValues($this->listAllEntities());

return;
}

if ($input->mustSuggestArgumentValuesFor('event')) {
$entityName = ltrim($input->getArgument('entity'), '\\');

if (! class_exists($entityName)) {
return;
}

$entityManager = $this->getManagerRegistry()->getManagerForClass($entityName);

if ($entityManager === null) {
return;
}

$classMetadata = $entityManager->getClassMetadata($entityName);
assert($classMetadata instanceof ClassMetadata);

$suggestions->suggestValues(array_keys($classMetadata->entityListeners));

return;
}
}

/** @return list<class-string> */
private function listAllEntities(): array
{
$entities = [];
foreach (array_keys($this->getManagerRegistry()->getManagerNames()) as $managerName) {
$entities[] = $this->getEntityManager($managerName)->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
}

$entities = array_values(array_unique(array_merge(...$entities)));

sort($entities);

return $entities;
}
}
111 changes: 111 additions & 0 deletions src/Tools/Console/Command/Debug/DebugEventManagerDoctrineCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Tools\Console\Command\Debug;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function array_keys;
use function array_values;
use function ksort;
use function method_exists;
use function sprintf;

final class DebugEventManagerDoctrineCommand extends AbstractCommand
{
protected function configure(): void
{
$this
->setName('orm:debug:event-manager')
->setDescription('Lists event listeners for an entity manager')
->addArgument('event', InputArgument::OPTIONAL, 'The event name to filter by (e.g. postPersist)')
->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command')
->setHelp(<<<'EOT'
The <info>%command.name%</info> command lists all event listeners for the default entity manager:

<info>php %command.full_name%</info>

You can also specify an entity manager:

<info>php %command.full_name% --em=default</info>

To show only listeners for a specific event, pass the event name as an argument:

<info>php %command.full_name% postPersist</info>
EOT);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$entityManagerName = $input->getOption('em') ?: $this->getManagerRegistry()->getDefaultManagerName();
$eventManager = $this->getEntityManager($entityManagerName)->getEventManager();

$eventName = $input->getArgument('event');

if ($eventName === null) {
$allListeners = $eventManager->getAllListeners();
if (! $allListeners) {
$io->info(sprintf('No listeners are configured for the "%s" entity manager.', $entityManagerName));

return self::SUCCESS;
}

ksort($allListeners);
} else {
$listeners = $eventManager->hasListeners($eventName) ? $eventManager->getListeners($eventName) : [];
if (! $listeners) {
$io->info(sprintf('No listeners are configured for the "%s" event.', $eventName));

return self::SUCCESS;
}

$allListeners = [$eventName => $listeners];
}

$io->title(sprintf('Event listeners for <info>%s</info> entity manager', $entityManagerName));

$rows = [];
foreach ($allListeners as $event => $listeners) {
if ($rows) {
$rows[] = new TableSeparator();
}

foreach (array_values($listeners) as $order => $listener) {
$method = method_exists($listener, '__invoke') ? '__invoke' : $event;
$rows[] = [$order === 0 ? $event : '', sprintf('#%d', ++$order), sprintf('%s::%s()', $listener::class, $method)];
}
}

$io->table(['Event', 'Order', 'Listener'], $rows);

return self::SUCCESS;
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('event')) {
$entityManagerName = $input->getOption('em') ?: $this->getManagerRegistry()->getDefaultManagerName();
$eventManager = $this->getEntityManager($entityManagerName)->getEventManager();

$suggestions->suggestValues(array_keys($eventManager->getAllListeners()));

return;
}

if ($input->mustSuggestOptionValuesFor('em')) {
$suggestions->suggestValues(array_keys($this->getManagerRegistry()->getManagerNames()));

return;
}
}
}
Loading