Skip to content

Harden core utilities, test state, and console compatibility#432

Merged
binaryfire merged 40 commits into
0.4from
audit/core-utilities-test-state-console-compatibility
Jul 13, 2026
Merged

Harden core utilities, test state, and console compatibility#432
binaryfire merged 40 commits into
0.4from
audit/core-utilities-test-state-console-compatibility

Conversation

@binaryfire

@binaryfire binaryfire commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Several foundational utility surfaces had accumulated mismatches between their advertised contracts and their runtime behavior:

  • higher-order Conditionable calls narrowed valid truthy and falsy values to booleans;
  • Macroable assumed every closure could be rebound and framework macro state could survive between tests;
  • eager and lazy Collections disagreed on enum keys, duplicate keys, iterator inputs, callback arguments, and inferred return types;
  • several Arr helpers advertised iterable support that their implementations did not provide;
  • framework tests could leak package-manifest and inherited Symfony Request state;
  • the typed console contract depended on whichever Symfony Console version was already loaded, which breaks when Composer preloads an older bundled version.

This change corrects those inconsistencies at their owning boundaries. It does not add a second state model, compatibility registry, or request-time synchronization layer. The public surface remains Laravel-shaped where intended, with current collection capabilities added and obsolete Laravel-deprecated aliases removed.

For more details, see: docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md

Conditionable and Testbench isolation

Higher-order Conditionable proxies now accept the same truthy and falsy values as direct when() and unless() calls. Values are normalized once into the existing boolean state, preserving the proxy design and avoiding any new lifetime or concurrency machinery.

The log split package now declares its direct Conditionable dependency.

Testbench now preserves and restores the package manifest through its existing test lifecycle. Remote CLI operations can rebuild that manifest, and parallel tests must not leave the rebuilt contents visible to later tests. Route-cache coverage also asserts its package-discovery precondition directly so contamination fails at the source instead of surfacing as an unrelated route assertion.

Macroable and framework test state

Macroable closure dispatch now handles the legal PHP binding shapes explicitly:

  • instance and static macro calls retain their appropriate scope;
  • static closures are not rebound to an object;
  • first-class callable closures remain usable when PHP forbids rebinding them;
  • expected native binding warnings are suppressed only at the checked boundary whose nullable result is inspected immediately.

Framework-owned Macroable classes now expose or complete their static reset hooks, and the centralized test subscriber clears those registries after each test. Notification fakes, HTTP client surfaces, responses, uploaded files, JSON resources, and existing collection/database reset owners all participate in the same cleanup model.

Hypervel\Http\Request::flushState() also restores the inherited mutable Symfony configuration that Hypervel still consumes: MIME formats, method-override settings, allowed override methods, and the request factory. This prevents one test from changing request behavior for the remainder of a worker.

Cookie, JWT, log, and notifications now declare their direct Macroable dependency. The testing documentation consistently explains that framework-owned macro state is cleared centrally and that application- or package-owned macro state belongs in the supported test-state cleanup mechanism.

Collections correctness and parity

The Collections work aligns eager and lazy behavior while preserving their existing allocation and evaluation models:

  • LazyCollection::keyBy() supports backed enums consistently with eager collections;
  • lazy has() handles duplicate yielded keys and an empty requested-key set correctly;
  • sum() callbacks receive both value and key, with truthful accumulated-result types;
  • reduceInto() is available across the shared contract and implementations;
  • eager and lazy random() support key preservation through a shared conditional return type;
  • callback-free times() reports integer values instead of unresolved mixed generics;
  • lazy iterator construction accepts both IteratorAggregate and plain Iterator sources, including generators;
  • Arr first(), last(), every(), and some() honor their iterable contracts and preserve duplicate iterator occurrences;
  • callback-based Arr last() preserves reverse traversal, short-circuiting, and exception order;
  • Arr push() now advertises only arrays, matching the mutation and array return contract it can actually fulfill.

The Laravel-deprecated containsOneItem() and containsManyItems() aliases are removed in favor of hasSole() and hasMany(). Intentional omission markers prevent later parity work from restoring them accidentally.

Collection metadata now uses the PHP 8.5 polyfill required by the supported PHP floor, advertises the optional VarDumper integration, and keeps direct dependencies sorted. Public PHPDoc, named-argument parameter names, static-analysis fixtures, and the Collections and helpers guides now match the implemented behavior.

Composer-safe console contracts

Hypervel\Console\Application now declares typed all() and run() methods directly and delegates to Symfony. This keeps the accurate Hypervel contract independent of the Symfony version already resident in the process.

This matters during Composer scripts: Composer can preload an older bundled Symfony Console application whose corresponding methods have no return types. Inheriting those methods caused PHP to reject the Hypervel class before the post-autoload script could run.

A deterministic isolated-process regression loads the literal Hypervel application against a fixture matching the relevant older Composer method surface. Reflection coverage separately pins both methods to the concrete class and their contract return types. The fixture deliberately excludes addCommand(), matching the older parent and guarding against an invalid #[Override] assumption.

The remaining Console unit tests now use the framework test base, so coroutine setup, Mockery verification, and framework teardown apply consistently.

Performance and complexity

The common paths retain their existing shapes:

  • array-native Arr operations stay on their native fast paths;
  • callback-free Traversable last() uses constant memory;
  • callback-based Traversable last() stores ordered pairs only when reverse callback semantics require them;
  • lazy iterator normalization adds one construction-time type check, not per-item work;
  • macro reset and Testbench manifest work execute only during tests;
  • inherited Request reset executes only during test cleanup;
  • console bridges add one parent delegation on console operations, not request handling.

