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

Recursion Context Laravel Package

sebastian/recursion-context

sebastian/recursion-context is a small PHP utility for tracking recursive traversals and avoiding infinite loops when walking nested structures. It provides the RecursionContext class used by sebastian/comparator and PHPUnit to handle recursion safely.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

sebastian/recursion-context is a single-purpose, low-level utility: it exposes a Context object that tracks object identities via spl_object_id() so that recursive traversal of PHP variables can detect and short-circuit circular references. It is not a Laravel package — there is no service provider, facade, config, or published asset — and it has no awareness of the framework at all. Architecturally it sits below the application layer, in the tooling/observability space: dumpers, exporters, test assertions, debug middleware, and DTO comparators.

For a Laravel team, the correct framing is that this is not a dependency you adopt as a product decision — it is a dependency you almost certainly already have. It arrives transitively through phpunit/phpunit (via sebastian/comparator, sebastian/exporter, sebastian/diff and friends) in every Laravel project with a standard test suite. The genuine TPM question is therefore not "should we integrate this?" but "do we have a policy for it, do we ever call it directly, and what is our exposure when it drops a PHP version?"

The legitimate direct-use cases are narrow and all internal-facing:

  • Safe serialization of deeply nested or bidirectional object graphs in logs, error reporters, or debug panels.
  • Custom PHPUnit constraints and assertions that compare object graphs with cycles (e.g. Eloquent models with reciprocal relationships, DTOs with parent/child back-references).
  • CLI/artisan tooling that inspects configuration or request state without hanging.

Integration feasibility

Trivially feasible technically — one Composer line, no bootstrapping, no configuration, no database or queue surface. The friction is entirely version-policy friction. Version 8.0.0 removes support for PHP 8.3 and below; 7.0.0 removed PHP 8.2; 6.0.2 swapped Psalm for PHPStan; 6.0.1 moved to spl_object_id(). This is a maintainer who treats PHP minor support as a hard gate and ships majors aggressively.

That means the package is only usable where the runtime matches. For a Laravel shop on a supported PHP version, installation is a non-event. For a shop on an older PHP (which is common — Laravel apps lag PHP upgrades by 12–24 months), the current major is simply unavailable and the team would be pinned to an older branch that is explicitly described as no longer supported.

Technical risk

  • Version-gate risk (high, but managed by Composer): the constraint on php is strict. A PHP upgrade or downgrade at the platform level can force a major bump of this package, which in turn can force a bump of PHPUnit, which can break the test suite. This is the realistic failure path, and it is a transitive blast radius, not a direct one.
  • API misuse risk (medium): the Context object is stateful. Reusing an instance across independent operations without calling reset() produces false-positive cycle detection — the documented [Recursion] marker appearing where no recursion exists. This is the #1 source of confusing bugs for teams that call the library directly, and it is invisible in code review.
  • Scope creep risk (medium): because it is tiny and dependency-free, there is a temptation to reach for it in application code (exporters, audit logging) rather than tooling. It only tracks objects, not arrays or resources, so a naive "make our logger cycle-safe" ticket can be closed with a false sense of completeness.
  • Abandonment risk (low): single-maintainer project, but that maintainer is the PHP ecosystem's most load-bearing figure and the package is a PHPUnit dependency. Bus-factor risk is real but the practical continuity risk is near zero.
  • Popularity signal is misleading: the ~6.5k stars reflect the Sebastian Bergmann tooling umbrella, not independent adoption. Do not treat it as a demand signal.

Key questions for the team

  1. On which PHP version(s) do our Laravel apps currently run, and does Composer already resolve this package transitively? (composer why sebastian/recursion-context)
  2. Do we have any direct usage, or is this purely transitive? If direct, is it in require-dev or require?
  3. Who owns the PHP-version-upgrade playbook, and does it account for test-tooling majors riding along?
  4. Is there an existing house preference for symfony/var-dumper for debug output, and would this overlap or conflict?
  5. Do we run any long-lived processes (Octane, queue workers) where a shared, unreset Context could leak state across jobs?

Integration Approach

Stack fit

Poor fit as a first-class application dependency; excellent fit as an invisible substrate. Laravel already ships symfony/var-dumper (via Ignition/dd) for developer-facing output, and PHPUnit for tests. recursion-context is the plumbing underneath the latter. The correct posture is: consume, do not adopt. Document it as a known transitive dependency, constrain it in composer.json only if you need to (e.g. "sebastian/recursion-context": "^7.0 || ^8.0" to keep the door open on PHP upgrades), and leave it alone otherwise.

Where a team does want direct use, the sanctioned surfaces should be:

  • Test-only helpers: custom Constraint subclasses, assertModelSynced-style assertions, deep-comparison utilities. Declared with composer require --dev.
  • Debug tooling: a safeExport()-style helper behind a debug flag, used by a custom error reporter or a Telescope-style panel — never in a request-path hot loop by default.

Explicitly out of bounds: domain logic, serialization for persistence, API response shaping, and anything a customer can trigger without a debug flag enabled.

Migration path

