symfony/clock
Symfony Clock decouples your code from the system clock. Inject ClockInterface to get DateTimeImmutable via now(), control timezones, and pause execution with sleep(). Ideal for testable, time-sensitive services without relying on global time.
ClockInterface can be injected into services, controllers, or jobs, replacing direct calls to now(), Carbon::now(), or time().NativeClock with MockClock in tests.UTC for internal services). Prevents bugs where local system time differs between dev/staging/production.ClockInterface as a singleton or bind it to specific implementations.sleep() or usleep() in background jobs with $clock->sleep() for predictable delays.ClockSensitiveTrait and can replace Carbon::setTestNow() or travel() in legacy tests.ClockInterface can be incrementally adopted:
now() calls with $clock->now() in production.sleep()/usleep() with $clock->sleep() in jobs.Carbon or DateTime usage. Can coexist with legacy code.ClockInterface to control time in CLI tools (e.g., php artisan migrate --force with a fixed timestamp).$clock->sleep() for reliable job delays (e.g., retries, exponential backoff).| Risk Area | Mitigation Strategy |
|---|---|
| Breaking Changes | Minimal risk: ClockInterface is stable; only replaces direct time calls. |
| Performance Overhead | Negligible: NativeClock uses DateTimeImmutable, which is optimized in PHP. |
| Testing Complexity | Reduces complexity: Replaces flaky time-based tests with deterministic mocks. |
| Time Zone Bugs | Eliminates risk by enforcing explicit timezone handling (e.g., withTimeZone('UTC')). |
| Legacy Code | Incremental adoption: Start with new features; refactor old code as needed. |
| PHP Version | Requires PHP 8.1+ (for Symfony 7+) or PHP 8.4+ (for Symfony 8+). Check compatibility. |
| MockClock Behavior | Aligned with NativeClock for negative sleep() values (fixed in v7.3.8+). |
NativeClock in production, or build a custom clock (e.g., Redis-backed for distributed systems)?UTC) for all services?Carbon::setTestNow()/travel() to MockClock?time(), date()) in favor of $clock->now()?$clock->sleep() replace usleep() in high-frequency jobs (e.g., WebSocket heartbeats)?NativeClock may not suffice?ClockInterface usage for observability?ClockInterface to NativeClock in AppServiceProvider:
$this->app->singleton(ClockInterface::class, fn() => new NativeClock());
sleep() calls in jobs with $clock->sleep() for deterministic delays.MockClock in PHPUnit tests to control time progression.ClockInterface into commands for reproducible CLI operations.symfony/mailer, symfony/http-client).spatie/laravel-rate-limiter).laravel/cashier).DateTimeImmutable compatibility).| Phase | Action Items | Laravel-Specific Steps |
|---|---|---|
| Assessment | Audit time-dependent logic (e.g., now(), time(), Carbon::now(), sleep()). |
Run grep for `now |
| Setup | Install package and register ClockInterface in Laravel’s service container. |
Add to composer.json; bind in AppServiceProvider. |
| Testing | Replace Carbon::setTestNow()/travel() with MockClock in unit tests. |
Use ClockSensitiveTrait or manually inject MockClock in tests. |
| Jobs/Queues | Replace sleep()/usleep() in jobs with $clock->sleep(). |
Update App\Jobs\* classes to inject ClockInterface. |
| Controllers | Inject ClockInterface into controllers for time-sensitive logic (e.g., feature flags). |
Use constructor injection in App\Http\Controllers\*. |
| Artisan | Inject ClockInterface into custom Artisan commands. |
Add to command constructors (e.g., php artisan my:time-sensitive-command). |
| Legacy Refactor | Replace direct now() calls with $clock->now() in critical paths. |
Start with high-risk areas (e.g., subscriptions, payments). |
| Monitoring | Add logging for ClockInterface usage (e.g., "Time decision: [timestamp]"). |
Use Laravel’s logging or Symfony’s Monolog integration. |
carbon/carbon (both use DateTimeImmutable under the hood).spatie/laravel-activitylog, spatie/laravel-permission, etc.How can I help you explore Laravel packages today?