diff --git a/config/help/MakeDoctrineListener.txt b/config/help/MakeDoctrineListener.txt
new file mode 100644
index 000000000..1057ae65c
--- /dev/null
+++ b/config/help/MakeDoctrineListener.txt
@@ -0,0 +1,5 @@
+The %command.name% command generates a new event or entity listener class.
+
+php %command.full_name% UserListener
+
+If the argument is missing, the command will ask for the class name interactively.
diff --git a/config/makers.xml b/config/makers.xml
index ad7d45483..4592d2041 100644
--- a/config/makers.xml
+++ b/config/makers.xml
@@ -69,6 +69,12 @@
                 
             
 
+            
+                
+                
+                
+            
+
             
                 
                 
diff --git a/config/services.xml b/config/services.xml
index 8f0b2a209..12ccb9029 100644
--- a/config/services.xml
+++ b/config/services.xml
@@ -31,6 +31,8 @@
                 
             
 
+            
+
             
                 
             
diff --git a/src/Doctrine/DoctrineEventRegistry.php b/src/Doctrine/DoctrineEventRegistry.php
new file mode 100644
index 000000000..4d482ef59
--- /dev/null
+++ b/src/Doctrine/DoctrineEventRegistry.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\MakerBundle\Doctrine;
+
+use Doctrine\Migrations\Event\MigrationsEventArgs;
+use Doctrine\Migrations\Event\MigrationsVersionEventArgs;
+use Doctrine\Migrations\Events as MigrationsEvents;
+use Doctrine\ORM\Events;
+use Doctrine\ORM\Tools\ToolEvents;
+
+/**
+ * @internal
+ */
+class DoctrineEventRegistry
+{
+    private array $lifecycleEvents;
+
+    private ?array $eventsMap = null;
+
+    public function __construct()
+    {
+        $this->lifecycleEvents = [
+            Events::prePersist => true,
+            Events::postPersist => true,
+            Events::preUpdate => true,
+            Events::postUpdate => true,
+            Events::preRemove => true,
+            Events::postRemove => true,
+            Events::preFlush => true,
+            Events::postLoad => true,
+        ];
+    }
+
+    public function isLifecycleEvent(string $event): bool
+    {
+        return isset($this->lifecycleEvents[$event]);
+    }
+
+    /**
+     * Returns all known event names.
+     */
+    public function getAllEvents(): array
+    {
+        return array_keys($this->getEventsMap());
+    }
+
+    /**
+     * Attempts to get the event class for a given event.
+     */
+    public function getEventClassName(string $event): ?string
+    {
+        return $this->getEventsMap()[$event]['event_class'] ?? null;
+    }
+
+    /**
+     * Attempts to find the class that defines the given event name as a constant.
+     */
+    public function getEventConstantClassName(string $event): ?string
+    {
+        return $this->getEventsMap()[$event]['const_class'] ?? null;
+    }
+
+    private function getEventsMap(): array
+    {
+        return $this->eventsMap ??= self::findEvents();
+    }
+
+    private static function findEvents(): array
+    {
+        $eventsMap = [];
+
+        foreach ((new \ReflectionClass(Events::class))->getConstants(\ReflectionClassConstant::IS_PUBLIC) as $event) {
+            $eventsMap[$event] = [
+                'const_class' => Events::class,
+                'event_class' => \sprintf('Doctrine\ORM\Event\%sEventArgs', ucfirst($event)),
+            ];
+        }
+
+        foreach ((new \ReflectionClass(ToolEvents::class))->getConstants(\ReflectionClassConstant::IS_PUBLIC) as $event) {
+            $eventsMap[$event] = [
+                'const_class' => ToolEvents::class,
+                'event_class' => \sprintf('Doctrine\ORM\Tools\Event\%sEventArgs', substr($event, 4)),
+            ];
+        }
+
+        if (class_exists(MigrationsEvents::class)) {
+            foreach ((new \ReflectionClass(MigrationsEvents::class))->getConstants(\ReflectionClassConstant::IS_PUBLIC) as $event) {
+                $eventsMap[$event] = [
+                    'const_class' => MigrationsEvents::class,
+                    'event_class' => str_contains($event, 'Version') ? MigrationsVersionEventArgs::class : MigrationsEventArgs::class,
+                ];
+            }
+        }
+
+        ksort($eventsMap);
+
+        return $eventsMap;
+    }
+}
diff --git a/src/Maker/MakeDoctrineListener.php b/src/Maker/MakeDoctrineListener.php
new file mode 100644
index 000000000..302add3c9
--- /dev/null
+++ b/src/Maker/MakeDoctrineListener.php
@@ -0,0 +1,198 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\MakerBundle\Maker;
+
+use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
+use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
+use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
+use Doctrine\Common\EventArgs;
+use Symfony\Bundle\MakerBundle\ConsoleStyle;
+use Symfony\Bundle\MakerBundle\DependencyBuilder;
+use Symfony\Bundle\MakerBundle\Doctrine\DoctrineEventRegistry;
+use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
+use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
+use Symfony\Bundle\MakerBundle\Generator;
+use Symfony\Bundle\MakerBundle\InputConfiguration;
+use Symfony\Bundle\MakerBundle\Str;
+use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
+use Symfony\Bundle\MakerBundle\Validator;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use Symfony\Component\Console\Question\Question;
+
+final class MakeDoctrineListener extends AbstractMaker
+{
+    public function __construct(
+        private readonly DoctrineEventRegistry $doctrineEventRegistry,
+        private readonly DoctrineHelper $doctrineHelper,
+    ) {
+    }
+
+    public static function getCommandName(): string
+    {
+        return 'make:doctrine:listener';
+    }
+
+    public static function getCommandDescription(): string
+    {
+        return 'Creates a new doctrine event or entity listener class';
+    }
+
+    public function configureCommand(Command $command, InputConfiguration $inputConfig): void
+    {
+        $command
+            ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your doctrine event or entity listener')
+            ->addArgument('event', InputArgument::OPTIONAL, 'What event do you want to listen to?')
+            ->addArgument('entity', InputArgument::OPTIONAL, 'What entity should the event be associate with?')
+            ->setHelp($this->getHelpFileContents('MakeDoctrineListener.txt'));
+
+        $inputConfig->setArgumentAsNonInteractive('event');
+        $inputConfig->setArgumentAsNonInteractive('entity');
+    }
+
+    public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
+    {
+        $io->writeln('');
+
+        $event = $input->getArgument('event');
+
+        if (!$event) {
+            $events = $this->doctrineEventRegistry->getAllEvents();
+
+            $io->writeln(' Suggested Events:>');
+            $io->listing(array_map(function (string $event): string {
+                if ($this->doctrineEventRegistry->isLifecycleEvent($event)) {
+                    $event .= ' (Lifecycle)>';
+                }
+
+                return $event;
+            }, $events));
+
+            $question = new Question($command->getDefinition()->getArgument('event')->getDescription());
+            $question->setAutocompleterValues($events);
+            $question->setValidator(Validator::notBlank(...));
+
+            $input->setArgument('event', $event = $io->askQuestion($question));
+
+            if ($this->doctrineEventRegistry->isLifecycleEvent($event) && !$input->getArgument('entity')) {
+                $question = new ConfirmationQuestion(\sprintf('The "%s" event is a lifecycle event, would you like to associate it with a specific entity (entity listener)?', $event));
+
+                if ($io->askQuestion($question)) {
+                    $question = new Question($command->getDefinition()->getArgument('entity')->getDescription());
+                    $question->setValidator(Validator::notBlank(...));
+                    $question->setAutocompleterValues($this->doctrineHelper->getEntitiesForAutocomplete());
+
+                    $input->setArgument('entity', $io->askQuestion($question));
+                }
+            }
+        }
+
+        if (!$this->doctrineEventRegistry->isLifecycleEvent($event) && $input->getArgument('entity')) {
+            throw new RuntimeCommandException(\sprintf('The "%s" event is not a lifecycle event and cannot be associated with a specific entity.', $event));
+        }
+    }
+
+    public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
+    {
+        $name = $input->getArgument('name');
+        $event = $input->getArgument('event');
+
+        $eventFullClassName = $this->doctrineEventRegistry->getEventClassName($event) ?? EventArgs::class;
+        $eventClassName = Str::getShortClassName($eventFullClassName);
+
+        $useStatements = new UseStatementGenerator([
+            $eventFullClassName,
+        ]);
+
+        $eventConstFullClassName = $this->doctrineEventRegistry->getEventConstantClassName($event);
+        $eventConstClassName = $eventConstFullClassName ? Str::getShortClassName($eventConstFullClassName) : null;
+
+        if ($eventConstFullClassName) {
+            $useStatements->addUseStatement($eventConstFullClassName);
+        }
+
+        $className = $generator->createClassNameDetails(
+            $name,
+            'EventListener\\',
+            'Listener',
+        )->getFullName();
+
+        $templateVars = [
+            'use_statements' => $useStatements,
+            'method_name' => $event,
+            'event' => $eventConstClassName ? \sprintf('%s::%s', $eventConstClassName, $event) : "'$event'",
+            'event_arg' => \sprintf('%s $event', $eventClassName),
+        ];
+
+        if ($input->getArgument('entity')) {
+            $this->generateEntityListenerClass($useStatements, $generator, $className, $templateVars, $input->getArgument('entity'));
+        } else {
+            $this->generateEventListenerClass($useStatements, $generator, $className, $templateVars);
+        }
+
+        $generator->writeChanges();
+
+        $this->writeSuccessMessage($io);
+
+        $io->text([
+            'Next: Open your new listener class and start customizing it.',
+            'Find the documentation at https://symfony.com/doc/current/doctrine/events.html>',
+        ]);
+    }
+
+    public function configureDependencies(DependencyBuilder $dependencies): void
+    {
+        $dependencies->addClassDependency(
+            DoctrineBundle::class,
+            'doctrine/doctrine-bundle',
+        );
+    }
+
+    /**
+     * @param array $templateVars
+     */
+    private function generateEntityListenerClass(UseStatementGenerator $useStatements, Generator $generator, string $className, array $templateVars, string $entityClassName): void
+    {
+        $entityClassDetails = $generator->createClassNameDetails(
+            $entityClassName,
+            'Entity\\',
+        );
+
+        $useStatements->addUseStatement(AsEntityListener::class);
+        $useStatements->addUseStatement($entityClassDetails->getFullName());
+
+        $generator->generateClass(
+            $className,
+            'doctrine/EntityListener.tpl.php',
+            $templateVars + [
+                'entity' => $entityClassName,
+                'entity_arg' => \sprintf('%s $entity', $entityClassName),
+            ],
+        );
+    }
+
+    /**
+     * @param array $templateVars
+     */
+    private function generateEventListenerClass(UseStatementGenerator $useStatements, Generator $generator, string $className, array $templateVars): void
+    {
+        $useStatements->addUseStatement(AsDoctrineListener::class);
+
+        $generator->generateClass(
+            $className,
+            'doctrine/EventListener.tpl.php',
+            $templateVars,
+        );
+    }
+}
diff --git a/templates/doctrine/EntityListener.tpl.php b/templates/doctrine/EntityListener.tpl.php
new file mode 100644
index 000000000..82e36fdf9
--- /dev/null
+++ b/templates/doctrine/EntityListener.tpl.php
@@ -0,0 +1,14 @@
+= "
+
+namespace = $namespace; ?>;
+
+= $use_statements; ?>
+
+#[AsEntityListener(event: = $event ?>, entity: = $entity ?>::class)]
+final class = $class_name."\n" ?>
+{
+    public function __invoke(= $entity_arg ?>, = $event_arg ?>): void
+    {
+        // ...
+    }
+}
diff --git a/templates/doctrine/EventListener.tpl.php b/templates/doctrine/EventListener.tpl.php
new file mode 100644
index 000000000..5ed67de6c
--- /dev/null
+++ b/templates/doctrine/EventListener.tpl.php
@@ -0,0 +1,14 @@
+= "
+
+namespace = $namespace; ?>;
+
+= $use_statements; ?>
+
+#[AsDoctrineListener(event: = $event ?>)]
+final class = $class_name."\n" ?>
+{
+    public function = $method_name ?>(= $event_arg ?>): void
+    {
+        // ...
+    }
+}
diff --git a/tests/Doctrine/DoctrineEventRegistryTest.php b/tests/Doctrine/DoctrineEventRegistryTest.php
new file mode 100644
index 000000000..8a0f533f7
--- /dev/null
+++ b/tests/Doctrine/DoctrineEventRegistryTest.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\MakerBundle\Tests\Doctrine;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Bundle\MakerBundle\Doctrine\DoctrineEventRegistry;
+
+class DoctrineEventRegistryTest extends TestCase
+{
+    private static ?DoctrineEventRegistry $doctrineEventRegistry = null;
+
+    public static function setUpBeforeClass(): void
+    {
+        self::$doctrineEventRegistry = new DoctrineEventRegistry();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::$doctrineEventRegistry = null;
+    }
+
+    /**
+     * @testWith ["prePersist", true]
+     *           ["preUpdate", true]
+     *           ["preFlush", true]
+     *           ["loadClassMetadata", false]
+     *           ["onFlush", false]
+     *           ["postFlush", false]
+     */
+    public function testIsLifecycleEvent(string $event, bool $expected)
+    {
+        self::assertSame($expected, self::$doctrineEventRegistry->isLifecycleEvent($event));
+    }
+
+    /**
+     * @testWith ["preUpdate", "Doctrine\\ORM\\Event\\PreUpdateEventArgs"]
+     *           ["preFlush", "Doctrine\\ORM\\Event\\PreFlushEventArgs"]
+     *           ["onFlush", "Doctrine\\ORM\\Event\\OnFlushEventArgs"]
+     *           ["postGenerateSchemaTable", "Doctrine\\ORM\\Tools\\Event\\GenerateSchemaTableEventArgs"]
+     *           ["foo", null]
+     *           ["bar", null]
+     */
+    public function testGetEventClassName(string $event, ?string $expected)
+    {
+        self::assertSame($expected, self::$doctrineEventRegistry->getEventClassName($event));
+    }
+
+    /**
+     * @testWith ["preUpdate", "Doctrine\\ORM\\Events"]
+     *           ["preFlush", "Doctrine\\ORM\\Events"]
+     *           ["onFlush", "Doctrine\\ORM\\Events"]
+     *           ["postGenerateSchemaTable", "Doctrine\\ORM\\Tools\\ToolEvents"]
+     *           ["foo", null]
+     *           ["bar", null]
+     */
+    public function testGetEventConstantClassName(string $event, ?string $expected)
+    {
+        self::assertSame($expected, self::$doctrineEventRegistry->getEventConstantClassName($event));
+    }
+}
diff --git a/tests/Maker/MakeDoctrineListenerTest.php b/tests/Maker/MakeDoctrineListenerTest.php
new file mode 100644
index 000000000..3c68d3af2
--- /dev/null
+++ b/tests/Maker/MakeDoctrineListenerTest.php
@@ -0,0 +1,183 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\MakerBundle\Tests\Maker;
+
+use Symfony\Bundle\MakerBundle\Maker\MakeDoctrineListener;
+use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
+use Symfony\Bundle\MakerBundle\Test\MakerTestDetails;
+use Symfony\Bundle\MakerBundle\Test\MakerTestRunner;
+
+class MakeDoctrineListenerTest extends MakerTestCase
+{
+    private const EXPECTED_LISTENER_PATH = __DIR__.'/../../tests/fixtures/make-doctrine-listener/tests/EventListener/';
+
+    private function createMakeDoctrineListenerTest(): MakerTestDetails
+    {
+        return $this->createMakerTest()
+            ->addExtraDependencies('doctrine/orm')
+        ;
+    }
+
+    public function getTestDetails(): \Generator
+    {
+        yield 'it_make_event_listener_without_conventional_name' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        'foo',
+                        // event name
+                        'preUpdate',
+                        // associate with entity?
+                        'n',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'FooListener.php',
+                    $runner->getPath('src/EventListener/FooListener.php'),
+                );
+            }),
+        ];
+
+        yield 'it_make_entity_listener_without_conventional_name' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        'fooEntity',
+                        // event name
+                        'preUpdate',
+                        // associate with entity?
+                        'y',
+                        // entity name
+                        'User',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'FooEntityListener.php',
+                    $runner->getPath('src/EventListener/FooEntityListener.php'),
+                );
+            }),
+        ];
+
+        yield 'it_makes_event_listener_for_known_event' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        // listener name
+                        'FooListener',
+                        // event name
+                        'preUpdate',
+                        // associate with entity?
+                        'n',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'FooListener.php',
+                    $runner->getPath('src/EventListener/FooListener.php'),
+                );
+            }),
+        ];
+
+        yield 'it_makes_entity_listener_for_known_event' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        // listener name
+                        'FooEntityListener',
+                        // event name
+                        'preUpdate',
+                        // associate with entity?
+                        'y',
+                        // entity name
+                        'User',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'FooEntityListener.php',
+                    $runner->getPath('src/EventListener/FooEntityListener.php'),
+                );
+            }),
+        ];
+
+        yield 'it_does_not_make_entity_listener_for_non_lifecycle_event' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        // listener name
+                        'BarListener',
+                        // event name
+                        'postFlush',
+                        // associate with entity?
+                        'y',
+                        // entity name
+                        'User',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'BarListener.php',
+                    $runner->getPath('src/EventListener/BarListener.php'),
+                );
+            }),
+        ];
+
+        yield 'it_makes_event_listener_for_custom_event' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        // listener name
+                        'BazListener',
+                        // event name
+                        'onFoo',
+                        // associate with entity?
+                        'n',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'BazListener.php',
+                    $runner->getPath('src/EventListener/BazListener.php'),
+                );
+            }),
+        ];
+
+        yield 'it_does_not_make_entity_listener_for_custom_event' => [$this->createMakeDoctrineListenerTest()
+            ->run(function (MakerTestRunner $runner) {
+                $runner->runMaker(
+                    [
+                        // listener name
+                        'BazListener',
+                        // event name
+                        'onFoo',
+                        // associate with entity?
+                        'y',
+                        // entity name
+                        'User',
+                    ],
+                );
+
+                self::assertFileEquals(
+                    self::EXPECTED_LISTENER_PATH.'BazListener.php',
+                    $runner->getPath('src/EventListener/BazListener.php'),
+                );
+            }),
+        ];
+    }
+
+    protected function getMakerClass(): string
+    {
+        return MakeDoctrineListener::class;
+    }
+}
diff --git a/tests/fixtures/make-doctrine-listener/entities/attributes/User.php b/tests/fixtures/make-doctrine-listener/entities/attributes/User.php
new file mode 100644
index 000000000..53b10d49a
--- /dev/null
+++ b/tests/fixtures/make-doctrine-listener/entities/attributes/User.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace App\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+#[ORM\Entity]
+class User
+{
+    #[ORM\Id]
+    #[ORM\GeneratedValue]
+    #[ORM\Column]
+    private ?int $id = null;
+
+    #[ORM\Column(length: 255, nullable: true)]
+    private ?string $firstName = null;
+
+    #[ORM\Column(length: 255, nullable: true)]
+    private ?string $lastName = null;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getFirstName(): string
+    {
+        return $this->firstName;
+    }
+
+    public function setFirstName(?string $firstName)
+    {
+        $this->firstName = $firstName;
+    }
+
+    public function getLastName(): string
+    {
+        return $this->lastName;
+    }
+
+    public function setLastName(?string $lastName)
+    {
+        $this->lastName = $lastName;
+    }
+}
diff --git a/tests/fixtures/make-doctrine-listener/tests/EventListener/BarListener.php b/tests/fixtures/make-doctrine-listener/tests/EventListener/BarListener.php
new file mode 100644
index 000000000..f9e6f3358
--- /dev/null
+++ b/tests/fixtures/make-doctrine-listener/tests/EventListener/BarListener.php
@@ -0,0 +1,16 @@
+