Event Hooks with Doctrine Lifecycle Events
Event Hooks with Doctrine Lifecycle Events
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
State processors (chapter 58) are the PREFERRED way for API-PLATFORM-specific logic – for logic that should apply REGARDLESS of HOW an entity gets saved (even outside the API), Doctrine events are the right choice.
An example: automatic updatedAt
// Project.php - new field
#[ORM\Column(nullable: true)]
#[Groups(['project:read'])]
private ?\DateTimeImmutable $updatedAt = null;
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeImmutable $updatedAt): static
{
$this->updatedAt = $updatedAt;
return $this;
}Writing an event listener
<?php
declare(strict_types=1);
namespace App\EventListener;
use App\Entity\Project;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Events;
#[AsEntityListener(event: Events::prePersist, entity: Project::class)]
#[AsEntityListener(event: Events::preUpdate, entity: Project::class)]
final class ProjectUpdatedAtListener
{
public function prePersist(Project $project, PrePersistEventArgs $args): void
{
$project->setUpdatedAt(new \DateTimeImmutable());
}
public function preUpdate(Project $project, PreUpdateEventArgs $args): void
{
$project->setUpdatedAt(new \DateTimeImmutable());
}
}#[AsEntityListener] AUTOMATICALLY registers the listener with Doctrine – prePersist fires on the FIRST save, preUpdate on EVERY subsequent change, REGARDLESS of whether it's triggered via the API, a fixture, or a console command.
Achtung: The KEY difference from chapter 58: a Doctrine event ALWAYS fires, even from a bin/console script with NO API involvement at all – a state processor only kicks in on API requests. For updatedAt, that's the DESIRED behavior (ALWAYS current, no matter HOW it's saved); for owner (chapter 54), a Doctrine event WOULD be UNSUITABLE, since Security::getUser() is NOT available outside an HTTP request.
When Doctrine events instead of a processor
| Mechanism | Use case |
|---|---|
| State processor | Logic is SPECIFIC to API requests (needs e.g. the logged-in user, request context) |
| Doctrine event | Logic should ALWAYS apply, regardless OF the trigger (data integrity, audit timestamps) |
Tipp: EXACTLY these same Doctrine events (prePersist, preUpdate, preRemove, etc.) were ALREADY covered in the Symfony course (chapter 28) – the knowledge transfers COMPLETELY, ONLY the context (API instead of a classic controller) is new.