tempest/clock
A lightweight clock abstraction for the Tempest framework, offering a testable interface for reading the current time. Swap in a frozen or mock clock during tests so time-dependent code stays deterministic instead of relying on native PHP date functions.
Verdict: Do not adopt as a dependency. Adopt the pattern, not the package. The capability is worth having; this specific distribution is not a viable dependency for a Laravel application at this maturity level.
Architecture fit
tempest/clock is a first-party component of the Tempest PHP framework, not a Laravel package. There is no ServiceProvider, no extra.laravel auto-discovery block, no facade, no publishable config, and no Laravel-facing contract. Its design assumptions are Tempest's: constructor-injection through the Tempest container, discovery-based wiring, and sibling tempest/* packages. In a Laravel application it is an unmanaged library at best.
The underlying concept — an injectable Clock boundary so that now(), sleep(), and elapsed-time logic are deterministic under test — fits Laravel extremely well. But Laravel already has three overlapping solutions in-tree or transitively:
Carbon::setTestNow() / Carbon::withTestNow() (the idiomatic path for the last decade, and what most Laravel test suites already use)symfony/clock + PSR-20 ClockInterface, which is present transitively in virtually every Laravel install via the Symfony console/HTTP stackIlluminate\Support\Facades\Date, which fronts Carbon for app-level overridesSo this package competes with an interface that is already on the classpath, already PSR-standardized, and already familiar to the team.
Integration feasibility
Two viable paths, in order of preference:
Psr\Clock\ClockInterface, with a SystemClock and a FrozenClock/MockClock for tests. This yields a Laravel-native service provider, full control of the contract, no third-party ownership of a cross-cutting concern, and no Tempest transitive dependencies.Clock into Laravel's container via a thin provider and alias the interface. This succeeds only if the component is genuinely standalone. If it pulls in tempest/support or the Tempest container contracts, path 2 collapses into vendoring a foreign framework's kernel, which is a hard no.Technical risk: HIGH
| Dimension | Assessment |
|---|---|
| Adoption | 0 stars, 0 dependents, no downstream usage to learn from |
| Maturity | Pre-1.0; no documented semver support window |
| Bus factor | Effectively 1 (Tempest core team); no external contributors of record |
| Security | No published security policy; no CVE history to extrapolate from |
| Supply chain | Small surface area, but unmonitored — no dependabot signal, no advisory feed |
| Lock-in | Bidirectional: adopting it couples your time abstraction to a framework you do not use |
The risk is not that the code is bad — it is that a cross-cutting, every-request-executed abstraction is being sourced from a zero-adoption package. That is a poor risk/reward trade for something the platform can express in a few lines.
Key questions (must be answered before any commitment)
tempest/support or Tempest container contracts? (composer show --tree tempest/clock)Psr\Clock\ClockInterface, or a bespoke interface? A PSR implementation is portable; a bespoke one is not.now(), sleep(), time(), timezone handling, monotonic vs. wall-clock semantics?Stack fit
Laravel 10/11/12 on PHP 8.2+. The recommended shape is a single Clock binding in a TimeServiceProvider aliasing Psr\Clock\ClockInterface to a SystemClock in production and a FrozenClock in tests. Consumers type-hint the interface; nothing calls now(), time(), date(), microtime(), or Carbon::now() directly in domain code. That is the entire architectural commitment — roughly one provider, one interface, two implementations.
Migration path
Phased and reversible, with no user-facing behavior change:
composer show --tree, confirm standalone-ness, and diff its API against Psr\Clock\ClockInterface. Decision gate: if it drags Tempest contracts in, stop and go to step 2b.app/Support/Clock/. Two implementations, one provider, one test fake.
2b. Adapter — only if the spike proves standalone: composer require tempest/clock with an exact version pin and a provide-style alias binding in a Laravel provider.Carbon::now() in presentation and logging layers.TestCase that freezes the clock and asserts determinism on a small set of flaky-history tests.Compatibility
Carbon::setTestNow() if the FrozenClock is implemented on top of Carbon, so both mechanisms agree and the team does not get two divergent notions of "now."symfony/clock is likely already installed; prefer binding to Psr\Clock\ClockInterface so either implementation satisfies the contract, keeping the adapter reversible.config('app.timezone') is something else. Normalize at the boundary; store UTC, render local.tempest/clock requires a PHP or Tempest version outside the app's constraint, Composer will either refuse or silently downgrade a shared dependency — pin exactly and run composer why-not before merging.Sequencing
Spike → interface decision (PSR vs. bespoke) → binding + provider → one pilot seam → test base class → expand. Do not block any roadmap item on this; it is a background quality investment sized at ~3–5 engineer-days end to end, most of which is retrofit and tests, not plumbing.
Maintenance
Under the transplant path, ownership is internal and the footprint is small enough that any Laravel engineer can maintain it — this is the whole argument for transplanting. Under the dependency path, you inherit an external release cadence you do not control for a component that executes on every request, with no dependents to surface breakage before you do. Upgrades become a manual, low-signal chore. Mitigation if the dependency is taken anyway: exact version pin, a composer outdated check in CI, and a documented 30-minute revert to an in-house implementation.
Support
There is no commercial support, no SLA, no documented support window, and effectively no community to ask. Escalation path is "read the source" — acceptable for a 200-line utility, not for something in the request hot path. Budget internal ownership explicitly rather than assuming upstream absorbs it.
Scaling
Runtime cost is negligible — a bound singleton returning a timestamp; no measurable throughput or memory impact at any realistic request volume. The scaling concern is organizational, not computational: the more call sites depend on the contract, the costlier a later contract change becomes. That argues for choosing the interface deliberately once (PSR-20) rather than letting a third party's signature dictate it.
Failure modes
BindingResolutionException; caught immediately in CI, low severity.SystemClock default bound in AppServiceProvider only, FrozenClock scoped to the test base class, and an assertion in the health check that "now" advances.Ramp-up
Near-zero. The API surface is a handful of methods; a mid-level engineer is productive in under an hour, and the retrofit work is mechanical. The real ramp-up cost is cultural — getting the team to stop calling now() directly and to inject the boundary consistently, which requires lint/static-analysis enforcement (a PHPStan or Pint rule banning bare now()/time() in app/Domain) rather than documentation alone.
Bottom line for the roadmap: treat this as a 3–5 day internal quality initiative, not a dependency evaluation. Revisit tempest/clock only if Tempest reaches 1.0 with meaningful adoption and the package ships a PSR-20 implementation with no Tempest kernel coupling. Until then, an in-house Clock bound to Psr\Clock\ClockInterface delivers the same value at strictly lower risk.
How can I help you explore Laravel packages today?