chamber-orchestra/doctrine-clock-bundle
Install the Bundle Add the package to your project via Composer:
composer require chamber-orchestra/doctrine-clock-bundle
Enable the bundle in your config/bundles.php:
return [
// ...
ChamberOrchestra\DoctrineClockBundle\DoctrineClockBundle::class => ['all' => true],
];
Apply Attributes to Entities
Use the provided attributes (#[CreateTimestamp] and #[UpdateTimestamp]) on any DateTimeInterface property in your Doctrine entities:
use ChamberOrchestra\DoctrineClockBundle\Attribute\CreateTimestamp;
use ChamberOrchestra\DoctrineClockBundle\Attribute\UpdateTimestamp;
#[ORM\Entity]
class Post
{
#[ORM\Column(type: 'datetime_immutable')]
#[CreateTimestamp]
private DateTimeImmutable $createdAt;
#[ORM\Column(type: 'datetime_immutable')]
#[UpdateTimestamp]
private DateTimeImmutable $updatedAt;
}
Configure Symfony Clock (Optional)
The bundle integrates with Symfony’s ClockInterface. If you’re using a custom clock (e.g., for testing), bind it to the ClockInterface service:
# config/services.yaml
services:
Symfony\Component\Clock\ClockInterface: '@your_custom_clock_service'
Verify Functionality Run migrations and test CRUD operations. Timestamps should auto-populate on entity creation/updates.
New Entity Creation
#[CreateTimestamp] and #[UpdateTimestamp] to DateTimeImmutable properties.datetime_immutable (recommended) or datetime.Existing Entity Migration
php bin/console doctrine:schema:validate to confirm no conflicts.Custom Timestamp Logic
ChamberOrchestra\DoctrineClockBundle\Contract\TimestampStrategyInterface and tagging it as a service:
services:
App\Service\CustomTimestampStrategy:
tags: ['doctrine_clock.timestamp_strategy']
Testing Entities
ClockInterface mock in tests to control timestamps:
$this->clock = $this->createMock(ClockInterface::class);
$this->clock->method('now')->willReturn(new DateTimeImmutable('2023-01-01'));
$container->set(ClockInterface::class, $this->clock);
Event Listeners: Combine with Doctrine lifecycle events (e.g., prePersist, preUpdate) for side effects:
#[ORM\PrePersist]
public function setCreatedAt(): void {
$this->createdAt = $this->clock->now();
}
Note: Avoid redundancy—let the bundle handle timestamps unless custom logic is needed.
Precision Control: Use #[CreateTimestamp(precision: 6)] to set microsecond precision (default: 0).
Soft Deletes: Pair with gedmo/doctrine-extensions for deletedAt timestamps:
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
#[UpdateTimestamp]
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt')]
private ?DateTimeImmutable $deletedAt = null;
Attribute Targeting
DateTimeInterface properties (e.g., DateTimeImmutable, DateTime). Using strings or other types will silently fail.assert($property->getType() instanceof DateTimeInterface);
Clock Dependency
ClockInterface service. If none is configured, it defaults to Symfony\Component\Clock\SystemClock.ClockNotFoundException if timestamps are missing.Doctrine Event Conflicts
LifecycleCallbacks) may override timestamp behavior. Ensure no duplicate prePersist/preUpdate logic sets the same fields.#[ORM\HasLifecycleCallbacks] sparingly when using this bundle.Database Migrations
NULL, ensure your app handles NULL checks:
if ($entity->getCreatedAt() === null) {
$entity->setCreatedAt($this->clock->now());
}
Log Timestamp Events: Enable Doctrine event logging in config/packages/doctrine.yaml:
doctrine:
orm:
eventmanager: '@doctrine.eventmanager'
logging: true
Check logs for doctrine_clock entries.
Verify Service Binding: Dump the ClockInterface service to confirm it’s bound:
php bin/console debug:container ClockInterface
Custom Strategies
TimestampStrategyInterface to modify default behavior (e.g., timezone conversion):
class UTCTimestampStrategy implements TimestampStrategyInterface {
public function getTimestamp(ClockInterface $clock): DateTimeImmutable {
return $clock->now()->setTimezone(new DateTimeZone('UTC'));
}
}
doctrine_clock.timestamp_strategy.Attribute Customization
#[CreateTimestamp(precision: 6, timezone: 'Europe/Paris')]).Type/CreateTimestampType.php and Type/UpdateTimestampType.php.Testing Utilities
trait DoctrineClockTestTrait {
protected function mockClock(DateTimeImmutable $time): ClockInterface {
$clock = $this->createMock(ClockInterface::class);
$clock->method('now')->willReturn($time);
$this->container->set(ClockInterface::class, $clock);
return $clock;
}
}
ClockInterface is available. If using older versions, install symfony/clock via Composer.EventSubscriber/DoctrineClockSubscriber).precision: 6) may require database columns like datetime(6) (MySQL) or TIMESTAMP(6) (PostgreSQL).$entityManager->getEventManager()->removeEventListeners('prePersist');
$entityManager->getEventManager()->removeEventListeners('preUpdate');
// ... bulk operations ...
$entityManager->getEventManager()->addEventSubscriber(new DoctrineClockSubscriber($clock));
How can I help you explore Laravel packages today?