icanhazstring/symfony-time-machine
Symfony Time Machine makes time-dependent Symfony code testable by letting you freeze, advance, or reset the application clock. Use it to create deterministic tests and simulate past or future dates without changing your system time.
Architecture fit. The first-order finding is unchanged by 0.1.1 and remains a framework mismatch: icanhazstring/symfony-time-machine is a Symfony bundle, not a Laravel package. It ships a Bundle subclass plus a DI extension and compiler pass, and is bootstrapped through config/bundles.php and the Symfony kernel. Laravel has no bundle loader — Illuminate\Foundation\Application cannot register a Symfony\Component\HttpKernel\Bundle\Bundle. A Laravel team therefore cannot "integrate" this package; it can only port the concept. That reframes the evaluation from "adopt" to "buy vs. build a ~100–200 LOC internal utility."
What changed in 0.1.1. A single patch release containing one PR, "Add timemachine handlers." It is the first activity since 0.1.0 (2022-10-14). The release notes give no detail on what a "handler" is — HTTP kernel handler/attribute, console command, message handler, or a clock-handler abstraction. That ambiguity is itself a data point: the changelog is a PR title, the source remains the spec, and the maintainer has added surface area without documenting it. Nothing in 0.1.1 addresses the framework mismatch, adds Laravel support, or adds dependents. It does not change the recommendation, but it does add entry points a reviewer must now enumerate before deciding anything about security exposure.
The underlying capability — mutating the application's notion of "now" so time-dependent logic (trial expiry, TTLs, scheduled promotions, aging workflows) can be exercised without waiting — is genuinely useful and legitimately hard to test otherwise. The value is in the pattern, not in this artifact.
Integration feasibility.
dev/test environments only, decorate the clock service, and now additionally audit whatever the new handlers expose (likely the operator-facing trigger surface).Clock contract and calls Carbon::setTestNow() / Carbon::setTestNowAndTimezone(), optionally driven by middleware reading a header or session key.Psr\Clock\ClockInterface / Symfony\Component\Clock, it only affects code that injects the clock — any legacy new \DateTime() or date() call site bypasses it. Coverage is partial by construction. The new handlers may widen the trigger surface without widening the capture surface; those are different problems.Technical risk (high).
symfony/framework-bundle ^5|^6 and PHP 7.4/8.0–8.1, which will collide with Symfony 7 and PHP 8.3+ — verify, do not assume the new release relaxed them, since a feature PR often ships without dependency housekeeping.opportunity: 2.67, 7 stars, and zero dependents. Treat the composite as unreliable; the opportunity sub-score is the honest signal.exp/nbf, TOTP windows, and rate-limit buckets. This must be hard-gated by environment and an explicit config flag, never by request input alone. If 0.1.1 adds a handler that reads from a request, that gate is now load-bearing in a way it previously was not.Key questions for the spike.
symfony/phpunit-bridge's ClockMock, or a custom service? (Determines coverage.)composer.json constraints? Are they satisfiable on our PHP 8.2/8.3 and Symfony 6.4/7.x matrix?sleep()-based retry logic, or only the request process?created_at rows written under shifted time?Stack fit. If the target is Laravel, close the package question and open a build ticket. The shim is small: one TimeMachineServiceProvider (bound to local/testing), a Clock binding or Carbon::setTestNow() call, and one middleware reading a test-only header or session key. Reimplementing costs roughly a day and yields a dependency we control, versus a fork of a barely-maintained bundle we would own anyway. 0.1.1 does not change this calculus; if anything the new handlers are Symfony-shaped plumbing we would not reuse.
If the target is genuinely Symfony, adopt with a fork: register the bundle in config/bundles.php only under dev and test, pin a vendored fork if composer constraints need loosening for Symfony 7, and review the new handlers before enabling them.
Migration path. There is no migration — nothing depends on this package, so there is no prior art to be compatible with and no downstream consumers to break. That is a benefit: we can define the contract ourselves (e.g., App\Contracts\Clock, TimeMachine::freeze()/travelTo()/reset()), and it also means zero ecosystem validation of the package's design. Note that 0.1.1 is a patch bump from 0.1.0, so any early adopter pinned at ^0.1 gets the new handlers automatically — which is the migration risk in miniature: new trigger surface arriving without a minor version or documented opt-in.
Compatibility. Verify before anything else: composer why-not against the current framework and PHP versions, re-run after 0.1.1 rather than reusing prior results. Expect friction on PHP ≥ 8.2 and Symfony ≥ 7. Also confirm whether it requires symfony/clock or psr/clock — if the codebase still calls now(), Carbon::now(), and date() directly rather than injecting a clock, the decorator approach yields false confidence. Grep for direct time calls first; that grep is the real integration cost, not the package.
Sequencing.
composer why-not.reset() in tearDown, and prove no cross-test leakage with a shuffled-order run.Maintenance. We inherit full ownership on day one. Zero dependents and a sparse release history mean no upstream will absorb our patches at any reliable cadence; the 0.1.1 release is evidence the maintainer is alive, not evidence of a support commitment. Treat this as an internal utility that happens to have a public ancestor. Budget ~0 recurring cost for the reimplementation and ~1–2 days/year if we fork (dependency bumps, Symfony/PHP upgrades) — plus one review pass now to understand the new handlers.
Support. Effectively none. 7 stars, one maintainer, no issue triage, changelog reduced to PR titles. Documentation is thin — the source is the spec, and 0.1.1 makes that more true, not less. Plan for a developer-day of source reading during ramp-up, with extra time for the handler diff.
Scaling. Runtime cost is negligible — a single service decoration. The scaling risk is concurrency semantics, not CPU: under Octane/workers, process-static state means a shift applied for one request can bleed into the next. Either scope the shift to a request lifecycle with guaranteed teardown in a finally, or restrict the feature to short-lived CLI/test processes. Queue workers need explicit propagation or they will silently run on real time while the web tier runs on shifted time — a subtle, hard-to-diagnose inconsistency. Additional handler entry points multiply the number of places teardown must be guaranteed.
Failure modes to design against.
reset() in teardown and randomized-order runs.created_at/updated_at; acceptable in ephemeral environments only, never against a shared or production-adjacent database.date()/now() call sites ignore the shift, producing false confidence in tests. Track coverage of clock injection as a first-class metric.^0.1 constraint pulls new trigger surface on composer update; pin exactly if we vendor it at all.Ramp-up. Low for Symfony-fluent engineers (familiar DI compiler-pass model), moderate for a Laravel team building the shim (provider + middleware + test harness, ~2–3 days including documentation). The organizational risk is not skill but discipline: an unguarded time machine is a security control bypass, so gate it in code review and in CI config policy.
TPM recommendation. Unchanged, and 0.1.1 does not move it. Do not adopt the package as a dependency. Port the capability (~1–3 days) behind an environment-gated, request-scoped clock abstraction, and invest the saved effort in removing direct time calls from the codebase — that refactor, not the time machine, is what actually makes time-dependent logic testable. The only follow-up 0.1.1 earns is a half-day source read to confirm what the handlers expose; if it turns out they add a request-reachable shift trigger without a hard config gate, that is a reason to strengthen the recommendation, not weaken it.
How can I help you explore Laravel packages today?