Key forwarding in sum() and checked closure binding perform the minimum work required for their corrected public behavior. No production lock, coroutine-context slot, dynamic registry, retry loop, or compatibility shim is introduced.

Validation

Coverage includes focused runtime regressions, static-analysis fixtures, deterministic coroutine/test-state isolation, manifest lifecycle tests, and an isolated Composer-preload class-loading test. PHP CS Fixer, both PHPStan configurations, the complete parallel component suite, the Testbench package contract suite, and the Testbench dogfood suite pass.

Summary by CodeRabbit

  • New Features

    • Added collection reduction with reduceInto(), key-preserving random selection, improved iterable support, and callback keys for summation.
    • Added broader static-state reset support across HTTP, notifications, factories, and related framework components.
    • Improved conditional proxy handling for truthy and falsy values.
    • Added console compatibility safeguards for older runtime environments.
  • Bug Fixes

    • Improved macro invocation reliability and cleanup.
    • Restored inherited request state during application teardown.
  • Documentation

    • Updated collection, helper, HTTP client, response, and audit documentation.
  • Tests

    • Expanded coverage for typing, iterators, state cleanup, macro behavior, and compatibility.

Higher-order Conditionable proxies accepted only boolean values even though when() and unless() use normal PHP truthiness. Property and method capture could therefore pass a valid integer, string, array, or object into a strict boolean parameter and fail with a TypeError.\n\nAccept unconstrained condition values at the proxy boundary and normalize them once into the existing typed boolean property. This preserves the Laravel-facing API and makes direct and captured conditions follow the same truthiness rules.
Cover every path affected by the proxy's previously narrow condition parameter. The regressions exercise direct integer conditions, higher-order property capture, higher-order method capture, truthy and falsy values, and the symmetric unless behavior.\n\nThe assertions observe the resulting fluent chain rather than implementation details, so they demonstrate the public behavior Laravel developers expect and fail against the old strict boolean boundary.
Declare void on the eight existing Conditionable test methods that omitted an explicit return type. This aligns both upstream-mirrored test locations with the repository's fully typed test convention without changing their behavior or structure.
The log package uses the Conditionable trait directly but relied on that package arriving transitively through another component. Add the direct split-package requirement so standalone log installations declare everything their source needs.\n\nThis matches the require-what-you-use convention followed by the other Hypervel Conditionable consumers and keeps the requirement in sorted order.
Remote Testbench CLI commands intentionally reuse the current ParaTest worker's disposable application copy. Booting those commands can rewrite bootstrap/cache/packages.php with root package providers, and the cache previously leaked into later tests assigned to the same worker.\n\nSnapshot the resolved manifest path after application setup and restore its exact existence and contents during the shared Testbench teardown lifecycle. Unchanged files are not rewritten, missing files are recreated atomically when required, and failed cleanup participates in the existing exhaustive first-exception-preserving teardown behavior.
Force a remote Testbench command to rebuild the worker-shared package manifest, prove that the child actually changed the cache, and then verify the base lifecycle restores the captured baseline before later teardown assertions run.\n\nThe regression covers both an originally present and absent manifest through the shared restoration contract, and documents the callback and setup ordering on which the observation relies.
The route-cache tests checked source route files and bootstrap configuration but not the raw package manifest consumed by their subprocess. A leaked provider manifest could therefore register package routes and produce a misleading successful cache result.\n\nRead the manifest through the checked filesystem boundary, validate its shape, and report any package providers before running the route assertions. Version-only package metadata remains valid, while route-producing contamination now fails immediately with the responsible provider names.
Route the framework-wide audit to Conditionable and record the verified package architecture, accepted findings, rejected concerns, cross-package implications, implementation, regression coverage, validation, performance impact, and review status.\n\nAlso record the independently exposed Testbench manifest-isolation defect under its owning package and add the Foundation revalidation dependency. The Conditionable checklist remains open until this work is integrated into the audit's 0.4 base, matching the plan's authoritative completion rules.
Make the Testbench subprocess regression exercise manifest restoration deterministically. The child first recreates a deleted manifest, after which the test writes a fixed valid probe and verifies that teardown restores the exact captured baseline.\n\nThis removes the ordering-dependent assumption that a child rebuild must produce bytes different from the original manifest while preserving coverage of the real shared-state lifecycle.
Bind closure macros using the legal instance and class scopes for each magic dispatch path. Static closures now follow Laravel's current behavior, while closures created from internal functions and first-class method callables retain their already-valid callable when PHP refuses rebinding.\n\nSuppress only the native binding warnings whose nullable results are checked immediately. Ordinary methods and non-Closure callables remain unchanged, and no reflection, callable registry, lock, or coroutine-local state is introduced.
Cover static and non-static closures through both instance and static macro dispatch, including protected class-scope access. Add regressions for internal-function closures, bound instance-method callables, static-method callables, and invokable-object controls.\n\nFlush both test fixture registries during teardown so the package's own macro state cannot contaminate later methods, and complete the required void return types while merging the current upstream coverage.
Extend the existing static reset hooks on Eloquent factories, HTTP client responses, and JSON resources to clear their Macroable registries. These classes already participate in centralized framework teardown, so their macros should follow the same test-lifetime boundary as their other mutable static state.\n\nImmutable per-class JSON resource metadata remains cached and untouched; only the mutable macro registries are reset.
Give each framework-owned HTTP class with a Macroable registry the standard flushState boundary. This allows the centralized test lifecycle to clear macros registered on HTTP factories, pending requests, response types, and uploaded files without package-local teardown duplication.\n\nThe hooks are pure static resets used between tests and add no work to request or response hot paths.
Add one authoritative Request flushState hook that clears macros and restores the inherited Symfony configuration still consumed by Hypervel: MIME formats, method-parameter override, allowed override methods, and the request factory.\n\nReset the private Symfony factory slot through its inherited setter while leaving coroutine-local trusted proxy and host state, immutable response metadata, and production request handling untouched. The hook runs only during framework test teardown.
Give the framework-owned notification fake a standard static reset hook for its Macroable registry. Macros installed by one test can otherwise survive for the PHPUnit worker lifetime and affect later notification tests.\n\nThe change is confined to centralized test cleanup and does not alter notification delivery or application runtime behavior.
Add every newly covered framework-owned Macroable class to AfterEachTestSubscriber's authoritative static reset list. This keeps cleanup centralized and exhaustive instead of requiring individual tests to remember which HTTP, request, or notification registries they touched.\n\nApplication and package-owned Macroable classes continue to use the existing test-state registration mechanism rather than being hardcoded into the framework subscriber.
Exercise the real framework reset boundary with macros registered on every previously omitted framework surface, including inherited JsonApi resource storage. Verify that one centralized cleanup clears every registry.\n\nAdd a companion regression that mutates all four inherited Symfony Request configuration surfaces Hypervel still consumes and proves teardown restores their visible behavior and defaults. Both tests retain local finally cleanup so failures cannot contaminate the worker.
Add hypervel/macroable to each split package that directly composes or invokes the trait: cookie, JWT, log, and notifications. This makes standalone subtree installations truthful instead of relying on unrelated transitive dependency paths.\n\nKeep every require block in package order and normalize the touched manifest endings. No application dependency graph changes because the monorepo already provides the same package versions.
Document that framework-owned collection, HTTP client, and response macros are cleared automatically by the test lifecycle. Remove stale instructions that made every test manually flush framework registries.\n\nDirect application and package Macroable classes remain responsible for registering their own cleanup through the documented test-state mechanism, preserving a clear ownership boundary.
Capture the verified callable, static-state, dependency, documentation, and test-isolation findings together with their accepted boundaries, rejected complexity, performance evidence, regression coverage, validation, and review status.\n\nRecord the inherited Request reset as shared http-01 for later HTTP and testing revalidation, mark Macroable complete, and route the next audit slice to Collections with the relevant completed ledger entries. Also retain the owner-selected multi-package branch and pull-request workflow established during this audit.
Align the package manifest with the PHP features and optional integrations the Collections component actually uses. Require the PHP 8.5 polyfill for PHP 8.4 runtimes, remove the unused PHP 8.6 polyfill, and advertise VarDumper for dump-oriented collection helpers.

Keep dependency and suggestion ordering canonical so future upstream and Composer metadata reviews see the package's real runtime surface.
Make Traversable-aware first, last, every, and some operations preserve iteration semantics without unsafe materialization, including duplicate keys and short-circuiting. Narrow Arr::push() to the array boundary its nested by-reference dot mutation genuinely requires.

Strengthen the generic and conditional PHPDoc for array transforms, random values, sorting, CSS compilation, filters, and wrapping. Add PHPStan fixtures that pin the resulting key, value, list, and conditional inference without adding production overhead.
Cover Traversable first and last selection with duplicate keys, callback short-circuit behavior, default handling, and iterable every and some semantics. These cases reproduce the materialization and key-collision failures fixed in Arr.

Pin the truthful array-only Arr::push() signature and complete the test declarations so the suite documents both the runtime behavior and public contract.
Correct enum_value() documentation so its fallback is described as either a zero-argument callable or a value, matching the helper's actual value() evaluation path rather than implying the enum value is passed into the callback.

Keep focused enum helper coverage and explicit test return types around native values, backed enums, nulls, and lazy defaults.
Describe value() and when() closures with their real argument and return templates, including conditional return types for known truthy and falsey inputs. Runtime behavior remains unchanged.

Add PHPStan fixtures for direct values, closures, defaults, dynamic conditions, and enum fallbacks so future annotation changes preserve the useful inference.
Add reduceInto() to the shared Enumerable surface, pass item keys to sum callbacks, and expose random key preservation through the contract because both eager and lazy implementations support it.

Correct factory, aggregate, reducer, sorting, and conditional return types across the interface and shared implementation. Add PHPStan fixtures for the new operation and the strengthened inference so these public contracts remain variance-safe and behaviorally accurate.
Normalize backed enum keys without attempting invalid object string casts, align ArrayAccess parameter names with the inherited interface for named arguments, and describe random, nth, and split results with their real key and failure behavior.

Remove Laravel's directly deprecated containsOneItem() and containsManyItems() aliases in favor of hasSole() and hasMany(), retaining an explicit removal marker for future upstream synchronization.
Accept plain Iterator sources wherever the lazy API advertises iterable input, normalize backed enum keys, and track outstanding requested keys so duplicate yields cannot falsely satisfy has(). Preserve keys in random selections when requested and reject a zero range step explicitly.

Remove the deprecated item-count aliases, strengthen lazy factory and aggregate types, and use Carbon's direct timestamp accessor. The iterator and membership corrections add no per-item work beyond the operations that require them.
Exercise backed-enum keying, reduceInto(), keyed sum callbacks, and ArrayAccess named arguments through the shared eager and lazy collection provider. Remove tests for the intentionally omitted deprecated aliases.

Complete explicit test and fixture return declarations across the shared suite so static analysis can check the full collection behavior surface without changing test semantics.
Cover ArrayIterator and Generator inputs through combine() and zip(), random key preservation, duplicate-key membership, and empty requested key sets. Remove obsolete coverage for the deprecated item-count aliases.

Complete explicit return declarations throughout the lazy collection suite while retaining the existing scheduling and enumeration behavior.
Retain the package's operation-by-operation enumeration limits while adding explicit test and helper return declarations. Remove the obsolete laziness assertion for the deprecated containsOneItem() alias and keep hasSole() as the supported behavior.

This keeps the suite focused on externally meaningful laziness guarantees after the public cleanup.
Declare the static-state regression test as void so its signature matches the framework test-suite convention.

The test continues to verify that Collection and LazyCollection flush both registered macros and higher-order proxy state between tests.
Run the focused Collection tests through Hypervel's framework TestCase rather than the raw PHPUnit base. This gives the file the same lifecycle, cleanup, and coroutine-aware test environment as the rest of the component suite.

No collection API behavior changes; this aligns test ownership with repository conventions.
Add explicit void return types to the higher-order proxy behavior tests.

Keep the existing property access, method forwarding, and fluent target-return assertions unchanged while making the file fully analyzable under the suite's declaration standards.
Document reduceInto(), keyed sum callbacks, key-preserving random selections, and iterable lazy-collection inputs using the final public API. Remove references to the intentionally omitted deprecated item-count aliases.

Keep examples Laravel-shaped and explain the new capabilities at their natural collection method sections rather than exposing internal implementation details.
Document the current enum_value(), value(), and conditional helper behavior, including lazy fallback evaluation and the relevant callable forms.

Align the examples with the corrected helper contracts so application and package authors see the same behavior that static analysis now models.
Add a focused Differences From Laravel section for the package's intentional public divergences: omitted deprecated item-count aliases, key preservation on the Enumerable random contract, and Arr::push()'s truthful array-only mutation boundary.

Keep the notes limited to user-visible API differences that matter when porting Laravel code or reviewing future upstream changes.
Record the verified Collections architecture, accepted defects and improvements, rejected lifecycle concerns, implementation boundaries, regression coverage, performance analysis, validation gates, and final review status in the audit ledger.

Mark Collections complete, preserve the exact 71-package checklist, and advance the compact routing index to Reflection with no unresolved cross-package revalidation.
Declare typed all() and run() bridges directly on the Hypervel console application. Composer can preload an older Symfony Console implementation whose inherited methods have no return types, so relying on the repository Symfony version caused Hypervel\Console\Application to fail during class loading even though the public contract itself was accurate.

Add a deterministic legacy-Symfony fixture and isolated subprocess regression that reproduces Composer method availability without depending on the installed Composer version. Reflection coverage also pins both methods to the concrete Hypervel class so the compatibility bridge cannot disappear silently.

Move the remaining Console unit tests off raw PHPUnit and onto the framework test base, including the required parent setup call, so coroutine setup, Mockery verification, and framework teardown apply consistently. Record the shared console/contracts boundary in the framework audit plan and ledger.

Validation: focused Console tests, the installed Composer PHAR preload probe, both PHPStan configurations, PHP CS Fixer, the full parallel component suite, Testbench contract tests, and Testbench dogfood tests all pass.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@binaryfire, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b20548f8-7cce-4391-a9c2-8574b77e819d

📥 Commits

Reviewing files that changed from the base of the PR and between 2ed3658 and e03b8a6.

📒 Files selected for processing (5)
  • docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
  • src/collections/src/Enumerable.php
  • src/collections/src/LazyCollection.php
  • src/macroable/src/Traits/Macroable.php
  • types/Collections/Collection.php
📝 Walkthrough

Walkthrough

The PR updates collection APIs and typing, improves macro dispatch and framework static-state cleanup, restores testbench package-manifest state, adds typed console compatibility bridges, and records the corresponding audit and validation changes.

Changes

Framework audit and lifecycle

Layer / File(s) Summary
Audit records and workflow
docs/plans/*
Audit ledger entries and branch, authorization, routing, and completion instructions are updated.
Macro and static-state cleanup
src/macroable/..., src/http/..., src/testing/..., src/testbench/..., src/database/..., src/support/...
Macro rebinding failures are handled, framework classes expose flush hooks, per-test cleanup covers additional HTTP and notification state, and cached package manifests are restored after tests.
Conditionable boundary
src/conditionable/..., tests/Support/SupportConditionableTest.php
Proxy conditions accept mixed values and normalize them to booleans, with corresponding truthiness coverage.

Collections

Layer / File(s) Summary
Collection contracts and behavior
src/collections/src/*, src/boost/docs/collections.md, src/collections/README.md, src/collections/composer.json
Iterable handling, key normalization, reducer APIs, random key preservation, generic PHPDoc, alias removal, documentation, and package metadata are updated.
Collection validation
tests/Support/*Collection*, tests/Support/SupportArrTest.php, types/Collections/*
Regression tests and PHPStan assertions cover the updated collection, Arr, lazy iteration, reducer, and helper contracts.

Console compatibility

Layer / File(s) Summary
Typed console compatibility bridge
src/console/src/Application.php, tests/Console/*
Application::all() and Application::run() declare local typed bridges, with reflection, fixture, subprocess, and test-base updates validating the compatibility boundary.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Test
  participant AfterEachTestSubscriber
  participant FrameworkClass
  participant Request
  Test->>AfterEachTestSubscriber: finish test
  AfterEachTestSubscriber->>FrameworkClass: flushFrameworkState()
  AfterEachTestSubscriber->>Request: flushState()
  Request-->>AfterEachTestSubscriber: reset inherited and macro state
  FrameworkClass-->>AfterEachTestSubscriber: clear static macro state
Loading

Possibly related PRs

Suggested reviewers: albertcht

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and broadly matches the PR’s main themes: core utilities, test-state cleanup, and console compatibility.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/core-utilities-test-state-console-compatibility

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR corrects behavioral mismatches across several foundational surfaces — Conditionable proxies, Macroable closure dispatch, Collection/LazyCollection parity, Arr iterable contracts, test-state isolation, and console compatibility with older Composer-bundled Symfony versions — without introducing new abstractions or state models.

  • Collections: LazyCollection::has() now handles empty key sets and duplicate-yielded keys correctly; sum() forwards the key to its callback; reduceInto() is added to the shared contract and both implementations; random() exposes preserveKeys through the Enumerable interface; makeIterator() handles plain Iterator sources from internal methods like combine(); deprecated containsOneItem()/containsManyItems() aliases are removed and documented in the new README under Differences From Laravel.
  • Macroable: __call now uses a three-tier @bindTo + ?? fallback chain (instance → class-scope → original) to handle static closures and first-class callables that PHP cannot rebind; __callStatic gets the same null-coalescing treatment.
  • Test isolation: Request::flushState() resets Symfony-inherited statics ($formats, $httpMethodParameterOverride, $allowedHttpMethodOverride, factory) so one test cannot alter request behavior for the remainder of the worker; eight additional HTTP and notification classes gain flushState() methods wired into AfterEachTestSubscriber; testbench preserves and restores the package manifest cache across each test lifecycle.

Confidence Score: 5/5

Safe to merge. All changes correct pre-existing behavioral gaps at their owning boundaries without introducing new state models or breaking existing contracts.

Each fix is traceable to a concrete mismatch between advertised contract and runtime behavior. The Macroable fallback chain handles all known PHP binding shapes. The collection parity changes are logically correct and covered by focused tests. The flushState() additions reset only pre-existing mutable static state. The console typed-override is a minimal fix for a real class-loading hazard.

No files require special attention. The most behaviorally dense change is LazyCollection — the has() fix and makeIterator() Iterator branch — both with dedicated test coverage.

Important Files Changed

Filename Overview
src/collections/src/Arr.php Iterable support added to first(), last(), every(), some(); Arr::push() narrowed to array-only; PHPDoc templates added throughout; all changes are correct
src/collections/src/LazyCollection.php Fixes has() for empty key sets and duplicate-key collections; fixes keyBy() backed enum handling; adds preserveKeys to random(); adds Iterator branch to makeIterator() for internal callers like combine()/zip(); switches Carbon->getTimestamp()
src/collections/src/Traits/EnumeratesValues.php Adds reduceInto(); forwards key to sum() callback; fixes times() return type to differentiate null/non-null callback; all changes correct
src/collections/src/Enumerable.php Removes deprecated interface methods with omission markers; adds reduceInto() and preserveKeys to random() contract; enriches PHPDoc types throughout
src/console/src/Application.php Adds typed all() and run() overrides with #[Override] to declare return types independent of whichever Symfony Console version Composer may have preloaded
src/macroable/src/Traits/Macroable.php Replaces unconditional bindTo() with @-suppressed null-coalescing fallback chain to handle static closures and first-class callables that cannot be rebound
src/http/src/Request.php Adds flushState() that resets Symfony-inherited static properties plus macros; placement immediately before trailing __isset/__get magic methods follows AGENTS.md rules
src/testing/src/PHPUnit/AfterEachTestSubscriber.php Adds flushState() calls for HTTP classes and NotificationFake to the centralized cleanup registry
src/testbench/src/TestCase.php Adds preservePackageManifestCache() preventing remote CLI manifest rebuilds from leaking to subsequent parallel tests
src/collections/README.md New README documents three intentional divergences from Laravel: removed deprecated aliases, extended random() contract, and narrowed push() to arrays only

Reviews (2): Last reviewed commit: "Refine macro and collection fallbacks" | Re-trigger Greptile

Comment thread src/macroable/src/Traits/Macroable.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/collections/src/Arr.php (1)

1261-1267: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a "Tests only." warning to flushState().

Same concern as UploadedFile::flushState()/Response::flushState(): flushing macros outside test cleanup silently removes every macro registered on Arr for the rest of the worker's life.

📝 Proposed docblock update
     /**
      * Flush all static state.
+     *
+     * Tests only. Calling this outside test cleanup removes every macro
+     * registered on this class for the remainder of the worker's life.
      */
     public static function flushState(): void

As per coding guidelines: "Add Boot-only., Tests only., or Boot or tests only. warnings to public mutators of worker-lifetime state when they are not intended for normal runtime use; name the concrete failure mode in the second sentence."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/collections/src/Arr.php` around lines 1261 - 1267, Add a “Tests only.”
warning to the docblock for Arr::flushState(), and in the second sentence state
that calling it outside test cleanup removes all macros registered on Arr for
the remainder of the worker lifetime.

Source: Coding guidelines

src/http/src/Client/Response.php (1)

540-547: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add Tests only. warning to flushState() docblock.

Now that flushState() calls static::flushMacros(), it mutates worker-lifetime macro state. Per coding guidelines, this public static method should carry a Tests only. warning naming the concrete failure mode.

As per coding guidelines: "Add Boot-only., Tests only., or Boot or tests only. warnings to public mutators of worker-lifetime state when they are not intended for normal runtime use; name the concrete failure mode in the second sentence."

📝 Proposed docblock update
     /**
      * Flush all static state.
+     *
+     * Tests only. Calling this method at runtime would clear all registered response macros for every subsequent request in the worker.
      */
     public static function flushState(): void
     {
         self::$defaultJsonDecodingFlags = 0;
         static::flushMacros();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/http/src/Client/Response.php` around lines 540 - 547, Update the docblock
for the public static method flushState() to include a “Tests only.” warning and
state that calling it during normal worker runtime mutates worker-lifetime macro
state, potentially affecting subsequent requests.

Source: Coding guidelines

🧹 Nitpick comments (5)
src/http/src/Client/Factory.php (1)

659-665: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add "Tests only." warning to flushState() docblock per coding guidelines.

Same as Request::flushState() — this public static method mutates worker-lifetime macro state. The coding guidelines require a Tests only. warning with the concrete failure mode. Other methods in this class (globalOptions, registerConnection, setConnectionConfig) already follow this pattern.

📝 Proposed docblock update
 /**
  * Flush all static state.
+ *
+ * Tests only. Calling this during normal request handling clears all
+ * registered macros for every subsequent HTTP client request on this worker.
  */
 public static function flushState(): void
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/http/src/Client/Factory.php` around lines 659 - 665, Update the docblock
for Factory::flushState() to include a “Tests only.” warning describing that it
mutates worker-lifetime macro state and must not be used in production, matching
the wording and structure of Request::flushState() and the existing warnings on
globalOptions(), registerConnection(), and setConnectionConfig().

Source: Coding guidelines

src/http/src/Request.php (1)

1183-1195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a Tests only. warning to Request::flushState()
Request::flushState() resets worker-lifetime static state and is already used in tests/Testing/PHPUnit/AfterEachTestSubscriber.php; calling it during normal request handling can leak state across concurrent requests on the worker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/http/src/Request.php` around lines 1183 - 1195, Add a clear “Tests only”
warning to the PHPDoc for Request::flushState(), stating that it resets
worker-lifetime static state and must not be called during normal request
handling. Keep the method’s reset behavior unchanged.

Source: Coding guidelines

src/macroable/src/Traits/Macroable.php (1)

122-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider replacing the exception-for-control-flow pattern in __call with a conditional.

Using throw new RuntimeException purely to jump into the catch block works but is an anti-pattern. A conditional check is clearer and avoids constructing an exception object on every rebind failure.

♻️ Proposed refactor
         if ($macro instanceof Closure) {
-            try {
-                // PHP warns when this binding is unsupported; the checked fallbacks select the next valid form.
-                $macro = @$macro->bindTo($this, static::class) ?? throw new RuntimeException;
-            } catch (Throwable) {
-                try {
-                    $macro = @$macro->bindTo(null, static::class) ?? $macro;
-                } catch (Throwable) {
-                    // Keep the original first-class callable when PHP does not permit rebinding it.
-                }
+            $bound = @$macro->bindTo($this, static::class);
+            if ($bound !== null) {
+                $macro = $bound;
+            } else {
+                try {
+                    $bound = @$macro->bindTo(null, static::class);
+                    $macro = $bound ?? $macro;
+                } catch (Throwable) {
+                    // Keep the original first-class callable when PHP does not permit rebinding it.
+                }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/macroable/src/Traits/Macroable.php` around lines 122 - 131, Replace the
intentional RuntimeException control flow in __call’s first bindTo fallback with
a conditional: only proceed to the second bindTo attempt when binding to $this
succeeds or otherwise explicitly handle the failed result without throwing.
Preserve the existing Throwable handling and original-callable fallback behavior
for unsupported rebinding.
src/collections/README.md (1)

8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use setext heading style for consistency.

The file opens with a setext-style title (=== on line 2), but ## Differences From Laravel on line 8 uses ATX style. markdownlint flags this as MD003.

[source_static_analysis_hints]

♻️ Proposed fix
-## Differences From Laravel
+Differences From Laravel
+---
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/collections/README.md` at line 8, Change the “Differences From Laravel”
heading in the README from ATX syntax to setext syntax, using the same heading
style as the opening title and preserving its heading level.
src/collections/src/LazyCollection.php (1)

991-996: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

$preserveKeys default is unused/dead due to func_get_args() forwarding.

Static analysis (PHPMD) flags $preserveKeys as unused — correctly so: func_get_args() only contains arguments the caller actually supplied, so this method's own $preserveKeys = false default is never actually applied; when omitted, behavior silently falls back to Collection::random()'s default instead. Forwarding explicitly makes intent clear and removes the redundant reliance on two independently-declared defaults staying in sync.

♻️ Proposed fix
-    public function random(callable|int|string|null $number = null, bool $preserveKeys = false): mixed
-    {
-        $result = $this->collect()->random(...func_get_args());
-
-        return is_null($number) ? $result : $this->newInstance($result);
-    }
+    public function random(callable|int|string|null $number = null, bool $preserveKeys = false): mixed
+    {
+        $result = $this->collect()->random($number, $preserveKeys);
+
+        return is_null($number) ? $result : $this->newInstance($result);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/collections/src/LazyCollection.php` around lines 991 - 996, Update
LazyCollection::random() to forward the resolved $number and $preserveKeys
arguments explicitly to collect()->random(), rather than using func_get_args().
Preserve the existing return behavior, including wrapping non-null-number
results with newInstance().

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md`:
- Line 207: Escape the pipe characters in the `collections-11` table row’s
“Final decision” text so `int|float|TReturnType` is rendered as literal text and
the markdown table retains its six-column structure.

In `@src/http/src/Client/PendingRequest.php`:
- Around line 1857-1864: Update the flushState() method’s documentation to
include a clear “Tests only.” warning, stating that it clears worker-lifetime
macro registrations and must not be called during requests because it can remove
macros needed by later requests in the same worker.

In `@src/http/src/Response.php`:
- Around line 327-334: Update the docblock for Response::flushState() to include
a “Tests only.” warning and state that calling it during normal runtime clears
all registered Response macros for the remainder of the worker’s lifetime.

In `@src/http/src/UploadedFile.php`:
- Around line 132-139: Update the docblock for UploadedFile::flushState() to
include a “Tests only.” warning and state that calling it during normal Swoole
worker runtime removes registered macros for the remainder of the worker’s
lifetime.

---

Outside diff comments:
In `@src/collections/src/Arr.php`:
- Around line 1261-1267: Add a “Tests only.” warning to the docblock for
Arr::flushState(), and in the second sentence state that calling it outside test
cleanup removes all macros registered on Arr for the remainder of the worker
lifetime.

In `@src/http/src/Client/Response.php`:
- Around line 540-547: Update the docblock for the public static method
flushState() to include a “Tests only.” warning and state that calling it during
normal worker runtime mutates worker-lifetime macro state, potentially affecting
subsequent requests.

---

Nitpick comments:
In `@src/collections/README.md`:
- Line 8: Change the “Differences From Laravel” heading in the README from ATX
syntax to setext syntax, using the same heading style as the opening title and
preserving its heading level.

In `@src/collections/src/LazyCollection.php`:
- Around line 991-996: Update LazyCollection::random() to forward the resolved
$number and $preserveKeys arguments explicitly to collect()->random(), rather
than using func_get_args(). Preserve the existing return behavior, including
wrapping non-null-number results with newInstance().

In `@src/http/src/Client/Factory.php`:
- Around line 659-665: Update the docblock for Factory::flushState() to include
a “Tests only.” warning describing that it mutates worker-lifetime macro state
and must not be used in production, matching the wording and structure of
Request::flushState() and the existing warnings on globalOptions(),
registerConnection(), and setConnectionConfig().

In `@src/http/src/Request.php`:
- Around line 1183-1195: Add a clear “Tests only” warning to the PHPDoc for
Request::flushState(), stating that it resets worker-lifetime static state and
must not be called during normal request handling. Keep the method’s reset
behavior unchanged.

In `@src/macroable/src/Traits/Macroable.php`:
- Around line 122-131: Replace the intentional RuntimeException control flow in
__call’s first bindTo fallback with a conditional: only proceed to the second
bindTo attempt when binding to $this succeeds or otherwise explicitly handle the
failed result without throwing. Preserve the existing Throwable handling and
original-callable fallback behavior for unsupported rebinding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8fa63fa5-0f1b-440c-93af-349a60dda3c3

📥 Commits

Reviewing files that changed from the base of the PR and between 831cbf4 and 2ed3658.

📒 Files selected for processing (58)
  • docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
  • docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
  • src/boost/docs/collections.md
  • src/boost/docs/helpers.md
  • src/boost/docs/http-client.md
  • src/boost/docs/responses.md
  • src/collections/README.md
  • src/collections/composer.json
  • src/collections/src/Arr.php
  • src/collections/src/Collection.php
  • src/collections/src/Enumerable.php
  • src/collections/src/LazyCollection.php
  • src/collections/src/Traits/EnumeratesValues.php
  • src/collections/src/functions.php
  • src/collections/src/helpers.php
  • src/conditionable/src/HigherOrderWhenProxy.php
  • src/console/src/Application.php
  • src/cookie/composer.json
  • src/database/src/Eloquent/Factories/Factory.php
  • src/http/src/Client/Factory.php
  • src/http/src/Client/PendingRequest.php
  • src/http/src/Client/Response.php
  • src/http/src/JsonResponse.php
  • src/http/src/RedirectResponse.php
  • src/http/src/Request.php
  • src/http/src/Resources/Json/JsonResource.php
  • src/http/src/Response.php
  • src/http/src/UploadedFile.php
  • src/jwt/composer.json
  • src/log/composer.json
  • src/macroable/src/Traits/Macroable.php
  • src/notifications/composer.json
  • src/support/src/Testing/Fakes/NotificationFake.php
  • src/testbench/src/TestCase.php
  • src/testing/src/PHPUnit/AfterEachTestSubscriber.php
  • tests/Conditionable/ConditionableTest.php
  • tests/Console/ConsoleApplicationCompatibilityTest.php
  • tests/Console/ContainerCommandLoaderTest.php
  • tests/Console/Fixtures/LegacySymfonyApplication.php
  • tests/Console/Scheduling/CacheEventMutexTest.php
  • tests/Console/Scheduling/CacheSchedulingMutexTest.php
  • tests/Console/Scheduling/FrequencyTest.php
  • tests/Integration/Foundation/Console/RouteCacheCommandTest.php
  • tests/Support/CollectionStaticStateTest.php
  • tests/Support/CollectionTest.php
  • tests/Support/HigherOrderProxyTest.php
  • tests/Support/SupportArrTest.php
  • tests/Support/SupportCollectionTest.php
  • tests/Support/SupportConditionableTest.php
  • tests/Support/SupportEnumValueFunctionTest.php
  • tests/Support/SupportLazyCollectionIsLazyTest.php
  • tests/Support/SupportLazyCollectionTest.php
  • tests/Support/SupportMacroableTest.php
  • tests/Testbench/Foundation/Process/RemoteCommandTest.php
  • tests/Testing/PHPUnit/AfterEachTestSubscriberTest.php
  • types/Collections/Arr.php
  • types/Collections/Collection.php
  • types/Collections/helpers.php

Comment thread docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md Outdated
Comment thread src/http/src/Client/PendingRequest.php
Comment thread src/http/src/Response.php
Comment thread src/http/src/UploadedFile.php
Replace exception-driven macro closure rebinding with the nullable binding contract provided by the supported PHP runtime. This preserves every callable form while avoiding exception construction when an instance call falls back to class-scope binding.

Forward LazyCollection random arguments explicitly, keep Laravel differences in the package README rather than inline source annotations, and repair the audit ledger's Markdown table escaping.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptileai @coderabbitai The review follow-up is in e03b8a6.

Accepted findings:

  • Removed both inline preserve-keys divergence annotations. The package README remains the single owner for that documented Laravel difference.
  • Escaped the union separators in the audit ledger so the Markdown table retains six columns.
  • Replaced Macroable exception-driven fallback with the PHP 8.4 nullable binding chain. The ordinary path is unchanged; static-closure fallback avoids exception construction; unsupported first-class callable bindings retain the original callable.
  • Forwarded both LazyCollection::random arguments explicitly. Existing behavior was already correct, but this removes func_get_args allocation and makes the local defaults authoritative.

Rejected findings:

  • No PHP 7 false-return handling was added. Hypervel requires PHP 8.4, whose Closure::bindTo contract is ?Closure; compatibility for an unloadable runtime would be dead code.
  • The seven flushState warning requests, including the outside-diff and nitpick items, remain unchanged. AGENTS.md has a specific rule requiring title-only flushState docblocks and explicitly forbidding those warning paragraphs. The underlying Macroable::flushMacros mutator already documents its worker-lifetime effect.
  • The Collections README keeps the repository-standard setext package title with ATX section headings. There is no project Markdownlint rule requiring the isolated style change.
  • No blanket docstrings were generated. The bot coverage threshold is not a repository gate, and self-evident comments would add maintenance noise.

The focused Macroable and LazyCollection regressions pass. The full formatter, both PHPStan configurations, parallel component suite, Testbench suite, and dogfood suite are green.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
git log --oneline -1 e03b8a637 2>/dev/null || echo "commit not found locally"
git show e03b8a637 --stat 2>/dev/null | head -50
echo "---Macroable---"
git show e03b8a637 -- src/macroable/src/Traits/Macroable.php 2>/dev/null
echo "---LazyCollection random---"
git show e03b8a637 -- src/collections/src/LazyCollection.php 2>/dev/null | head -80

Length of output: 5737


@binaryfire Confirmed — I checked commit e03b8a6 and it matches your summary exactly:

  • Macroable.php: The try/catch/RuntimeException fallback is gone, replaced by the clean nullable-coalescing chain (@$macro->bindTo($this, static::class) ?? @$macro->bindTo(null, static::class) ?? $macro) for the instance path, and the simplified single-line version for the static path. Unused RuntimeException/Throwable imports were removed accordingly. This is cleaner and avoids exception overhead on the fallback path while preserving identical behavior.
  • LazyCollection::random(): Now forwards $number and $preserveKeys explicitly instead of func_get_args().
  • Audit ledger: Markdown table escaping fixed.
  • Inline preserve-keys annotations removed from Enumerable.php and types/Collections/Collection.php, leaving the README as the single source of truth.

All changes are minimal and consistent with what was described. Thanks for the detailed rationale on the rejected findings too — the PHP 8.4 baseline, AGENTS.md docblock rule, README heading convention, and docstring-noise reasoning all make sense and I have nothing further to push back on there.

(´。• ᵕ •。`) ♡


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Changes in e03b8a6 look correct across all four accepted items.

Macroable: The nullable binding chain in both __call and __callStatic is well-structured. @ suppression is scoped to exactly the individual bindTo calls whose nullable results are consumed immediately — not wrapping broader logic — so the intent is clear and the warning surface is minimal. The three-way fallback in __call ($this scope → class scope → original callable) covers all the PHP binding shapes correctly.

LazyCollection random: Explicit argument forwarding is cleaner than func_get_args; local defaults are now authoritative.

Inline annotations and ledger escaping: Both applied cleanly.

The rejections are all well-grounded — PHP 7 compat would be unreachable dead code on PHP 8.4, the flushState docblock form is covered by AGENTS.md, and the setext title is repo-standard. Nothing to revisit there.

This is ready.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

The four accepted changes in e03b8a6 all look correct.

Macroable nullable chain — the new shape is clean:

// instance path
$macro = @$macro->bindTo($this, static::class)
    ?? @$macro->bindTo(null, static::class)
    ?? $macro;

// static path
$macro = @$macro->bindTo(null, static::class) ?? $macro;

Every callable form is handled without exception construction: regular closures bind to $this, static closures fall through to class-scope binding, first-class callables that PHP refuses to rebind retain the original. The @ suppression is scoped precisely to the checked boundary whose nullable result is consumed immediately — that's the right place for it.

LazyCollection::random forwarding — explicit args over func_get_args is correct; the local defaults are now authoritative and the allocation is gone.

Inline annotation removal — README as single owner is the right call. Inline divergence notes tend to drift independently of prose docs.

Audit ledger escaping — table renders correctly now.

The rejected findings are all sound. The PHP 7 false path would be unreachable dead code on the 8.4 floor, the flushState docblock policy is settled by AGENTS.md, the setext heading is consistent with the repo standard, and the blanket docstring threshold isn't a gate here.

@binaryfire binaryfire merged commit 8327e0e into 0.4 Jul 13, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant