symfony/clock
Symfony Clock decouples your app from the system clock. Inject ClockInterface to get deterministic time via now() and control behavior like sleep() and time zones. Ideal for testable, time-sensitive code without relying on global time functions.
Installation:
composer require symfony/clock
Basic Usage:
Replace direct time calls with ClockInterface in your service constructor:
use Symfony\Component\Clock\NativeClock;
use Symfony\Component\Clock\ClockInterface;
class MyService {
public function __construct(private ClockInterface $clock) {}
}
First Use Case:
Replace new DateTimeImmutable() with $clock->now() in a time-sensitive method:
public function checkExpiry(DateTimeInterface $expiryDate) {
return $this->clock->now() > $expiryDate;
}
Dependency Injection:
Register the clock in Laravel’s service container (e.g., AppServiceProvider):
$this->app->bind(ClockInterface::class, function ($app) {
return new NativeClock();
});
Testing:
Use MockClock to simulate time:
use Symfony\Component\Clock\MockClock;
$clock = new MockClock('2024-01-01T00:00:00+00:00');
$service = new MyService($clock);
ClockInterface for method signatures (now(), sleep(), withTimeZone()).NativeClock and MockClock for production and testing implementations.ClockSensitiveTrait for quick adoption in existing classes.Refactor services to depend on ClockInterface:
class SubscriptionService {
public function __construct(
private ClockInterface $clock,
private SubscriptionRepository $repository
) {}
public function isExpired(Subscription $subscription) {
return $this->clock->now() > $subscription->expires_at;
}
}
NativeClock for MockClock in tests.Enforce a global timezone (e.g., UTC) or override per service:
$clock = (new NativeClock())->withTimeZone('UTC');
$service = new MyService($clock);
AppServiceProvider:
$this->app->bind(ClockInterface::class, fn() =>
(new NativeClock())->withTimeZone(config('app.timezone'))
);
Simulate time progression without real delays:
$clock = new MockClock('2024-01-01T00:00:00+00:00');
$service = new SubscriptionService($clock, $repository);
// Fast-forward 30 days
$clock->advance(new DateInterval('P30D'));
$this->assertTrue($service->isExpired($subscription));
Replace sleep() with controlled delays:
$clock = new MockClock();
$service = new RateLimiter($clock);
// Simulate a 2-second delay without waiting
$clock->sleep(2.0);
$this->assertTrue($service->isRateLimited());
ClockInterface into jobs for deterministic time checks:
class SendReminderJob implements ShouldQueue {
public function __construct(private ClockInterface $clock) {}
public function handle() {
if ($this->clock->now() > $this->dueDate) {
// Send reminder
}
}
}
ClockInterface in message handlers for time-based routing.$clock = new MockClock();
$client = new SymfonyHttpClient($clock);
Isolate time-sensitive features (e.g., A/B tests) by injecting clocks:
class NewCheckoutFlow {
public function __construct(private ClockInterface $clock) {}
public function isEnabled() {
return $this->clock->now() > config('features.new_checkout_date');
}
}
Inject ClockInterface to make commands testable:
class SendScheduledEmails extends Command {
protected $signature = 'emails:send-scheduled';
protected $description = 'Send emails with scheduled timestamps';
public function __construct(private ClockInterface $clock) {
parent::__construct();
}
public function handle() {
$now = $this->clock->now();
// Logic using $now
}
}
Use clocks for time-based access control:
class TimeBasedMiddleware {
public function __construct(private ClockInterface $clock) {}
public function handle(Request $request, Closure $next) {
if ($this->clock->now() > now()->startOfDay()->addHours(20)) {
abort(429, 'Maintenance mode');
}
return $next($request);
}
}
Avoid now() in model methods:
class Subscription extends Model {
public function isActive(ClockInterface $clock) {
return $clock->now() < $this->expires_at;
}
}
Clock facade (if available) or bind ClockInterface globally.Replace now() in scheduled tasks:
class CleanupOldLogs {
public function __construct(private ClockInterface $clock) {}
public function handle() {
$threshold = $this->clock->now()->subDays(30);
Log::where('created_at', '<', $threshold)->delete();
}
}
Standardize timestamps using the clock:
class ApiController {
public function __construct(private ClockInterface $clock) {}
public function index() {
return response()->json([
'data' => [...],
'generated_at' => $this->clock->now()->format('c'),
]);
}
}
sleep() ValuesMockClock::sleep(-1) throws an exception (unlike NativeClock).MockClock::advance() for time adjustments:
$clock->advance(new DateInterval('PT-1S')); // Rewind 1 second
now() results.$clock = (new NativeClock())->withTimeZone('UTC');
now(), Carbon::now(), or time() will bypass the clock.grep -r "new DateTime" --include="*.php" app/
grep -r "now()" --include="*.php" app/
NativeClock is not thread-safe (uses time() under the hood).NativeClock instance per process (Laravel’s container handles this).NativeClock may lose microsecond precision on some systems.ClockInterface methods that return DateTimeImmutable (which supports microseconds).MockClock isn’t properly advanced.$clock = new MockClock('2024-01-01T00:00:00+00:00');
// Test logic...
$clock = new MockClock(); // Reset for next test
Use Laravel’s DI container to inspect bound clocks:
$clock = app(ClockInterface::class);
dump($clock::class); // Should
How can I help you explore Laravel packages today?