rikudou/clock
A lightweight clock abstraction for PHP that makes time-dependent code easier to test. Inject a clock instead of calling time() directly, use the system clock in production, and swap in a fixed or mocked clock in tests to control dates and durations reliably. Framework-agnostic and simple to integra...
Install via Composer:
composer require rikudou/clock
Start by opening src/ — the package is intentionally tiny, essentially one or two classes around a now() method. Check composer.json for psr/clock in require (which would mean it implements Psr\Clock\ClockInterface) and confirm the exact namespace (Rikudou\Clock\...). That interface is what you'll type-hint throughout your application.
The first use case is almost always the same: a service that calls new DateTimeImmutable() or time() inline, making it untestable. Swap that call for an injected clock:
use Rikudou\Clock\ClockInterface; // confirm exact FQCN in src/
final class SubscriptionRenewer
{
public function __construct(private ClockInterface $clock) {}
public function renew(Subscription $subscription): void
{
$subscription->setExpiresAt(
$this->clock->now()->modify('+1 month')
);
}
}
Register the system clock in your container (Laravel: a singleton() binding in AppServiceProvider::register()), and from that point you have one seam for all time-dependent behavior.
Constructor injection, interface-first. Never type-hint a concrete clock in domain code. Bind the interface to a system clock singleton in production, and to a frozen clock in tests. This is the entire payoff of the package.
Frozen clock for tests. If the package doesn't ship one, a five-line anonymous class is enough:
$clock = new class(new DateTimeImmutable('2024-01-01 00:00:00')) implements ClockInterface {
public function __construct(private DateTimeImmutable $now) {}
public function now(): DateTimeImmutable { return $this->now; }
};
$this->app->instance(ClockInterface::class, $clock);
Use it wherever domain time matters. Token TTLs, subscription expirations, "created at" stamps, age gates, scheduled promotions, rate-limit windows. Leave infra-level timestamps (Laravel's created_at magic) alone — those are fine.
Wrap repetitive arithmetic in a domain helper. If you keep writing ->modify('+30 days'), a TokenClock decorator around the package clock keeps call sites readable.
Workflow tip: audit for now(), time(), new DateTime, date(), Carbon::now(). Each hit is a candidate. Migrate one service at a time, running tests after each.
Laravel integration — bind once:
// AppServiceProvider::register()
$this->app->singleton(ClockInterface::class, fn () => new SystemClock());
Then any resolved class gets it automatically. No facade needed.
Verify the exact API first. This is a tiny package — one developer, one star — so don't assume the namespace or that it implements PSR-20. If it does, you can freely swap it for laminas/laminas-clock, symfony/clock, or a PSR-20 adapter. If not, wrap it in your own thin interface so you're not locked in.
Timezone is the classic trap. Decide up front whether now() returns UTC or local-system time. If it uses new DateTimeImmutable() internally, it picks up PHP's date.timezone, so production and CI can disagree. Set date_default_timezone_set('UTC') at bootstrap and convert to display timezones only at the edges.
Immutability matters. If now() returns DateTimeImmutable, modify() returns a new instance and shared state stays clean. If it ever returns a mutable DateTime, clone before modifying — otherwise anything holding the clock gets corrupted.
Don't over-inject. Only classes whose behavior depends on when they run need the clock. A repository that just persists entities doesn't. Spreading it everywhere makes constructors noisy.
Long-running workers. A clock returns the current time at each call, so queue workers are fine — but if you cache a now() value in a constructor, you freeze it at boot. Always call $this->clock->now() at the moment you need it.
Debugging time bugs: when something behaves differently in tests vs. prod, first check which clock is bound. A stray new DateTimeImmutable() in the same class is the usual culprit.
Extension points. Write OffsetClock (delegates, adds a duration), LoggingClock (records calls), or SteppingClock (advances on each now() call) — each trivial, and useful for replaying slow-day scenarios or asserting call order.
How can I help you explore Laravel packages today?