There is no migration to run — this is a policy exercise, not an implementation project. The work is:

  1. Run composer why sebastian/recursion-context and composer show sebastian/recursion-context across the estate to establish the current resolved versions and what pins them.
  2. Grep for direct imports of SebastianBergmann\RecursionContext — expect zero to a handful.
  3. Where direct usage exists in require (production), move it to require-dev, or replace with symfony/var-dumper if the goal was developer-facing output.
  4. Where a Context is held as a shared or singleton service, audit for reset() discipline; in Octane/queue contexts, prefer per-invocation instantiation over shared instances.
  5. Record the PHP-version coupling in the upgrade playbook so the next PHP bump surfaces the cascade in advance rather than mid-sprint.

Compatibility

  • PHP: strict and moving. New majors drop PHP minors on a rolling basis; treat the PHP version as the controlling constraint, not the package version.
  • Laravel: no framework constraint at all — compatibility is entirely a function of the PHP version Laravel supports.
  • PHPUnit: tightly coupled by design; the package major and the PHPUnit major will move together. Pin PHPUnit, and let Composer resolve this alongside it.
  • Composer: no exotic platform requirements beyond the PHP gate. If composer platform config is used to simulate a production PHP version, that config will drive resolution — verify it before assuming you can take a major.

Sequencing

Phase 1 (a day): inventory — resolved versions, direct imports, PHP versions per app, Octane/queue exposure. Phase 2 (a day): policy — classify as transitive-only, document the PHP-gate coupling, add a lint or CI step that fails if a production require introduces sebastian/*. Phase 3 (ongoing): fold into the existing PHP-upgrade runbook rather than creating a separate workstream.

Operational Impact

Maintenance

Near-zero ongoing cost if it stays transitive — Composer handles it, and the maintainer's release cadence is small and predictable (patch releases for PHP deprecation hygiene, majors for PHP version drops). The real maintenance cost is not in the package; it is in the cascade: a PHP version drop here forces a PHPUnit major, which forces a test-suite validation pass, which occasionally surfaces behavioural differences in comparisons and exporters. Budget that as part of PHP upgrades, not as a separate line item.

The one piece of ongoing hygiene needed is on any direct-usage code: stateful Context objects require discipline around reset(), and there is no static analysis rule that will catch a missed reset.

Support

Support burden is essentially nil at the package level — no vendor to call, no SLA, no paid tier, BSD-3-Clause with no attribution friction. Escalation path for a bug is an upstream GitHub issue on a project with a single primary maintainer, so mean-time-to-fix is best modelled as "whenever the maintainer next ships," not "within our contractual window." Because the package is a PHPUnit dependency, a genuine bug is more likely to be worked around in-house than to block a release; the code surface is small enough (a handful of methods on one class) that a local fork is a realistic last resort, though it should never be a first one.

Scaling

No runtime scaling characteristics to speak of. The implementation deliberately uses spl_object_id() rather than spl_object_hash() precisely to be cheap, and it only tracks objects. Traversal cost is O(number of distinct objects visited), which is the same order as the traversal the caller was going to do anyway — the context adds a hash-set lookup per object, not per scalar.

The one scaling-adjacent concern is memory in long-lived processes: a Context that is created, populated, and never released will hold references to every object it has seen, which is a genuine leak vector in Octane or long-running queue workers. The mitigation is structural — instantiate per operation, never bind as a singleton.

Failure modes

Mode Symptom Blast radius Mitigation
Stale context, no reset() Spurious [Recursion] markers; comparisons silently return wrong results Test suite produces false passes/failures Per-invocation instantiation; code review focus on Context lifetime
PHP platform upgrade Composer resolves a new major; PHPUnit major moves; test suite breaks Build pipeline, all apps on that platform Fold into PHP upgrade runbook; verify via composer platform
PHP downgrade / older platform Current major unavailable; must pin to an unsupported branch Cannot take patches Ensure PHP floor is aligned before adoption; avoid direct use on unsupported runtimes
Direct use in production path Debug/serialization helper invoked in request or job path; adds overhead and potential unbounded traversal on adversarial input User-facing latency, possible DoS vector Keep out of require; gate behind debug flags; code-owner review
Arrays mistaken for covered Caller assumes full cycle safety; array-based recursion still infinite-loops Hung request or worker Document the object-only limitation explicitly in internal guidance
Upstream stalls No fix available for a defect or new PHP deprecation Low — small surface, forkable Accept; monitor as part of PHPUnit upgrades

Ramp-up

Effectively zero for consumers — no training, no onboarding artifact beyond a paragraph. The only knowledge worth institutionalising is small enough to live in a short internal note: (1) this is a PHPUnit transitive dependency and we treat it as such; (2) it tracks objects only, not arrays; (3) Context is stateful and must be reset or re-instantiated; (4) it belongs in require-dev, never in production require. An engineer can be productive with it in under fifteen minutes, and the realistic onboarding cost is the first time someone debugs a spurious recursion marker.

Bottom line for the product decision: approve implicitly as a transitive dependency, formally decline any proposal to adopt it as a direct production dependency, and treat the PHP-version gate as the only item requiring ongoing governance.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource