Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Clock Laravel Package

lcobucci/clock

lcobucci/clock is a lightweight clock abstraction for PHP that decouples code from DateTimeImmutable. Use SystemClock for real time or FrozenClock for tests, making time-dependent logic easy to control and verify.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What it is: A ~4-file library that defines a PSR-20 Clock interface plus two implementations (SystemClock, FrozenClock). Its entire value proposition is replacing direct new DateTimeImmutable() / now() calls with an injected seam so time becomes deterministic in tests. There is no I/O, no state, no configuration, and no service provider.

Architecture fit: Poor-to-neutral for a typical Laravel codebase, and this is the central finding. Laravel already ships a time abstraction: Carbon::setTestNow(), the now() helper, and Carbon's CarbonImmutable all solve the same testability problem with zero new dependencies and zero new concepts for the team. Adopting lcobucci/clock means running two time abstractions in parallel, and the team must consciously decide which is canonical. The package is a better architectural fit for framework-agnostic libraries and DDD-style domain layers that must not depend on Laravel's helpers — that is its real niche, not "a Laravel app."

Integration feasibility: High mechanical feasibility, low political cost. Composer install, one container binding, and injection into services. The friction is not installation, it is retrofitting: every now(), Carbon::now(), new DateTimeImmutable(), and time() call in domain code must be converted before the abstraction pays for itself. Partial adoption yields the worst outcome — a clock that is injected in some paths and bypassed in others, so tests freeze time that production logic never reads.

Technical risk — the PHP floor is the headline risk. The release history shows an unusually aggressive support policy: 3.2.0 added PHP 8.3, 3.4.0 dropped PHP 8.2, and 3.6.0 requires PHP 8.4. This library tracks the newest PHP release and does not maintain a wide-version LTS line. If the application (or any deploy target, CI image, or shared hosting tenant) runs PHP 8.2 or 8.3, you are pinned to an older minor and will be forced into a PHP upgrade on the maintainer's schedule, not yours. For a TPM this is a recurring upgrade tax on a dependency that delivers maybe 200 lines of logic you could vendor yourself.

Secondary risks: a single primary maintainer; version 3.0.0 was a BC break (stella-maris/clock → psr/clock); 2.0.0 was a BC break (mandatory timezone argument); FrozenClock shipped with a bug fixed in 3.3.1. The trajectory is stable now, but this is a package that has broken compatibility three times in its history.

Data caveat: "Dependents: 0" should not be read as "nobody uses this." lcobucci/jwt — one of the most-installed PHP JWT libraries — depends on this package, so the real deployment footprint is orders of magnitude above the reported figure. Treat the metric as an artifact of the scanner, not as an adoption signal. Conversely, "Last release 2026-04-13" combined with a near-future dating convention means the freshness signal is only as good as the snapshot.

Key questions to resolve before committing:

  1. What is the minimum PHP version across all environments we deploy to today, and what is our 12-month PHP upgrade posture? If we are not already on 8.4, this package is effectively unavailable at current versions.
  2. Do we have domain code that genuinely must not know about Laravel/Carbon, or is every consumer an app-level service where Carbon::setTestNow() is sufficient?
  3. Are we willing to enforce the abstraction (static analysis or code review rule banning now()/new DateTimeImmutable() in the affected namespace)? If no, do not adopt.
  4. Does any existing dependency already bind a PSR-20 clock in the container? If so, we may get the seam for free without adding a direct dependency.
  5. Is FrozenClock guaranteed never to be reachable in a production code path, and how is that enforced?

Integration Approach

Stack fit: Composer-native, PSR-20-compliant, framework-agnostic — so it slots into Laravel cleanly but with no framework integration offered. There is no service provider, no facade, no config file; we own the wiring. Recommended bindings:

  • Clock::class → SystemClock::fromUTC() as a singleton (the class is readonly/immutable as of 3.4.0, so this is safe and cheap).
  • Test harness swaps the binding to FrozenClock::fromUTC() or new FrozenClock(new DateTimeImmutable('...')), with adjustTime() available (3.3.0+) for advancing time mid-test.
  • Prefer SystemClock::fromUTC() over fromSystemTimezone() — fromSystemTimezone() reintroduces the ambient-environment dependency the package exists to remove, and it silently changes behavior when php.ini date settings differ between local, CI, and production.

Migration path — incremental, evidence-driven, opt-in:

  1. Baseline check. Confirm PHP ≥ 8.4 in composer constraints, all CI runners, the production image, and every deploy target. If any target is below, stop and revisit after the PHP upgrade — do not adopt a downgraded pin.
  2. Shadow pilot. Add the binding and use the clock in one bounded context where time is a genuine business rule: token/session expiry, trial or subscription window calculations, rate-limit windows, proration, or report date ranges. These are the places where a frozen clock turns a flaky test into a deterministic one and where the ROI is demonstrable within a sprint.
  3. Instrument the gap. Before widening, measure how many time-reading call sites exist outside the pilot. If the number is large and clustered in Eloquent models, controllers, and Blade, the retrofit is not worth it — pivot to Carbon::setTestNow() globally and drop this package.
  4. Establish the boundary. If the pilot succeeds, define an explicit contract: domain services and actions accept Clock; framework glue, Eloquent timestamp columns, and Laravel's scheduler continue to use Carbon. Document that boundary; ambiguity here is how you end up with two half-adopted abstractions.
  5. Enforce. Add a PHPStan/PSR rule or a review checklist item prohibiting direct time reads inside the domain namespace. Without enforcement, adoption decays the moment the original author rotates off.
  6. Only then generalize to additional contexts, each as its own PR with a test that would previously have been flaky or required sleep().

Compatibility notes:

  • The package requires psr/clock (^1.0) — a two-method interface with no runtime weight. Many other libraries type-hint the same interface, so interop is genuine, not theoretical. Confirm no conflicting PSR-20 implementation is pinned elsewhere in the lockfile.
  • Watch the transitive pull: if lcobucci/jwt is already installed, lcobucci/clock may already be in composer.lock as a transitive dependency at a version pinned by JWT, not by us. Promote it to a direct dependency deliberately (and align the constraint) rather than relying on an incidental transitive copy.
  • No Laravel version constraint exists on the package itself, so Laravel compatibility is entirely a PHP-version question.

Sequencing recommendation: Do the PHP-baseline check and the time-read-call-site count first — both are hours of work and either can kill the initiative. Commit to the container binding and one pilot only if both come back clean. Budget the retrofit, not the install: the install is minutes, the conversion of call sites and the enforcement discipline is where the effort lives.

Operational Impact

Maintenance burden: Near zero at runtime, non-trivial at upgrade time. The dependency is dependency-light (Composer, PSR-20) but the maintainer ships frequent releases that track the newest PHP and bump dev tooling aggressively — the 3.6.0 notes are almost entirely Renovate bot PRs plus a dependency upgrade. Practically, expect a forced PHP minimum bump roughly every 12–18 months and a major version every few years. Plan for this in the platform upgrade calendar; it is a small but inescapable recurring commitment, and it will occasionally be the package that gates a Laravel major upgrade.

Support model: Single primary maintainer, active, MIT licensed, GitHub issues only, no commercial backing and no security-response commitment (though the surface is tiny — two methods returning DateTimeImmutable). Bus-factor risk exists but is mitigated by the fact that the entire library is small enough to fork or vendor in an afternoon. If this becomes load-bearing, capture a fork-and-maintain contingency in the architecture decision record. Do not treat it as a supply-chain critical dependency requiring ongoing vendor management; do treat it as a package you may one day own.

Scaling: Not a factor. No I/O, no caching, no shared state, immutable objects, single shared singleton instance. Cost is one object allocation per request. There is no horizontal or vertical scaling consideration and it will never appear in a performance profile. This is genuinely the strongest part of the case.

Failure modes and mitigations:

  1. Frozen clock in production — the highest-severity failure. A container misconfiguration or an environment branch that binds FrozenClock yields a permanently static "now," silently breaking expiry checks, token validity, TTLs, and time-window logic. It fails quietly and can persist undetected. Mitigation: bind FrozenClock only inside test bootstrap, never keyed off a runtime env flag; add an assertion or health check that Clock::class resolves to SystemClock outside the testing environment; alert on any production path returning a monotonic timestamp.
  2. Timezone drift — SystemClock requires an explicit timezone (since 2.0.0), which is good design, but a team that reaches for fromSystemTimezone() reinstates the ambient php.ini dependency and gets divergent behavior between containers. Mitigation: mandate fromUTC() in the binding and convert to display timezones at the presentation edge only.
  3. Dual time abstractions — half the code uses the injected clock, half uses Carbon/now(). Tests freeze one and exercise the other, producing false confidence. This is the most likely failure and the hardest to detect. Mitigation: the enforcement rule in step 5 above; treat any new direct now() in the domain namespace as a review defect.
  4. PHP floor escalation — a routine composer update raises the minimum PHP and breaks CI or deploy. Mitigation: pin with a caret constraint on the current major, gate dependency updates through CI's PHP matrix, and never accept a transitive bump to this package without checking the required PHP version.
  5. Silent behavior change on major upgrade — 3.0.0's interface swap and 2.0.0's mandatory timezone both changed call sites. Mitigation: read release notes for majors rather than relying on Composer's constraint solver alone.

Ramp-up: Trivial technically — a mid-level PHP developer understands the injected-clock pattern in under an hour, and the README's example is sufficient documentation. The real ramp-up cost is cultural, not technical: convincing a team accustomed to now() and Carbon::setTestNow() to consistently inject a clock, and keeping them consistent after the initial advocate moves on. Budget for that inertia explicitly, and be honest that for many Laravel teams it is the deciding factor. If the team will not sustain the discipline, the correct decision is to skip this package and standardize on Carbon's test-time facilities — the dependency is cheap, but a half-implemented abstraction is worse than none.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core