From cd92fa1b0783fc02a8825cd1d66af0bb5220071c Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:08:57 +0000
Subject: [PATCH 01/40] Fix Conditionable proxy truthiness
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.
---
src/conditionable/src/HigherOrderWhenProxy.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/conditionable/src/HigherOrderWhenProxy.php b/src/conditionable/src/HigherOrderWhenProxy.php
index 1fb3fa7c0..ba43176f6 100644
--- a/src/conditionable/src/HigherOrderWhenProxy.php
+++ b/src/conditionable/src/HigherOrderWhenProxy.php
@@ -32,9 +32,9 @@ public function __construct(
/**
* Set the condition on the proxy.
*/
- public function condition(bool $condition): static
+ public function condition(mixed $condition): static
{
- [$this->condition, $this->hasCondition] = [$condition, true];
+ [$this->condition, $this->hasCondition] = [(bool) $condition, true];
return $this;
}
From 54dc9d4900484c9e9025cd5d4aa3afdc9f47b27a Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:09:54 +0000
Subject: [PATCH 02/40] Test non-boolean Conditionable proxy values
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.
---
tests/Support/SupportConditionableTest.php | 36 ++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/tests/Support/SupportConditionableTest.php b/tests/Support/SupportConditionableTest.php
index 386551b7e..44c9edf68 100644
--- a/tests/Support/SupportConditionableTest.php
+++ b/tests/Support/SupportConditionableTest.php
@@ -137,6 +137,19 @@ public function testWhenProxy()
$this->assertSame(['init', 'one', 'three', 'six'], $logger->values);
}
+ public function testWhenProxyUsesNonBooleanTruthiness(): void
+ {
+ $logger = (new ConditionableLogger)
+ ->when(1)->log('direct truthy')
+ ->when(0)->log('direct falsy')
+ ->when()->truthyProperty->log('property truthy')
+ ->when()->falsyProperty->log('property falsy')
+ ->when()->truthyMethod()->log('method truthy')
+ ->when()->falsyMethod()->log('method falsy');
+
+ $this->assertSame(['direct truthy', 'property truthy', 'method truthy'], $logger->values);
+ }
+
public function testUnlessProxy()
{
// With static condition
@@ -164,6 +177,15 @@ public function testUnlessProxy()
$this->assertSame(['init', 'two', 'four', 'five'], $logger->values);
}
+
+ public function testUnlessProxyUsesNonBooleanTruthiness(): void
+ {
+ $logger = (new ConditionableLogger)
+ ->unless()->truthyProperty->log('truthy')
+ ->unless()->falsyProperty->log('falsy');
+
+ $this->assertSame(['falsy'], $logger->values);
+ }
}
class ConditionableLogger
@@ -174,6 +196,10 @@ class ConditionableLogger
public bool $toggle = false;
+ public int $truthyProperty = 1;
+
+ public int $falsyProperty = 0;
+
public function log(mixed ...$values): static
{
array_push($this->values, ...$values);
@@ -186,6 +212,16 @@ public function has(mixed $value): bool
return in_array($value, $this->values);
}
+ public function truthyMethod(): int
+ {
+ return 1;
+ }
+
+ public function falsyMethod(): int
+ {
+ return 0;
+ }
+
public function toggle(): static
{
$this->toggle = ! $this->toggle;
From cc095407d5df4cb20f962184c93a7e342d837501 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:10:08 +0000
Subject: [PATCH 03/40] Add return types to Conditionable tests
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.
---
tests/Conditionable/ConditionableTest.php | 4 ++--
tests/Support/SupportConditionableTest.php | 12 ++++++------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/tests/Conditionable/ConditionableTest.php b/tests/Conditionable/ConditionableTest.php
index 971c5cfc8..20026a827 100644
--- a/tests/Conditionable/ConditionableTest.php
+++ b/tests/Conditionable/ConditionableTest.php
@@ -27,7 +27,7 @@ protected function setUp(): void
$db->setAsGlobal();
}
- public function testWhen()
+ public function testWhen(): void
{
$this->assertInstanceOf(HigherOrderWhenProxy::class, TestConditionableModel::query()->when(true));
$this->assertInstanceOf(HigherOrderWhenProxy::class, TestConditionableModel::query()->when(false));
@@ -37,7 +37,7 @@ public function testWhen()
}));
}
- public function testUnless()
+ public function testUnless(): void
{
$this->assertInstanceOf(HigherOrderWhenProxy::class, TestConditionableModel::query()->unless(true));
$this->assertInstanceOf(HigherOrderWhenProxy::class, TestConditionableModel::query()->unless(false));
diff --git a/tests/Support/SupportConditionableTest.php b/tests/Support/SupportConditionableTest.php
index 44c9edf68..4238cf36e 100644
--- a/tests/Support/SupportConditionableTest.php
+++ b/tests/Support/SupportConditionableTest.php
@@ -9,7 +9,7 @@
class SupportConditionableTest extends TestCase
{
- public function testWhenConditionCallback()
+ public function testWhenConditionCallback(): void
{
// With static condition
$logger = (new ConditionableLogger)
@@ -34,7 +34,7 @@ public function testWhenConditionCallback()
$this->assertSame(['init', 'when', true], $logger->values);
}
- public function testWhenDefaultCallback()
+ public function testWhenDefaultCallback(): void
{
// With static condition
$logger = (new ConditionableLogger)
@@ -59,7 +59,7 @@ public function testWhenDefaultCallback()
$this->assertSame(['default', false], $logger->values);
}
- public function testUnlessConditionCallback()
+ public function testUnlessConditionCallback(): void
{
// With static condition
$logger = (new ConditionableLogger)
@@ -84,7 +84,7 @@ public function testUnlessConditionCallback()
$this->assertSame(['unless', false], $logger->values);
}
- public function testUnlessDefaultCallback()
+ public function testUnlessDefaultCallback(): void
{
// With static condition
$logger = (new ConditionableLogger)
@@ -109,7 +109,7 @@ public function testUnlessDefaultCallback()
$this->assertSame(['init', 'default', true], $logger->values);
}
- public function testWhenProxy()
+ public function testWhenProxy(): void
{
// With static condition
$logger = (new ConditionableLogger)
@@ -150,7 +150,7 @@ public function testWhenProxyUsesNonBooleanTruthiness(): void
$this->assertSame(['direct truthy', 'property truthy', 'method truthy'], $logger->values);
}
- public function testUnlessProxy()
+ public function testUnlessProxy(): void
{
// With static condition
$logger = (new ConditionableLogger)
From d201e3050b63712acd2b99a275ccd95c7c444dd8 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:10:19 +0000
Subject: [PATCH 04/40] Declare the log Conditionable dependency
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.
---
src/log/composer.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/log/composer.json b/src/log/composer.json
index 8678888ab..a08d3ca11 100644
--- a/src/log/composer.json
+++ b/src/log/composer.json
@@ -36,6 +36,7 @@
"monolog/monolog": "^3.1",
"psr/log": "^3.0",
"hypervel/collections": "^0.4",
+ "hypervel/conditionable": "^0.4",
"hypervel/config": "^0.4",
"hypervel/context": "^0.4",
"hypervel/contracts": "^0.4",
From d4f9d130b4ddf66bde9b601d4f1864030c8bca5c Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:10:28 +0000
Subject: [PATCH 05/40] Isolate the Testbench package manifest
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.
---
src/testbench/src/TestCase.php | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/src/testbench/src/TestCase.php b/src/testbench/src/TestCase.php
index 91a1c8715..a8fa5ce7b 100644
--- a/src/testbench/src/TestCase.php
+++ b/src/testbench/src/TestCase.php
@@ -6,11 +6,13 @@
use Hypervel\Coordinator\Constants;
use Hypervel\Coordinator\CoordinatorManager;
+use Hypervel\Filesystem\Filesystem;
use Hypervel\Foundation\Testing\DatabaseMigrations;
use Hypervel\Foundation\Testing\DatabaseTransactions;
use Hypervel\Foundation\Testing\RefreshDatabase;
use Hypervel\Foundation\Testing\TestCase as BaseTestCase;
use Hypervel\Testbench\Pest\WithPest;
+use RuntimeException;
use Swoole\Timer;
/**
@@ -70,6 +72,8 @@ protected function setUp(): void
parent::setUp();
+ $this->preservePackageManifestCache();
+
$this->baseUrl = config('app.url', 'http://localhost');
// Execute BeforeEach attributes INSIDE coroutine context
@@ -77,6 +81,35 @@ protected function setUp(): void
$this->runInCoroutine(fn () => $this->setUpTheTestEnvironmentUsingTestCase());
}
+ /**
+ * Preserve the package manifest cache for the duration of the test.
+ */
+ protected function preservePackageManifestCache(): void
+ {
+ $files = new Filesystem;
+ $path = $this->app->getCachedPackagesPath();
+ $existed = $files->isFile($path);
+ $contents = $existed ? $files->get($path) : '';
+
+ $this->beforeApplicationDestroyed(static function () use ($files, $path, $existed, $contents): void {
+ $exists = $files->isFile($path);
+
+ if (! $existed) {
+ if ($exists && ! $files->delete($path) && $files->isFile($path)) {
+ throw new RuntimeException("Unable to delete the test-owned package manifest cache [{$path}].");
+ }
+
+ return;
+ }
+
+ if ($exists && $files->get($path) === $contents) {
+ return;
+ }
+
+ $files->replace($path, $contents);
+ });
+ }
+
/**
* Set up database-related testing traits.
*
From df1d146c4d4ce193179b7c22563783b8a859bc7a Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:10:35 +0000
Subject: [PATCH 06/40] Test Testbench package manifest restoration
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.
---
.../Foundation/Process/RemoteCommandTest.php | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/tests/Testbench/Foundation/Process/RemoteCommandTest.php b/tests/Testbench/Foundation/Process/RemoteCommandTest.php
index 6f812e3ab..ad05f633c 100644
--- a/tests/Testbench/Foundation/Process/RemoteCommandTest.php
+++ b/tests/Testbench/Foundation/Process/RemoteCommandTest.php
@@ -4,6 +4,7 @@
namespace Hypervel\Tests\Testbench\Foundation\Process;
+use Hypervel\Filesystem\Filesystem;
use Hypervel\Foundation\Application;
use Hypervel\Testbench\Attributes\WithConfig;
use Hypervel\Testbench\Concerns\Database\InteractsWithSqliteDatabaseFile;
@@ -67,6 +68,39 @@ public function itDoesNotForwardTheParentRuntimeCopyToServeCommands(): void
});
}
+ #[Test]
+ public function itRestoresThePackageManifestAfterRemoteCommands(): void
+ {
+ $this->withoutSqliteDatabase(function (): void {
+ $files = new Filesystem;
+ $path = $this->app->getCachedPackagesPath();
+ // This must match the baseline captured by the base TestCase earlier in setUp.
+ $existed = $files->isFile($path);
+ $contents = $existed ? $files->get($path) : '';
+
+ // The base TestCase registered its restoration callback during setUp,
+ // so this later callback observes the already-restored manifest.
+ $this->beforeApplicationDestroyed(function () use ($files, $path, $existed, $contents): void {
+ if ($existed) {
+ $this->assertFileExists($path);
+ $this->assertSame($contents, $files->get($path));
+ } else {
+ $this->assertFileDoesNotExist($path);
+ }
+ });
+
+ // Force the child to build the manifest instead of reusing the baseline cache.
+ if ($files->isFile($path)) {
+ $this->assertTrue($files->delete($path));
+ }
+
+ remote('about --json')->mustRun();
+
+ $this->assertFileExists($path);
+ $this->assertNotSame($contents, $files->get($path));
+ });
+ }
+
/**
* Get the configured environment variables for the wrapped Symfony process.
*
From 840b45a90f6c8e90f7ca76a660516ecfb9ded434 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:10:44 +0000
Subject: [PATCH 07/40] Harden route cache test preconditions
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.
---
.../Console/RouteCacheCommandTest.php | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/tests/Integration/Foundation/Console/RouteCacheCommandTest.php b/tests/Integration/Foundation/Console/RouteCacheCommandTest.php
index b9d1696f9..0a21d4308 100644
--- a/tests/Integration/Foundation/Console/RouteCacheCommandTest.php
+++ b/tests/Integration/Foundation/Console/RouteCacheCommandTest.php
@@ -298,5 +298,32 @@ protected function assertCleanTestbenchRouteSources(): void
$routeFiles,
'Unexpected Testbench runtime route files found: ' . implode(', ', $routeFiles),
);
+
+ $manifestPath = $this->app->getCachedPackagesPath();
+
+ if ($this->files->isFile($manifestPath)) {
+ $manifest = $this->files->getRequire($manifestPath);
+
+ $this->assertIsArray(
+ $manifest,
+ "Testbench package manifest [{$manifestPath}] did not return an array.",
+ );
+
+ $providers = [];
+
+ foreach ($manifest as $configuration) {
+ if (is_array($configuration)) {
+ $providers = [...$providers, ...(array) ($configuration['providers'] ?? [])];
+ }
+ }
+
+ $providers = array_values(array_unique(array_filter($providers, is_string(...))));
+
+ $this->assertSame(
+ [],
+ $providers,
+ 'Unexpected Testbench package providers found: ' . implode(', ', $providers),
+ );
+ }
}
}
From fe4e638397bbfcde9c89993134cf748923fb1388 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:11:05 +0000
Subject: [PATCH 08/40] Record the Conditionable audit
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.
---
...-coroutine-state-lifecycle-audit-ledger.md | 19 +++++++++++++++++++
...amework-coroutine-state-lifecycle-audit.md | 5 +++--
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
index e47eb2343..80ca31c29 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -134,3 +134,22 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Implementation and cleanup:** Replaced the generic queue-contract checks with the Eloquent types the restoration path can actually reconstruct. Collection restoration now resolves the model class once before construction and pivot checks, matching the already-correct single-model path. Removed the now-unused generic imports and retained only focused comments at the two non-obvious boundaries; no user-facing difference entry was added.
- **Validation:** Eloquent relation stripping, non-Eloquent entity/collection round trips, and morph-mapped Eloquent collection restoration pass. Pre-implementation second-opinion consensus, owner approval, and work-unit code review are complete; the later full `queue` audit remains pending.
- **Revalidation:** Full `queue` audit remains pending.
+
+### Restore Conditionable proxy truthiness
+
+- **Architecture and inspected risk surfaces:** Conditionable consists of one stateless trait and one short-lived higher-order proxy under the shared `Hypervel\Support` namespace. The audit covered both source files, both package-owned test files, every framework consumer, all consuming split-package manifests, package history, and current Laravel source and tests. It found no static or worker-lifetime state, container lifetime, coroutine, resource, native boundary, or cleanup owner.
+
+| ID | Category | Severity | Confidence | Failure and owning boundary | Final decision |
+|---|---|---|---|---|---|
+| `conditionable-01` | Defect | Major | High | `HigherOrderWhenProxy::condition(bool)` rejects the non-boolean truthy/falsy values accepted by `when()` and returned by higher-order property/method capture | Accept `mixed` at the single setter boundary, normalize once into the existing bool property, and regress direct/property/method truthiness plus the symmetric `unless` path |
+| `conditionable-02` | Improvement | Improvement | High | Eight package-owned test methods omit the repository-required `void` return type | Add `: void` without restructuring the tests |
+| `conditionable-03` | Defect | Minor | High | `hypervel/log` directly uses the trait but is the sole one of 17 Hypervel consumer packages not to declare `hypervel/conditionable` | Add the direct split-package requirement, matching Hypervel's require-what-you-use convention |
+| `testbench-01` | Defect | Minor | High | Testbench CLI subprocesses implicitly rewrite the per-worker package manifest with root providers, and the shared Testbench lifecycle did not restore it between tests | Preserve the manifest once in the Testbench base test lifecycle and make the route-cache test assert its no-provider precondition |
+
+- **Important rejected concerns:** Do not add coroutine context, scoped bindings, cloning, locks, or cleanup machinery to a per-invocation proxy with no hidden shared state. Keep the Eloquent integration test because it verifies the split trait through a real consumer and its Capsule global is reset centrally. Do not consolidate the two upstream-mirrored test locations or add a README absent from this family of Laravel and Hypervel micro-packages.
+- **Cross-package implications:** `conditionable-03` corrects the `log` split manifest; the later full `log` audit must retain and revalidate that direct dependency. Future `macroable` and `collections` audits should repeat the same consumer-manifest sweep rather than assuming either a direct-edge or umbrella dependency convention. `testbench-01` belongs to the shared Testbench lifecycle and is also revalidated by the Foundation route-cache subprocess tests; the later full `testbench` and `foundation` audits must retain that ownership boundary.
+- **Implementation:** The higher-order proxy now accepts unconstrained condition values and stores their boolean normalization in its existing typed property. The log split manifest now declares its direct Conditionable dependency. Testbench snapshots the resolved package-manifest cache after application setup and restores its exact existence and contents through the existing exhaustive pre-destruction callback lifecycle, without rewriting an unchanged file. The route-cache precondition guard now reports unexpected raw package providers directly.
+- **Regression tests:** Conditionable coverage now exercises direct, higher-order property, and higher-order method truthiness with non-boolean values, plus the symmetric `unless` path. The remote-command regression forces a Testbench CLI child to rebuild the shared manifest, proves the write happened, and verifies teardown restored the captured baseline. The previously failing remote-command-before-route-cache sequence passes deterministically.
+- **Performance and complexity:** The Conditionable correction adds one allocation-free boolean normalization per higher-order proxy condition. It adds no lookup, lock, yield, retained state, compatibility path, or abstraction. The manifest dependency and test signatures have no runtime cost. Manifest preservation adds small filesystem reads only to Testbench test setup/teardown and performs no write when the cache is unchanged; production and application hot paths are unaffected.
+- **Validation and review:** Focused Conditionable, remote-command, route-cache, and cross-test ordering regressions pass. The log split manifest validates strictly, `git diff --check` passes, and the full `composer fix` gate is green. Pre-implementation second-opinion consensus, owner approvals, fresh self-review, and final code-review sign-off are complete; integration into `0.4` remains pending.
+- **Assessment:** The Conditionable architecture remains stateless, minimal, and coroutine-safe. The accepted changes correct one narrowed truthiness boundary, one split-manifest dependency, and one independently discovered Testbench worker-state leak at their respective owners without adding production machinery.
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
index fc4c8f322..607ecfc93 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
@@ -974,8 +974,8 @@ An exceptionally large shared work unit may receive its own linked detail plan w
This compact index routes the completed-work history that must be consulted with the full plan after compaction. Detailed history remains in the [companion ledger](2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md).
-- **Active package or work unit:** none; contracts work is complete and awaiting integration
-- **Ledger entries required for the active work:** none
+- **Active package or work unit:** `conditionable`
+- **Ledger entries required for the active work:** `Restore Conditionable proxy truthiness`
- **Pending revalidation carried into the active work:** none
Update these three lines when a package starts, completes, or gains a cross-package dependency. Name exact work-unit headings or shared finding IDs from the companion ledger; never use “see recent entries” or require a full-ledger reread.
@@ -990,6 +990,7 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `view-01` | `view` | `contracts`, `foundation`; later full `view` and `foundation` audits | `Harden framework contracts and request-scoped state`; shared finding `view-01` |
| `filesystem-01` | `filesystem` | `contracts`; later full `filesystem` audit | `Harden framework contracts and request-scoped state`; shared finding `filesystem-01` |
| `queue-01` | `queue` | `contracts`; later full `queue` audit | `Harden framework contracts and request-scoped state`; shared finding `queue-01` |
+| `testbench-01` | `testbench` | `foundation`; later full `testbench` and `foundation` audits | `Restore Conditionable proxy truthiness`; shared finding `testbench-01` |
## Package checklist
From 39102d059aaaf822c49d0b15b67fc86ea615fd13 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:43:30 +0000
Subject: [PATCH 09/40] Stabilize remote manifest restoration coverage
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.
---
tests/Testbench/Foundation/Process/RemoteCommandTest.php | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/tests/Testbench/Foundation/Process/RemoteCommandTest.php b/tests/Testbench/Foundation/Process/RemoteCommandTest.php
index ad05f633c..1de13b86e 100644
--- a/tests/Testbench/Foundation/Process/RemoteCommandTest.php
+++ b/tests/Testbench/Foundation/Process/RemoteCommandTest.php
@@ -97,7 +97,13 @@ public function itRestoresThePackageManifestAfterRemoteCommands(): void
remote('about --json')->mustRun();
$this->assertFileExists($path);
- $this->assertNotSame($contents, $files->get($path));
+
+ // Exercise restoration deterministically even when the child's
+ // rebuilt manifest matches the captured baseline byte-for-byte.
+ $probe = " true];\n";
+ $files->replace($path, $probe);
+
+ $this->assertSame($probe, $files->get($path));
});
}
From 139b61b9efdb260ae98530d316183363337f764d Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:43:37 +0000
Subject: [PATCH 10/40] Harden Macroable closure dispatch
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.
---
src/macroable/src/Traits/Macroable.php | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/src/macroable/src/Traits/Macroable.php b/src/macroable/src/Traits/Macroable.php
index c0c47a6a4..dcf65de1c 100644
--- a/src/macroable/src/Traits/Macroable.php
+++ b/src/macroable/src/Traits/Macroable.php
@@ -9,6 +9,8 @@
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
+use RuntimeException;
+use Throwable;
trait Macroable
{
@@ -52,7 +54,7 @@ public static function mixin(object $mixin, bool $replace = true): void
}
/**
- * Checks if macro is registered.
+ * Check if macro is registered.
*/
public static function hasMacro(string $name): bool
{
@@ -88,7 +90,12 @@ public static function __callStatic(string $method, array $parameters): mixed
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
- $macro = $macro->bindTo(null, static::class);
+ try {
+ // PHP warns when a valid first-class callable cannot be rebound; inspect the nullable result instead.
+ $macro = @$macro->bindTo(null, static::class) ?? $macro;
+ } catch (Throwable) {
+ // Keep the original first-class callable when PHP does not permit rebinding it.
+ }
}
return $macro(...$parameters);
@@ -112,7 +119,16 @@ public function __call(string $method, array $parameters): mixed
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
- $macro = $macro->bindTo($this, static::class);
+ 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.
+ }
+ }
}
return $macro(...$parameters);
From 010bd429bb9760c8f22eb6c065dbc35b9e0816ea Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:43:47 +0000
Subject: [PATCH 11/40] Expand Macroable callable regression coverage
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.
---
tests/Support/SupportMacroableTest.php | 132 ++++++++++++++++++++++---
1 file changed, 120 insertions(+), 12 deletions(-)
diff --git a/tests/Support/SupportMacroableTest.php b/tests/Support/SupportMacroableTest.php
index cb3573fe8..d372ba0c4 100644
--- a/tests/Support/SupportMacroableTest.php
+++ b/tests/Support/SupportMacroableTest.php
@@ -5,6 +5,7 @@
namespace Hypervel\Tests\Support;
use BadMethodCallException;
+use Closure;
use Hypervel\Support\Traits\Macroable;
use Hypervel\Tests\TestCase;
@@ -19,12 +20,20 @@ protected function setUp(): void
$this->macroable = $this->createObjectForTrait();
}
+ protected function tearDown(): void
+ {
+ EmptyMacroable::flushMacros();
+ TestMacroable::flushMacros();
+
+ parent::tearDown();
+ }
+
private function createObjectForTrait(): EmptyMacroable
{
return new EmptyMacroable;
}
- public function testRegisterMacro()
+ public function testRegisterMacro(): void
{
$macroable = $this->macroable;
$macroable::macro(__CLASS__, function () {
@@ -33,7 +42,7 @@ public function testRegisterMacro()
$this->assertSame('Taylor', $macroable::{__CLASS__}());
}
- public function testHasMacro()
+ public function testHasMacro(): void
{
$macroable = $this->macroable;
$macroable::macro('foo', function () {
@@ -43,7 +52,7 @@ public function testHasMacro()
$this->assertFalse($macroable::hasMacro('bar'));
}
- public function testRegisterMacroAndCallWithoutStatic()
+ public function testRegisterMacroAndCallWithoutStatic(): void
{
$macroable = $this->macroable;
$macroable::macro(__CLASS__, function () {
@@ -52,7 +61,7 @@ public function testRegisterMacroAndCallWithoutStatic()
$this->assertSame('Taylor', $macroable->{__CLASS__}());
}
- public function testWhenCallingMacroClosureIsBoundToObject()
+ public function testWhenCallingMacroClosureIsBoundToObject(): void
{
TestMacroable::macro('tryInstance', function () {
return $this->protectedVariable;
@@ -69,14 +78,14 @@ public function testWhenCallingMacroClosureIsBoundToObject()
$this->assertSame('static', $result);
}
- public function testClassBasedMacros()
+ public function testClassBasedMacros(): void
{
TestMacroable::mixin(new TestMixin);
$instance = new TestMacroable;
$this->assertSame('instance-Adam', $instance->methodOne('Adam'));
}
- public function testClassBasedMacrosNoReplace()
+ public function testClassBasedMacrosNoReplace(): void
{
TestMacroable::macro('methodThree', function () {
return 'bar';
@@ -89,7 +98,7 @@ public function testClassBasedMacrosNoReplace()
$this->assertSame('foo', $instance->methodThree());
}
- public function testFlushMacros()
+ public function testFlushMacros(): void
{
TestMacroable::macro('flushMethod', function () {
return 'flushMethod';
@@ -106,7 +115,7 @@ public function testFlushMacros()
$instance->flushMethod();
}
- public function testFlushMacrosStatic()
+ public function testFlushMacrosStatic(): void
{
TestMacroable::macro('flushMethod', function () {
return 'flushMethod';
@@ -123,7 +132,7 @@ public function testFlushMacrosStatic()
$instance::flushMethod();
}
- public function testMacroWithArguments()
+ public function testMacroWithArguments(): void
{
$this->macroable::macro('concatenate', function ($arg1, $arg2) {
return $arg1 . ' ' . $arg2;
@@ -133,7 +142,7 @@ public function testMacroWithArguments()
$this->assertSame('Hello World', $result);
}
- public function testMacroWithDefaultArguments()
+ public function testMacroWithDefaultArguments(): void
{
$this->macroable::macro('greet', function ($name = 'Guest') {
return 'Hello, ' . $name;
@@ -143,14 +152,14 @@ public function testMacroWithDefaultArguments()
$this->assertSame('Hello, Saleh', $this->macroable::greet('Saleh'));
}
- public function testCallingUndefinedMacroThrowsException()
+ public function testCallingUndefinedMacroThrowsException(): void
{
$this->expectException(BadMethodCallException::class);
$this->macroable::nonExistentMacro();
}
- public function testMethodConflictDoesNotThrowException()
+ public function testMethodConflictDoesNotThrowException(): void
{
$this->macroable::macro('existingMethod', function () {
return 'oldMethod';
@@ -163,6 +172,84 @@ public function testMethodConflictDoesNotThrowException()
$this->assertSame('newMethod', $this->macroable::existingMethod());
}
+
+ public function testStaticCallOfNonStaticClosure(): void
+ {
+ $this->macroable::macro('nonStaticClosure', function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $this->macroable::nonStaticClosure());
+ }
+
+ public function testNonStaticCallOfNonStaticClosure(): void
+ {
+ $this->macroable::macro('nonStaticClosure', function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $this->macroable->nonStaticClosure());
+ }
+
+ public function testStaticCallOfStaticClosure(): void
+ {
+ $this->macroable::macro('staticClosure', static function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $this->macroable::staticClosure());
+ }
+
+ public function testNonStaticCallOfStaticClosure(): void
+ {
+ $this->macroable::macro('staticClosure', static function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $this->macroable->staticClosure());
+ }
+
+ public function testNonStaticCallOfStaticClosureBindsClassScope(): void
+ {
+ TestMacroable::macro('staticClosure', static function () {
+ return static::getProtectedStatic();
+ });
+
+ $this->assertSame('static', (new TestMacroable)->staticClosure());
+ }
+
+ public function testClosureFromInternalFunctionRetainsItsCallableBinding(): void
+ {
+ $this->macroable::macro('length', Closure::fromCallable('strlen'));
+
+ $this->assertSame(6, $this->macroable::length('Taylor'));
+ $this->assertSame(6, $this->macroable->length('Taylor'));
+ }
+
+ public function testBoundInstanceMethodFirstClassCallableRetainsItsBinding(): void
+ {
+ $callable = new TestMacroCallable;
+ $this->macroable::macro('instanceCallable', $callable->instanceMethod(...));
+
+ $this->assertSame('instance-Taylor', $this->macroable::instanceCallable('Taylor'));
+ $this->assertSame('instance-Taylor', $this->macroable->instanceCallable('Taylor'));
+ }
+
+ public function testStaticMethodFirstClassCallableRetainsItsBinding(): void
+ {
+ $this->macroable::macro('staticCallable', TestMacroCallable::staticMethod(...));
+
+ $this->assertSame('static-Taylor', $this->macroable::staticCallable('Taylor'));
+ $this->assertSame('static-Taylor', $this->macroable->staticCallable('Taylor'));
+ }
+
+ public function testInvokableObjectCanBeCalledStaticallyAndNonStatically(): void
+ {
+ $this->macroable::macro('invokable', new TestInvokableMacro);
+
+ $this->assertSame('invokable-Taylor', $this->macroable::invokable('Taylor'));
+ $this->assertSame('invokable-Taylor', $this->macroable->invokable('Taylor'));
+ }
}
class EmptyMacroable
@@ -205,3 +292,24 @@ protected function methodThree()
};
}
}
+
+class TestMacroCallable
+{
+ public function instanceMethod(string $value): string
+ {
+ return 'instance-' . $value;
+ }
+
+ public static function staticMethod(string $value): string
+ {
+ return 'static-' . $value;
+ }
+}
+
+class TestInvokableMacro
+{
+ public function __invoke(string $value): string
+ {
+ return 'invokable-' . $value;
+ }
+}
From 7efb9dc9ed4c15aae0f9b1e1c526faab22be0044 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:43:57 +0000
Subject: [PATCH 12/40] Complete existing framework macro resets
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.
---
src/database/src/Eloquent/Factories/Factory.php | 1 +
src/http/src/Client/Response.php | 1 +
src/http/src/Resources/Json/JsonResource.php | 1 +
3 files changed, 3 insertions(+)
diff --git a/src/database/src/Eloquent/Factories/Factory.php b/src/database/src/Eloquent/Factories/Factory.php
index cec5fb487..aa5e55a23 100644
--- a/src/database/src/Eloquent/Factories/Factory.php
+++ b/src/database/src/Eloquent/Factories/Factory.php
@@ -997,6 +997,7 @@ public static function flushState(): void
static::$namespace = 'Database\Factories\\';
static::$expandRelationshipsByDefault = true;
static::$cachedModelAttributes = [];
+ static::flushMacros();
}
/**
diff --git a/src/http/src/Client/Response.php b/src/http/src/Client/Response.php
index 58774e19c..d298195e9 100644
--- a/src/http/src/Client/Response.php
+++ b/src/http/src/Client/Response.php
@@ -543,6 +543,7 @@ public function offsetUnset($offset): void
public static function flushState(): void
{
self::$defaultJsonDecodingFlags = 0;
+ static::flushMacros();
}
/**
diff --git a/src/http/src/Resources/Json/JsonResource.php b/src/http/src/Resources/Json/JsonResource.php
index 0a05a58df..02109aa9d 100644
--- a/src/http/src/Resources/Json/JsonResource.php
+++ b/src/http/src/Resources/Json/JsonResource.php
@@ -271,5 +271,6 @@ public static function flushState(): void
{
static::$wrap = 'data';
static::$forceWrapping = false;
+ static::flushMacros();
}
}
From 8bffaff671969b0e211835a2f59af1a0094be8e4 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:44:05 +0000
Subject: [PATCH 13/40] Add static reset hooks to HTTP macro surfaces
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.
---
src/http/src/Client/Factory.php | 8 ++++++++
src/http/src/Client/PendingRequest.php | 8 ++++++++
src/http/src/JsonResponse.php | 8 ++++++++
src/http/src/RedirectResponse.php | 8 ++++++++
src/http/src/Response.php | 8 ++++++++
src/http/src/UploadedFile.php | 8 ++++++++
6 files changed, 48 insertions(+)
diff --git a/src/http/src/Client/Factory.php b/src/http/src/Client/Factory.php
index c956c77d9..b464415bd 100644
--- a/src/http/src/Client/Factory.php
+++ b/src/http/src/Client/Factory.php
@@ -656,6 +656,14 @@ protected function validateConnectionConfig(array $config): void
ReservedOptions::reject($config, true, 'registered connection configuration');
}
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
+
/**
* Execute a method against a new pending request instance.
*/
diff --git a/src/http/src/Client/PendingRequest.php b/src/http/src/Client/PendingRequest.php
index 6224668e4..7fd5f0dbf 100644
--- a/src/http/src/Client/PendingRequest.php
+++ b/src/http/src/Client/PendingRequest.php
@@ -1854,4 +1854,12 @@ protected function validateRequestOptions(array $options, string $source): void
{
ReservedOptions::reject($options, false, $source);
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
}
diff --git a/src/http/src/JsonResponse.php b/src/http/src/JsonResponse.php
index cdb1064ea..33b527b1b 100755
--- a/src/http/src/JsonResponse.php
+++ b/src/http/src/JsonResponse.php
@@ -115,4 +115,12 @@ public function hasEncodingOption(int $option): bool
{
return (bool) ($this->encodingOptions & $option);
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
}
diff --git a/src/http/src/RedirectResponse.php b/src/http/src/RedirectResponse.php
index e7f02df11..0ac07b90d 100755
--- a/src/http/src/RedirectResponse.php
+++ b/src/http/src/RedirectResponse.php
@@ -219,6 +219,14 @@ public function setSession(SessionStore $session): static
return $this;
}
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
+
/**
* Dynamically bind flash data in the session.
*
diff --git a/src/http/src/Response.php b/src/http/src/Response.php
index d4a764fe5..f58c49f99 100755
--- a/src/http/src/Response.php
+++ b/src/http/src/Response.php
@@ -324,4 +324,12 @@ public function send(bool $flush = true): static
{
throw new RuntimeException('Response::send() is not supported in Hypervel. Responses are emitted through Swoole\'s response API.');
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
}
diff --git a/src/http/src/UploadedFile.php b/src/http/src/UploadedFile.php
index cc932cb54..0997d7c59 100644
--- a/src/http/src/UploadedFile.php
+++ b/src/http/src/UploadedFile.php
@@ -129,4 +129,12 @@ protected function parseOptions(array|string $options): array
return $options;
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
}
From 4b728807cd2d1a7a8ad05c6096257dc7b6040d6b Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:44:12 +0000
Subject: [PATCH 14/40] Reset mutable Request state between tests
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.
---
src/http/src/Request.php | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/http/src/Request.php b/src/http/src/Request.php
index 1e68c65bb..8953219f5 100644
--- a/src/http/src/Request.php
+++ b/src/http/src/Request.php
@@ -1180,6 +1180,20 @@ public function offsetUnset(mixed $offset): void
$this->getInputSource()->remove($offset);
}
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::$formats = null;
+ static::$httpMethodParameterOverride = false;
+ static::$allowedHttpMethodOverride = null;
+
+ // Symfony keeps the factory slot private, so reset it through the inherited setter.
+ static::setFactory(null);
+ static::flushMacros();
+ }
+
/**
* Check if an input element is set on the request.
*/
From 51eaa8af5e6cc5781b93897257f4ad1c217b799c Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:44:46 +0000
Subject: [PATCH 15/40] Add macro cleanup to NotificationFake
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.
---
src/support/src/Testing/Fakes/NotificationFake.php | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/support/src/Testing/Fakes/NotificationFake.php b/src/support/src/Testing/Fakes/NotificationFake.php
index a1326372d..814f3d1b4 100644
--- a/src/support/src/Testing/Fakes/NotificationFake.php
+++ b/src/support/src/Testing/Fakes/NotificationFake.php
@@ -334,4 +334,12 @@ public function sentNotifications(): array
{
return $this->notifications;
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushMacros();
+ }
}
From be040f48abb46130d84ac08518e3e47e5849592b Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:44:59 +0000
Subject: [PATCH 16/40] Register framework macro resets centrally
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.
---
src/testing/src/PHPUnit/AfterEachTestSubscriber.php | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/testing/src/PHPUnit/AfterEachTestSubscriber.php b/src/testing/src/PHPUnit/AfterEachTestSubscriber.php
index c0ce773a4..4f5d3730f 100644
--- a/src/testing/src/PHPUnit/AfterEachTestSubscriber.php
+++ b/src/testing/src/PHPUnit/AfterEachTestSubscriber.php
@@ -162,15 +162,22 @@ protected function flushFrameworkState(): void
\Hypervel\Foundation\Support\Providers\RouteServiceProvider::flushState();
\Hypervel\Foundation\Vite::flush();
\Hypervel\Foundation\WorkerCachedMaintenanceMode::flushCache();
+ \Hypervel\Http\Client\Factory::flushState();
+ \Hypervel\Http\Client\PendingRequest::flushState();
\Hypervel\Http\Client\Request::flushState();
\Hypervel\Http\Client\RequestException::flushState();
\Hypervel\Http\Client\Response::flushState();
\Hypervel\Http\Client\ResponseSequence::flushState();
+ \Hypervel\Http\JsonResponse::flushState();
\Hypervel\Http\Middleware\HandleCors::flushState();
\Hypervel\Http\Middleware\TrustHosts::flushState();
\Hypervel\Http\Middleware\TrustProxies::flushState();
+ \Hypervel\Http\RedirectResponse::flushState();
+ \Hypervel\Http\Request::flushState();
\Hypervel\Http\Resources\Json\JsonResource::flushState();
\Hypervel\Http\Resources\JsonApi\JsonApiResource::flushState();
+ \Hypervel\Http\Response::flushState();
+ \Hypervel\Http\UploadedFile::flushState();
\Hypervel\Jwt\ClaimFactory::flushState();
\Hypervel\Jwt\JwtGuard::flushState();
\Hypervel\Log\Context\Repository::flushState();
@@ -238,6 +245,7 @@ protected function flushFrameworkState(): void
\Hypervel\Support\Str::flushState();
\Hypervel\Support\StrCache::flushState();
\Hypervel\Support\Stringable::flushState();
+ \Hypervel\Support\Testing\Fakes\NotificationFake::flushState();
\Hypervel\Support\Uri::flushState();
\Hypervel\Testing\Fluent\AssertableJson::flushState();
\Hypervel\Testing\ParallelRunner::flushState();
From 9001d614e4b9e1d5272b5c4b272aae884c96c48b Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:45:20 +0000
Subject: [PATCH 17/40] Cover centralized macro and Request cleanup
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.
---
.../PHPUnit/AfterEachTestSubscriberTest.php | 87 +++++++++++++++++++
1 file changed, 87 insertions(+)
diff --git a/tests/Testing/PHPUnit/AfterEachTestSubscriberTest.php b/tests/Testing/PHPUnit/AfterEachTestSubscriberTest.php
index c3b2ca591..e9515889e 100644
--- a/tests/Testing/PHPUnit/AfterEachTestSubscriberTest.php
+++ b/tests/Testing/PHPUnit/AfterEachTestSubscriberTest.php
@@ -5,7 +5,19 @@
namespace Hypervel\Tests\Testing\PHPUnit;
use Hypervel\Contracts\Pool\ConnectionInterface;
+use Hypervel\Database\Eloquent\Factories\Factory as EloquentFactory;
use Hypervel\Foundation\Testing\DatabaseConnectionResolver;
+use Hypervel\Http\Client\Factory as HttpFactory;
+use Hypervel\Http\Client\PendingRequest;
+use Hypervel\Http\Client\Response as HttpClientResponse;
+use Hypervel\Http\JsonResponse;
+use Hypervel\Http\RedirectResponse;
+use Hypervel\Http\Request;
+use Hypervel\Http\Resources\Json\JsonResource;
+use Hypervel\Http\Resources\JsonApi\JsonApiResource;
+use Hypervel\Http\Response as HttpResponse;
+use Hypervel\Http\UploadedFile;
+use Hypervel\Support\Testing\Fakes\NotificationFake;
use Hypervel\Testing\PHPUnit\AfterEachTestCleanup;
use Hypervel\Testing\PHPUnit\AfterEachTestSubscriber;
use Hypervel\Tests\TestCase;
@@ -25,6 +37,81 @@ protected function tearDown(): void
parent::tearDown();
}
+ public function testFrameworkCleanupFlushesEveryMacroableRegistry(): void
+ {
+ $classes = [
+ EloquentFactory::class,
+ HttpFactory::class,
+ PendingRequest::class,
+ HttpClientResponse::class,
+ JsonResponse::class,
+ RedirectResponse::class,
+ Request::class,
+ JsonResource::class,
+ JsonApiResource::class,
+ HttpResponse::class,
+ UploadedFile::class,
+ NotificationFake::class,
+ ];
+ $macro = 'testingStaticStateProbe';
+
+ foreach ($classes as $class) {
+ $class::macro($macro, static fn (): string => 'ok');
+ $this->assertTrue($class::hasMacro($macro));
+ }
+
+ $subscriber = new class extends AfterEachTestSubscriber {
+ public function flushFrameworkStateForTest(): void
+ {
+ $this->flushFrameworkState();
+ }
+ };
+
+ try {
+ $subscriber->flushFrameworkStateForTest();
+
+ foreach ($classes as $class) {
+ $this->assertFalse($class::hasMacro($macro));
+ }
+ } finally {
+ foreach ($classes as $class) {
+ $class::flushMacros();
+ }
+ }
+ }
+
+ public function testFrameworkCleanupFlushesInheritedRequestStaticState(): void
+ {
+ $request = new Request;
+ $request->setFormat('testing', 'application/x-testing');
+ Request::enableHttpMethodParameterOverride();
+ Request::setAllowedHttpMethodOverride(['PATCH']);
+ Request::setFactory(static fn (): Request => new Request(attributes: ['from_factory' => true]));
+
+ $this->assertSame(['application/x-testing'], Request::getMimeTypes('testing'));
+ $this->assertTrue(Request::getHttpMethodParameterOverride());
+ $this->assertSame(['PATCH'], Request::getAllowedHttpMethodOverride());
+ $this->assertTrue(Request::create('/')->attributes->get('from_factory'));
+
+ $subscriber = new class extends AfterEachTestSubscriber {
+ public function flushFrameworkStateForTest(): void
+ {
+ $this->flushFrameworkState();
+ }
+ };
+
+ try {
+ $subscriber->flushFrameworkStateForTest();
+
+ $this->assertSame([], Request::getMimeTypes('testing'));
+ $this->assertFalse(Request::getHttpMethodParameterOverride());
+ $this->assertNull(Request::getAllowedHttpMethodOverride());
+ $this->assertNull(Request::create('/')->attributes->get('from_factory'));
+ } finally {
+ Request::flushState();
+ }
+ }
+
public function testFlushStateAfterTestRunsCustomCallbacksBeforeFrameworkCleanup(): void
{
$subscriber = new class extends AfterEachTestSubscriber {
From 15655cede8f4d878795847617bb39dbad2476341 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:45:38 +0000
Subject: [PATCH 18/40] Declare direct Macroable package dependencies
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.
---
src/cookie/composer.json | 3 ++-
src/jwt/composer.json | 1 +
src/log/composer.json | 1 +
src/notifications/composer.json | 3 ++-
4 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/cookie/composer.json b/src/cookie/composer.json
index 29710e3be..087caf03c 100644
--- a/src/cookie/composer.json
+++ b/src/cookie/composer.json
@@ -34,6 +34,7 @@
"hypervel/context": "^0.4",
"hypervel/contracts": "^0.4",
"hypervel/http": "^0.4",
+ "hypervel/macroable": "^0.4",
"hypervel/support": "^0.4"
},
"config": {
@@ -49,4 +50,4 @@
"dev-main": "0.4-dev"
}
}
-}
\ No newline at end of file
+}
diff --git a/src/jwt/composer.json b/src/jwt/composer.json
index cdd636e43..59dbc7332 100644
--- a/src/jwt/composer.json
+++ b/src/jwt/composer.json
@@ -35,6 +35,7 @@
"hypervel/context": "^0.4",
"hypervel/contracts": "^0.4",
"hypervel/http": "^0.4",
+ "hypervel/macroable": "^0.4",
"hypervel/support": "^0.4"
},
"autoload": {
diff --git a/src/log/composer.json b/src/log/composer.json
index a08d3ca11..5f5222286 100644
--- a/src/log/composer.json
+++ b/src/log/composer.json
@@ -40,6 +40,7 @@
"hypervel/config": "^0.4",
"hypervel/context": "^0.4",
"hypervel/contracts": "^0.4",
+ "hypervel/macroable": "^0.4",
"hypervel/queue": "^0.4",
"hypervel/support": "^0.4"
},
diff --git a/src/notifications/composer.json b/src/notifications/composer.json
index cc70b57ca..e70239382 100644
--- a/src/notifications/composer.json
+++ b/src/notifications/composer.json
@@ -44,6 +44,7 @@
"hypervel/contracts": "^0.4",
"hypervel/database": "^0.4",
"hypervel/filesystem": "^0.4",
+ "hypervel/macroable": "^0.4",
"hypervel/mail": "^0.4",
"hypervel/object-pool": "^0.4",
"hypervel/queue": "^0.4",
@@ -62,4 +63,4 @@
"dev-main": "0.4-dev"
}
}
-}
\ No newline at end of file
+}
From 01b8dd5c140d2c073833e425b640e7aba67d29f4 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:45:52 +0000
Subject: [PATCH 19/40] Clarify framework macro cleanup in tests
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.
---
src/boost/docs/collections.md | 2 +-
src/boost/docs/http-client.md | 2 +-
src/boost/docs/responses.md | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/boost/docs/collections.md b/src/boost/docs/collections.md
index d375345b1..21d06067c 100644
--- a/src/boost/docs/collections.md
+++ b/src/boost/docs/collections.md
@@ -64,7 +64,7 @@ $upper = $collection->toUpper();
Typically, you should declare collection macros in the `boot` method of a [service provider](/docs/{{version}}/providers).
-If you register a macro inside a test, flush the macro state before the test finishes. Macros are stored globally for the life of the PHP process.
+Macros are stored globally for the life of the PHP process. Hypervel automatically flushes framework-owned collection macros after every test. If your application or package defines its own macroable class, add it to your [test-state cleanup](/docs/{{version}}/testing#macro-state).
#### Macro Arguments
diff --git a/src/boost/docs/http-client.md b/src/boost/docs/http-client.md
index 66c7e962a..3385d09d4 100644
--- a/src/boost/docs/http-client.md
+++ b/src/boost/docs/http-client.md
@@ -893,7 +893,7 @@ Once your macro has been configured, you may invoke it from anywhere in your app
$response = Http::github()->get('/');
```
-Macros are stored globally for the life of the PHP process. If you register a macro inside a test, flush the macro state before the test finishes.
+Macros are stored globally for the life of the PHP process. Hypervel automatically flushes framework-owned HTTP client macros after every test. If your application or package defines its own macroable class, add it to your [test-state cleanup](/docs/{{version}}/testing#macro-state).
## Testing
diff --git a/src/boost/docs/responses.md b/src/boost/docs/responses.md
index 2b894bb00..b39d20199 100644
--- a/src/boost/docs/responses.md
+++ b/src/boost/docs/responses.md
@@ -1121,4 +1121,4 @@ return response()->caps('foo');
```
> [!NOTE]
-> Macros register on a static property and persist for the life of the Swoole worker. If you register a macro inside a test, call `Hypervel\Routing\ResponseFactory::flushState()` or `Response::flushMacros()` before the next test runs so the macro doesn't leak into other tests.
+> Macros register on a static property and persist for the life of the Swoole worker. Hypervel automatically flushes framework-owned response macros after every test. If your application or package defines its own macroable class, add it to your [test-state cleanup](/docs/{{version}}/testing#macro-state).
From 9a9844561211f3bc55b14ab5667bb5f41de1b47b Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:46:06 +0000
Subject: [PATCH 20/40] Record the completed Macroable audit
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.
---
...-coroutine-state-lifecycle-audit-ledger.md | 38 ++++++++++++++++++-
...amework-coroutine-state-lifecycle-audit.md | 25 ++++++------
2 files changed, 49 insertions(+), 14 deletions(-)
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
index 80ca31c29..8376e2955 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -149,7 +149,41 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Important rejected concerns:** Do not add coroutine context, scoped bindings, cloning, locks, or cleanup machinery to a per-invocation proxy with no hidden shared state. Keep the Eloquent integration test because it verifies the split trait through a real consumer and its Capsule global is reset centrally. Do not consolidate the two upstream-mirrored test locations or add a README absent from this family of Laravel and Hypervel micro-packages.
- **Cross-package implications:** `conditionable-03` corrects the `log` split manifest; the later full `log` audit must retain and revalidate that direct dependency. Future `macroable` and `collections` audits should repeat the same consumer-manifest sweep rather than assuming either a direct-edge or umbrella dependency convention. `testbench-01` belongs to the shared Testbench lifecycle and is also revalidated by the Foundation route-cache subprocess tests; the later full `testbench` and `foundation` audits must retain that ownership boundary.
- **Implementation:** The higher-order proxy now accepts unconstrained condition values and stores their boolean normalization in its existing typed property. The log split manifest now declares its direct Conditionable dependency. Testbench snapshots the resolved package-manifest cache after application setup and restores its exact existence and contents through the existing exhaustive pre-destruction callback lifecycle, without rewriting an unchanged file. The route-cache precondition guard now reports unexpected raw package providers directly.
-- **Regression tests:** Conditionable coverage now exercises direct, higher-order property, and higher-order method truthiness with non-boolean values, plus the symmetric `unless` path. The remote-command regression forces a Testbench CLI child to rebuild the shared manifest, proves the write happened, and verifies teardown restored the captured baseline. The previously failing remote-command-before-route-cache sequence passes deterministically.
+- **Regression tests:** Conditionable coverage now exercises direct, higher-order property, and higher-order method truthiness with non-boolean values, plus the symmetric `unless` path. The remote-command regression forces a Testbench CLI child to rebuild a deleted manifest, then applies a deterministic probe mutation and verifies teardown restores the exact captured baseline. The previously failing remote-command-before-route-cache sequence passes deterministically.
- **Performance and complexity:** The Conditionable correction adds one allocation-free boolean normalization per higher-order proxy condition. It adds no lookup, lock, yield, retained state, compatibility path, or abstraction. The manifest dependency and test signatures have no runtime cost. Manifest preservation adds small filesystem reads only to Testbench test setup/teardown and performs no write when the cache is unchanged; production and application hot paths are unaffected.
-- **Validation and review:** Focused Conditionable, remote-command, route-cache, and cross-test ordering regressions pass. The log split manifest validates strictly, `git diff --check` passes, and the full `composer fix` gate is green. Pre-implementation second-opinion consensus, owner approvals, fresh self-review, and final code-review sign-off are complete; integration into `0.4` remains pending.
+- **Validation and review:** Focused Conditionable, remote-command, route-cache, and cross-test ordering regressions pass. The log split manifest validates strictly, `git diff --check` passes, and the full `composer fix` gate is green. Pre-implementation second-opinion consensus, owner approvals, fresh self-review, final code-review sign-off, and coherent commits are complete.
- **Assessment:** The Conditionable architecture remains stateless, minimal, and coroutine-safe. The accepted changes correct one narrowed truthiness boundary, one split-manifest dependency, and one independently discovered Testbench worker-state leak at their respective owners without adding production machinery.
+
+### Complete Macroable callable and test-state handling
+
+- **Architecture and inspected risk surfaces:** Macroable is a leaf trait package whose only state is the per-using-class static macro registry. The audit covered both package files, current Laravel source and the originating static-closure fix, every framework trait consumer and reset hook, all consuming split-package manifests, package tests, public macro documentation, PHP closure-binding behavior with and without a throwing error handler, and the complete repository caller surface. Macro registration remains intentionally boot-time worker state; valid runtime reads require no lock or coroutine-local copy.
+
+| ID | Category | Severity | Confidence | Failure and owning boundary | Final decision |
+|---|---|---|---|---|---|
+| `macroable-01` | Defect | Minor | High | Instance dispatch fatals for static closures, and both dispatch paths fail valid first-class callable closures because PHP forbids rebinding them | Port Laravel's current static-closure behavior and retain the original callable when every legal rebind fails; cover all closure/call combinations, all three unbindable callable shapes, and invokable objects |
+| `macroable-02` | Defect | Minor | High | Eleven framework Macroable surfaces omit macro cleanup from their existing reset or have no registered reset, allowing one test's macros to survive into later tests | Complete the three existing reset methods, add eight standard static `flushState()` hooks, register them centrally, and exercise the real framework reset including inherited JsonApi macro storage |
+| `macroable-03` | Defect | Minor | High | Cookie, JWT, log, and notifications directly use Macroable but rely on a transitive dependency | Add the direct sorted `hypervel/macroable` requirement to all four split manifests |
+| `macroable-04` | Defect | Minor | High | Collections, HTTP-client, and response documentation contradict centralized framework test cleanup by instructing developers to clear framework macros manually | Document automatic framework cleanup and direct application/package-owned macro state to the existing test-state cleanup mechanism |
+| `macroable-05` | Defect | Minor | High | Macroable's test fixtures retain registered macros across methods | Flush both fixture registries during test teardown |
+| `macroable-06` | Improvement | Improvement | High | Existing Macroable test methods omit the repository-required return type | Add `: void` while merging current upstream coverage |
+| `http-01` | Defect | Minor | High | The new Request reset clears macros but leaves four inherited mutable Symfony configuration surfaces alive between framework tests | Reset only the inherited MIME-format, method-override, allowed-method, and request-factory state that Hypervel still uses |
+
+- **Important rejected concerns:** Do not make macros coroutine-local, lock immutable runtime reads, clone or cache callables, or add a manager/provider/reset registry. Current Laravel 13.x still fails closures produced from internal functions and first-class methods; Hypervel intentionally supports its existing callable contract, and the local source comment explains the PHP invariant without narrating upstream history. Focused PHPUnit coverage disproved the original assumption that unsupported bindings are silent outside a booted application. The checked native `bindTo()` boundaries therefore suppress their expected warnings and immediately inspect the nullable result; per-call reflection costs more, registration-time classification adds disproportionate parallel state, and warning-tolerant tests would hide real standalone behavior.
+- **Cross-package implications:** The later full audits of `cookie`, `jwt`, `log`, and `notifications` must retain and revalidate their direct Macroable dependencies. The later `database`, `support`, and `testing` audits must retain the completed static reset boundaries and centralized subscriber ownership. The later `http` audit must retain the completed inherited-state reset under shared finding `http-01` and separately judge whether the inherited public mutators need explicit boot-lifetime guidance.
+- **Completeness method:** Consumer/reset counts come from a whole-source trait-composition sweep, and documentation contradiction counts come from a whole-docs pattern sweep rather than an expected-file enumeration.
+- **Approved implementation boundary:** Keep the two magic dispatch paths explicit because their legal binding sequences differ. Add no helper or dynamic abstraction. Existing non-Closure callable dispatch is unchanged; ordinary closure macros still perform one binding operation. Suppressing the checked native warning adds a measured approximately 16 nanoseconds to closure-macro invocation; the owner approved that negligible opt-in cost after reviewing the more expensive or more complex alternatives. The additional binding attempt is confined to previously failing closure shapes. Static cleanup runs only between tests. Owner approval covers the callable behavior, eight additive public testing hooks, cross-package manifest corrections, and test-signature improvement.
+- **Implementation:** Macro dispatch now applies the legal instance/class and static/class binding sequence, suppresses only the native warnings whose nullable results are checked immediately, and retains already-valid first-class callables when PHP forbids rebinding. Three existing static resets now include macros; eight framework classes gained the standard static reset hook and central subscriber registration. Request's reset also restores the four mutable inherited Symfony configuration surfaces that remain live in Hypervel. Cookie, JWT, log, and notifications declare their direct Macroable dependency. Collections, HTTP-client, and response documentation now matches centralized framework cleanup. Macroable's own fixtures release their registries, and the earlier Testbench manifest regression now forces its restoration path with deterministic valid-PHP probe content instead of assuming a child rebuild must differ from the baseline.
+- **Regression tests:** Current Laravel's four static/non-static closure/call combinations are covered alongside protected class-scope preservation, closures from internal functions, bound instance-method and static-method first-class callables, and invokable objects. A real-subscriber regression registers macros on every previously omitted framework surface, including the inherited JsonApi entrypoint, and proves one authoritative reset clears them. A companion Request regression mutates every inherited mutable configuration surface Hypervel still consumes and proves the same reset restores Symfony's defaults. The Testbench remote-command test separately proves child recreation of a deleted manifest and exact teardown restoration after a deterministic mutation.
+- **Performance and complexity:** Ordinary methods and non-Closure callables are unchanged. The checked warning suppression adds a measured approximately 16 nanoseconds only to closure-macro calls; owner approval is recorded above. The Laravel-shaped exception fallback preserves the faster normal-closure path; an explicit null-branch rewrite measured slower on normal calls and substantially slower on static-closure calls. Test cleanup, manifest edges, test signatures, and documentation add no production cost. No lock, context slot, dynamic registry, reflection, callable cache, or compatibility path was added.
+- **Validation and review:** All focused Macroable, framework-reset, database-factory, HTTP, notification, and Testbench process tests pass. The four split manifests validate strictly, stale macro-cleanup documentation sweeps are clean, `git diff --check` passes, and the complete `composer fix` gate is green. Pre-implementation second-opinion consensus, owner approvals, focused unexpected-finding reviews, a fresh full-diff self-review, final independent code-review sign-off, and owner pre-commit approval are complete.
+- **Assessment:** Macroable remains a small boot-time static capability with lock-free runtime reads. The result is Laravel-compatible at the public API, supports the full existing callable contract, makes framework test cleanup exhaustive, and confines its only measured runtime cost to a negligible checked native boundary on opt-in closure macros without speculative machinery.
+
+### Shared finding `http-01`: inherited Request static-state reset
+
+- **Owner:** `http`, specifically mutable Symfony Request configuration inherited and consumed by `Hypervel\Http\Request`; `testing` owns the centralized framework reset invocation.
+- **Affected packages:** `macroable` introduced the Request reset while completing framework macro cleanup; `http` owns the underlying state and regression; `testing` invokes the reset.
+- **Failure:** Framework test teardown cleared Request macros but retained a custom MIME format, method-parameter override flag, allowed override list, and request factory for the rest of the PHPUnit worker. Later tests could therefore observe configuration established by an earlier test.
+- **Decision:** Restore only the four mutable inherited surfaces Hypervel still consumes to their installed Symfony defaults. Do not reset Symfony's trusted-proxy/host statics because Hypervel's complete trusted-request path uses coroutine-request instance state, and do not reset immutable response/status metadata or caches.
+- **Implementation and cleanup:** `Request::flushState()` now resets the lazy format table and both method-override settings directly, resets Symfony's private request-factory slot through its public setter, and then clears macros. No production path, inherited public mutator, trusted-request implementation, or immutable metadata changes.
+- **Validation:** A focused real-subscriber regression proves all four inherited values and their visible custom behavior disappear after framework cleanup. Focused HTTP/server coverage and the complete `composer fix` gate pass; final review sign-off and owner pre-commit approval are complete. The later full `http` and `testing` audits must retain and revalidate this boundary.
+- **Revalidation:** Full `http` and `testing` audits remain pending.
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
index 607ecfc93..bf79c521f 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
@@ -857,11 +857,11 @@ A package checkbox means the entire cycle below is finished, not merely that fil
### Branch and authorization model
-This audit targets the repository's `0.4` integration branch. Each completed package or cross-package work unit uses a dedicated branch from the latest owner-approved `0.4` state and is reviewed as its own pull request. Name the branch for the work unit, such as `audit/contracts-coroutine-state-lifecycle`. Do not perform audit work directly on `0.4`.
+This audit targets the repository's `0.4` integration branch. Start an audit branch from the latest owner-approved `0.4` state and do not perform audit work directly on `0.4`. The owner decides pull-request boundaries: one audit branch may accumulate one or several completed packages or cross-package work units before the owner determines that it contains enough coherent work for a pull request.
-The owner has explicitly authorized coherent commits and pushes after each completed package or cross-package work unit for this audit. That authorization does not include opening, merging, or closing pull requests, and it does not automatically carry into a future audit run. Do not begin the next work unit until the preceding pull request has been integrated into `0.4` and the next branch includes that integrated state, unless the owner explicitly authorizes a stacked branch.
+The owner has explicitly authorized coherent commits after each completed package or cross-package work unit for this audit, following the owner pre-commit checkpoint below. Continue the next package on the same active audit branch after those commits unless the owner directs otherwise. Do not push, open, merge, or close a pull request until the owner explicitly requests it, and do not infer that a completed package should become its own pull request.
-A resumed session verifies the active branch, the last pushed package, and whether newer base-branch changes affect audited assumptions before editing. Never infer a different target branch from a hosting service's default branch.
+A resumed session verifies the active branch, the last completed package commits, and whether an owner-requested base update affects audited assumptions before editing. Never infer a different target branch from a hosting service's default branch.
### 1. Audit
@@ -936,7 +936,7 @@ Request an independent review of the complete diff and validation. Continue unti
### 9. Prepare final audit records
-Update the companion-ledger work-unit block with implemented changes, cross-package revalidation, tests/gates, and review sign-off. Its heading must match the pull-request title so the corresponding repository history is easy to locate without duplicating branch, pull-request, or commit references. Prepare the routing index, cross-package dependency index, and package-checklist changes in this plan. Remove wording that describes abandoned designs.
+Update the companion-ledger work-unit block with implemented changes, cross-package revalidation, tests/gates, and review sign-off. Give it a concise work-unit heading; multiple ledger work units may later be included in one owner-selected pull request. Prepare the routing index, cross-package dependency index, and package-checklist changes in this plan. Remove wording that describes abandoned designs and do not duplicate branch, pull-request, or commit references in the audit documents.
### 10. Owner pre-commit checkpoint
@@ -949,11 +949,11 @@ After every code change is complete, all gates are green, the fresh self-review
Do not create any source, test, documentation, ledger, or bookkeeping commit before that explicit approval. If the owner requests changes, implement them, rerun proportionate validation and review, update the summary, notify again, and wait for approval.
-### 11. Commit and push
+### 11. Commit
Commit source, tests, and documentation in as many coherent commits as useful, with detailed bodies. Make one final audit-bookkeeping commit containing the ledger entry and this plan's checklist/index updates. Do not duplicate branch, pull-request, commit, or merge references in the audit documents; repository history already owns that information. A clean audit with no executable or documentation correction has only this bookkeeping commit.
-Push the complete commit set only after the package is green and signed off. The checked box becomes authoritative when that push succeeds. If the push fails, do not begin the next package; repair the push or revert the unpushed completion bookkeeping so the branch does not claim externally completed work.
+The checked box becomes authoritative when the final bookkeeping commit succeeds. Continue on the same audit branch after the owner approves the next work unit. Push only when the owner requests it, normally when the owner decides the accumulated work is ready for a pull request.
## Cross-package work units
@@ -974,8 +974,8 @@ An exceptionally large shared work unit may receive its own linked detail plan w
This compact index routes the completed-work history that must be consulted with the full plan after compaction. Detailed history remains in the [companion ledger](2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md).
-- **Active package or work unit:** `conditionable`
-- **Ledger entries required for the active work:** `Restore Conditionable proxy truthiness`
+- **Active package or work unit:** `collections`
+- **Ledger entries required for the active work:** `Restore Conditionable proxy truthiness`; `Complete Macroable callable and test-state handling`
- **Pending revalidation carried into the active work:** none
Update these three lines when a package starts, completes, or gains a cross-package dependency. Name exact work-unit headings or shared finding IDs from the companion ledger; never use “see recent entries” or require a full-ledger reread.
@@ -991,6 +991,7 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `filesystem-01` | `filesystem` | `contracts`; later full `filesystem` audit | `Harden framework contracts and request-scoped state`; shared finding `filesystem-01` |
| `queue-01` | `queue` | `contracts`; later full `queue` audit | `Harden framework contracts and request-scoped state`; shared finding `queue-01` |
| `testbench-01` | `testbench` | `foundation`; later full `testbench` and `foundation` audits | `Restore Conditionable proxy truthiness`; shared finding `testbench-01` |
+| `http-01` | `http` | `macroable`, `testing`; later full `http` and `testing` audits | `Complete Macroable callable and test-state handling`; shared finding `http-01` |
## Package checklist
@@ -1014,8 +1015,8 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
### Core semantics and long-lived state
- [x] `contracts`
-- [ ] `conditionable`
-- [ ] `macroable`
+- [x] `conditionable`
+- [x] `macroable`
- [ ] `collections`
- [ ] `reflection`
- [ ] `config`
@@ -1107,7 +1108,7 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
### Before each package
-- confirm the previous work-unit pull request is integrated into `0.4`, then create the current work-unit branch from that latest integrated state unless the owner explicitly authorized stacking;
+- confirm the previous work unit is committed and the owner has authorized continuing on the active audit branch;
- set the active-package routing fields and list every exact ledger entry required for the work;
- read the current package's existing companion-ledger entry, if any, plus only the cross-referenced entries named by the routing/dependency indexes;
- check whether a completed lower-level package changed assumptions used here;
@@ -1129,7 +1130,7 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
- code review is signed off;
- every accepted hot-path regression, if any, received explicit owner approval before implementation;
- the owner reviewed the post-sign-off summary and explicitly approved committing;
-- commits were pushed;
+- the final bookkeeping commit succeeded;
- routing and dependency indexes reflect the next active work and every pending revalidation;
- any affected completed package was revalidated and its companion-ledger entry amended.
From 037fee8d54e80cf18413b87e48b4ca9d3fc19ee0 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:55:54 +0000
Subject: [PATCH 21/40] Correct Collections package metadata
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.
---
src/collections/composer.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/collections/composer.json b/src/collections/composer.json
index 4ee50c2b1..4f61b0bc6 100644
--- a/src/collections/composer.json
+++ b/src/collections/composer.json
@@ -35,12 +35,13 @@
"require": {
"php": "^8.4",
"hypervel/conditionable": "^0.4",
- "hypervel/macroable": "^0.4",
"hypervel/contracts": "^0.4",
- "symfony/polyfill-php86": "^1.36"
+ "hypervel/macroable": "^0.4",
+ "symfony/polyfill-php85": "^1.36"
},
"suggest": {
- "hypervel/http": "Required to convert collections to API resources (^0.4)."
+ "hypervel/http": "Required to convert collections to API resources (^0.4).",
+ "symfony/var-dumper": "Required to use the dump method (^7.4 || ^8.0)."
},
"config": {
"sort-packages": true
From 7d4aafae757c3aebe08a50451edc901672499be6 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:55:54 +0000
Subject: [PATCH 22/40] Improve Arr contracts and type inference
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.
---
src/collections/src/Arr.php | 162 ++++++++++++++++++++++++++++++++++--
types/Collections/Arr.php | 46 ++++++++++
2 files changed, 203 insertions(+), 5 deletions(-)
create mode 100644 types/Collections/Arr.php
diff --git a/src/collections/src/Arr.php b/src/collections/src/Arr.php
index 7cdaaa011..0b503805a 100644
--- a/src/collections/src/Arr.php
+++ b/src/collections/src/Arr.php
@@ -31,6 +31,20 @@ public static function accessible(mixed $value): bool
/**
* Determine whether the given value is arrayable.
+ *
+ * @return ($value is array
+ * ? true
+ * : ($value is Arrayable
+ * ? true
+ * : ($value is Traversable
+ * ? true
+ * : ($value is Jsonable
+ * ? true
+ * : ($value is JsonSerializable ? true : false)
+ * )
+ * )
+ * )
+ * )
*/
public static function arrayable(mixed $value): bool
{
@@ -109,6 +123,11 @@ public static function collapse(iterable $array): array
/**
* Cross join the given arrays, returning all possible permutations.
+ *
+ * @template TValue
+ *
+ * @param iterable ...$arrays
+ * @return array>
*/
public static function crossJoin(iterable ...$arrays): array
{
@@ -133,6 +152,12 @@ public static function crossJoin(iterable ...$arrays): array
/**
* Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @return array{TKey[], TValue[]}
*/
public static function divide(array $array): array
{
@@ -252,7 +277,15 @@ public static function first(iterable $array, ?callable $callback = null, mixed
return value($default);
}
- $array = static::from($array);
+ if (! is_array($array)) {
+ foreach ($array as $key => $value) {
+ if ($callback($value, $key)) {
+ return $value;
+ }
+ }
+
+ return value($default);
+ }
$key = array_find_key($array, $callback);
@@ -260,7 +293,7 @@ public static function first(iterable $array, ?callable $callback = null, mixed
}
/**
- * Return the last element in an array passing a given truth test.
+ * Return the last element in an iterable passing a given truth test.
*
* @template TKey
* @template TValue
@@ -273,6 +306,37 @@ public static function first(iterable $array, ?callable $callback = null, mixed
*/
public static function last(iterable $array, ?callable $callback = null, mixed $default = null): mixed
{
+ if (! is_array($array)) {
+ if (is_null($callback)) {
+ $found = false;
+ $last = null;
+
+ foreach ($array as $value) {
+ $found = true;
+ $last = $value;
+ }
+
+ return $found ? $last : value($default);
+ }
+
+ // Preserve the array path's reverse callback order without collapsing duplicate iterator keys.
+ $items = [];
+
+ foreach ($array as $key => $value) {
+ $items[] = [$key, $value];
+ }
+
+ for ($index = count($items) - 1; $index >= 0; --$index) {
+ [$key, $value] = $items[$index];
+
+ if ($callback($value, $key)) {
+ return $value;
+ }
+ }
+
+ return value($default);
+ }
+
if (is_null($callback)) {
return empty($array) ? value($default) : array_last($array);
}
@@ -517,7 +581,17 @@ public static function hasAny(mixed $array, array|string|int|float|null $keys):
*/
public static function every(iterable $array, callable $callback): bool
{
- return array_all($array, $callback);
+ if (is_array($array)) {
+ return array_all($array, $callback);
+ }
+
+ foreach ($array as $key => $value) {
+ if (! $callback($value, $key)) {
+ return false;
+ }
+ }
+
+ return true;
}
/**
@@ -525,7 +599,17 @@ public static function every(iterable $array, callable $callback): bool
*/
public static function some(iterable $array, callable $callback): bool
{
- return array_any($array, $callback);
+ if (is_array($array)) {
+ return array_any($array, $callback);
+ }
+
+ foreach ($array as $key => $value) {
+ if ($callback($value, $key)) {
+ return true;
+ }
+ }
+
+ return false;
}
/**
@@ -550,6 +634,8 @@ public static function integer(ArrayAccess|array $array, string|int|null $key, ?
* Determines if an array is associative.
*
* An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ *
+ * @return ($array is list ? false : true)
*/
public static function isAssoc(array $array): bool
{
@@ -560,6 +646,8 @@ public static function isAssoc(array $array): bool
* Determines if an array is a list.
*
* An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between.
+ *
+ * @return ($array is list ? true : false)
*/
public static function isList(array $array): bool
{
@@ -797,6 +885,8 @@ public static function query(array $array): string
/**
* Get one or a specified number of random values from an array.
*
+ * @return ($number is null ? mixed : array)
+ *
* @throws InvalidArgumentException
*/
public static function random(array $array, int|string|null $number = null, bool $preserveKeys = false): mixed
@@ -881,8 +971,10 @@ public static function set(array &$array, string|int|float|null $key, mixed $val
/**
* Push an item into an array using "dot" notation.
+ *
+ * ArrayAccess cannot support the nested by-reference mutation required by dot notation.
*/
- public static function push(ArrayAccess|array &$array, string|int|null $key, mixed ...$values): array
+ public static function push(array &$array, string|int|null $key, mixed ...$values): array
{
$target = static::array($array, $key, []);
@@ -926,6 +1018,13 @@ public static function sole(array $array, ?callable $callback = null): mixed
/**
* Sort the array using the given callback or "dot" notation.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param iterable $array
+ * @param null|array|callable|int|string $callback
+ * @return array
*/
public static function sort(iterable $array, callable|array|int|string|null $callback = null): array
{
@@ -940,6 +1039,13 @@ public static function sort(iterable $array, callable|array|int|string|null $cal
/**
* Sort the array in descending order using the given callback or "dot" notation.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param iterable $array
+ * @param null|array|callable|int|string $callback
+ * @return array
*/
public static function sortDesc(iterable $array, callable|array|int|string|null $callback = null): array
{
@@ -954,6 +1060,13 @@ public static function sortDesc(iterable $array, callable|array|int|string|null
/**
* Recursively sort an array by keys and values.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @param int-mask-of $options
+ * @return array
*/
public static function sortRecursive(array $array, int $options = SORT_REGULAR, SortDirection|bool $descending = false): array
{
@@ -980,6 +1093,13 @@ public static function sortRecursive(array $array, int $options = SORT_REGULAR,
/**
* Recursively sort an array by keys and values in descending order.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @param int-mask-of $options
+ * @return array
*/
public static function sortRecursiveDesc(array $array, int $options = SORT_REGULAR): array
{
@@ -1006,6 +1126,9 @@ public static function string(ArrayAccess|array $array, string|int|null $key, ?s
/**
* Conditionally compile classes from an array into a CSS class list.
+ *
+ * @param array|array|string $array
+ * @return ($array is array ? '' : ($array is '' ? '' : ($array is array{} ? '' : non-empty-string)))
*/
public static function toCssClasses(array|string $array): string
{
@@ -1026,6 +1149,9 @@ public static function toCssClasses(array|string $array): string
/**
* Conditionally compile styles from an array into a style list.
+ *
+ * @param array|array|string $array
+ * @return ($array is array ? '' : ($array is '' ? '' : ($array is array{} ? '' : non-empty-string)))
*/
public static function toCssStyles(array|string $array): string
{
@@ -1046,6 +1172,13 @@ public static function toCssStyles(array|string $array): string
/**
* Filter the array using the given callback.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @param callable(TValue, TKey): bool $callback
+ * @return array
*/
public static function where(array $array, callable $callback): array
{
@@ -1054,6 +1187,13 @@ public static function where(array $array, callable $callback): array
/**
* Filter the array using the negation of the given callback.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @param callable(TValue, TKey): bool $callback
+ * @return array
*/
public static function reject(array $array, callable $callback): array
{
@@ -1088,6 +1228,12 @@ public static function partition(iterable $array, callable $callback): array
/**
* Filter items where the value is not null.
+ *
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @param array $array
+ * @return array
*/
public static function whereNotNull(array $array): array
{
@@ -1096,6 +1242,12 @@ public static function whereNotNull(array $array): array
/**
* If the given value is not an array and not null, wrap it in one.
+ *
+ * @template TKey of array-key = array-key
+ * @template TValue = mixed
+ *
+ * @param null|array|TValue $value
+ * @return ($value is null ? array{} : ($value is array ? array : array{TValue}))
*/
public static function wrap(mixed $value): array
{
diff --git a/types/Collections/Arr.php b/types/Collections/Arr.php
new file mode 100644
index 000000000..ae90dfeb2
--- /dev/null
+++ b/types/Collections/Arr.php
@@ -0,0 +1,46 @@
+, array<1|2>}", Arr::divide(['a' => 1, 'b' => 2]));
+assertType('array>', Arr::crossJoin([1], [2], ['third' => 3]));
+
+$array = ['first' => 1, 'second' => 2, 'third' => 3];
+
+assertType('mixed', Arr::random($array));
+assertType('array', Arr::random($array, 2));
+assertType("array<'first'|'second'|'third', 1|2|3>", Arr::sort($array));
+assertType("array<'first'|'second'|'third', 1|2|3>", Arr::sortDesc($array));
+assertType("array<'first'|'second'|'third', 1|2|3>", Arr::where($array, static fn (int $value): bool => $value > 1));
+assertType("array<'first'|'second'|'third', 1|2|3>", Arr::reject($array, static fn (int $value): bool => $value > 1));
+
+/** @var array $nullable */
+$nullable = [];
+assertType('array', Arr::whereNotNull($nullable));
+
+assertType('array{}', Arr::wrap(null));
+assertType('array{1}', Arr::wrap(1));
+assertType("array<'first'|'second'|'third', 1|2|3>", Arr::wrap($array));
+assertType("''", Arr::toCssClasses([]));
+assertType('non-empty-string', Arr::toCssClasses(['hidden' => true]));
+assertType("''", Arr::toCssStyles([]));
+assertType('non-empty-string', Arr::toCssStyles(['display: none' => true]));
+
+/** @var iterable $iterable */
+$iterable = new ArrayObject($array);
+assertType('int|null', Arr::last($iterable));
+assertType('bool', Arr::every($iterable, static fn (int $value, string $key): bool => $value > 0 && $key !== ''));
+assertType('bool', Arr::some($iterable, static fn (int $value, string $key): bool => $value > 0 && $key !== ''));
+
+$target = [];
+assertType('array', Arr::push($target, 'items', new stdClass));
From fc69520c380ce58b2e374a1e5ac6218e22289d32 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:55:54 +0000
Subject: [PATCH 23/40] Expand Arr regression coverage
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.
---
tests/Support/SupportArrTest.php | 254 ++++++++++++++++++++++---------
1 file changed, 182 insertions(+), 72 deletions(-)
diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php
index dbda395ba..84dfd14f1 100644
--- a/tests/Support/SupportArrTest.php
+++ b/tests/Support/SupportArrTest.php
@@ -14,6 +14,8 @@
use Hypervel\Tests\TestCase;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
+use ReflectionMethod;
+use RuntimeException;
use SortDirection;
use stdClass;
use WeakMap;
@@ -60,7 +62,7 @@ public function testArrayable(): void
$this->assertFalse(Arr::arrayable(static fn () => null));
}
- public function testAdd()
+ public function testAdd(): void
{
$array = Arr::add(['name' => 'Desk'], 'price', 100);
$this->assertEquals(['name' => 'Desk', 'price' => 100], $array);
@@ -75,7 +77,7 @@ public function testAdd()
$this->assertEquals(['category' => ['type' => 'Table']], Arr::add(['category' => ['type' => 'Table']], 'category.type', 'Chair'));
}
- public function testPush()
+ public function testPush(): void
{
$array = [];
@@ -100,7 +102,15 @@ public function testPush()
Arr::push($array, 'foo.bar', 'baz');
}
- public function testCollapse()
+ public function testPushTruthfullyDeclaresItsArrayOnlyMutationBoundary(): void
+ {
+ // Laravel declares ArrayAccess here, but dot notation requires nested array references.
+ $parameter = (new ReflectionMethod(Arr::class, 'push'))->getParameters()[0];
+
+ $this->assertSame('array', (string) $parameter->getType());
+ }
+
+ public function testCollapse(): void
{
// Normal case: a two-dimensional array with different elements
$data = [['foo', 'bar'], ['baz']];
@@ -124,7 +134,7 @@ public function testCollapse()
$this->assertEquals([1, 2, 3, 'foo', 'bar', 'baz', 'boom'], Arr::collapse($mixedArray));
}
- public function testCrossJoin()
+ public function testCrossJoin(): void
{
// Single dimension
$this->assertSame(
@@ -213,7 +223,7 @@ public function testDivide(): void
$this->assertEquals([['one' => 1, 2 => 'second'], 'one'], $values);
}
- public function testDot()
+ public function testDot(): void
{
$array = Arr::dot(['foo' => ['bar' => 'baz']]);
$this->assertSame(['foo.bar' => 'baz'], $array);
@@ -297,7 +307,7 @@ public function testDotWithDepth(): void
$this->assertSame(['prefix.user.name' => 'Taylor'], $array);
}
- public function testUndot()
+ public function testUndot(): void
{
$array = Arr::undot([
'user.name' => 'Taylor',
@@ -321,7 +331,7 @@ public function testUndot()
$this->assertEquals(['foo', 'foo' => ['bar' => 'baz', 'baz' => ['a' => 'b']]], $array);
}
- public function testExcept()
+ public function testExcept(): void
{
$array = ['name' => 'taylor', 'age' => 26];
$this->assertEquals(['age' => 26], Arr::except($array, ['name']));
@@ -337,7 +347,7 @@ public function testExcept()
$this->assertEquals([1 => 'hAz', 2 => [12 => 'baz']], Arr::except($array, 2.5));
}
- public function testExceptValues()
+ public function testExceptValues(): void
{
$array = ['name' => 'taylor', 'age' => 26, 'city' => 'austin'];
$this->assertEquals(['name' => 'taylor', 'city' => 'austin'], Arr::exceptValues($array, [26]));
@@ -365,7 +375,7 @@ public function testExceptValues()
$this->assertEquals([], Arr::exceptValues($array, [1, 0]));
}
- public function testExists()
+ public function testExists(): void
{
$this->assertTrue(Arr::exists([1], 0));
$this->assertTrue(Arr::exists([null], 0));
@@ -397,7 +407,7 @@ public function testWhereNotNull(): void
$this->assertEquals([1, 'string', 0.0, false, [], $class, $function], $array);
}
- public function testFirst()
+ public function testFirst(): void
{
$array = [100, 200, 300];
@@ -445,7 +455,7 @@ public function testFirst()
$this->assertNull(Arr::first($cursor));
}
- public function testFirstWorksWithArrayObject()
+ public function testFirstWorksWithArrayObject(): void
{
$arrayObject = new ArrayObject([0, 10, 20]);
@@ -454,7 +464,26 @@ public function testFirstWorksWithArrayObject()
$this->assertSame(0, $result);
}
- public function testJoin()
+ public function testFirstPreservesDuplicateIteratorKeysAndStopsAtTheFirstMatch(): void
+ {
+ $values = static function (): iterable {
+ yield 'duplicate' => 1;
+ yield 'other' => 2;
+ yield 'duplicate' => 3;
+ };
+
+ $this->assertSame(1, Arr::first($values(), static fn (int $value): bool => $value === 1));
+
+ $shortCircuit = static function (): iterable {
+ yield 'first' => 1;
+
+ throw new RuntimeException('The iterator should not advance beyond the first match.');
+ };
+
+ $this->assertSame(1, Arr::first($shortCircuit(), static fn (int $value): bool => $value === 1));
+ }
+
+ public function testJoin(): void
{
$this->assertSame('a, b, c', Arr::join(['a', 'b', 'c'], ', '));
@@ -467,7 +496,7 @@ public function testJoin()
$this->assertSame('', Arr::join([], ', ', ' and '));
}
- public function testLast()
+ public function testLast(): void
{
$array = [100, 200, 300];
@@ -508,7 +537,52 @@ public function testLast()
$this->assertEquals(200, $value5);
}
- public function testFlatten()
+ public function testLastAcceptsTraversableValuesWithAndWithoutACallback(): void
+ {
+ $values = static function (): iterable {
+ yield 'first' => 100;
+ yield 'second' => 200;
+ yield 'third' => 300;
+ };
+
+ $this->assertSame(300, Arr::last($values()));
+ $this->assertSame(200, Arr::last(
+ $values(),
+ static fn (int $value, string $key): bool => $value < 250 && $key !== 'first',
+ ));
+ $this->assertSame('fallback', Arr::last(
+ new ArrayObject,
+ static fn (): bool => true,
+ 'fallback',
+ ));
+ }
+
+ public function testLastPreservesDuplicateIteratorKeysAndReverseCallbackOrder(): void
+ {
+ $values = static function (): iterable {
+ yield 'duplicate' => 1;
+ yield 'other' => 2;
+ yield 'duplicate' => 3;
+ };
+
+ $this->assertSame(3, Arr::last($values()));
+
+ $visited = [];
+
+ $result = Arr::last($values(), static function (int $value, string $key) use (&$visited): bool {
+ $visited[] = [$key, $value];
+
+ return $value === 2;
+ });
+
+ $this->assertSame(2, $result);
+ $this->assertSame([
+ ['duplicate', 3],
+ ['other', 2],
+ ], $visited);
+ }
+
+ public function testFlatten(): void
{
// Flat arrays are unaffected
$array = ['#foo', '#bar', '#baz'];
@@ -547,7 +621,7 @@ public function testFlatten()
$this->assertEquals(['#foo', '#bar', '#zap', '#baz'], Arr::flatten($array));
}
- public function testFlattenWithDepth()
+ public function testFlattenWithDepth(): void
{
// No depth flattens recursively
$array = [['#foo', ['#bar', ['#baz']]], '#zap'];
@@ -561,7 +635,7 @@ public function testFlattenWithDepth()
$this->assertEquals(['#foo', '#bar', ['#baz'], '#zap'], Arr::flatten($array, 2));
}
- public function testGet()
+ public function testGet(): void
{
$array = ['products.desk' => ['price' => 100]];
$this->assertEquals(['price' => 100], Arr::get($array, 'products.desk'));
@@ -649,7 +723,7 @@ public function testGet()
$this->assertSame('bar', Arr::get(['' => ['' => 'bar']], '.'));
}
- public function testItGetsAString()
+ public function testItGetsAString(): void
{
$test_array = ['string' => 'foo bar', 'integer' => 1234];
@@ -671,7 +745,7 @@ public function testItGetsAString()
Arr::string($test_array, 'integer');
}
- public function testItGetsAnInteger()
+ public function testItGetsAnInteger(): void
{
$test_array = ['string' => 'foo bar', 'integer' => 1234];
@@ -693,7 +767,7 @@ public function testItGetsAnInteger()
Arr::integer($test_array, 'string');
}
- public function testItGetsAFloat()
+ public function testItGetsAFloat(): void
{
$test_array = ['string' => 'foo bar', 'float' => 12.34];
@@ -715,7 +789,7 @@ public function testItGetsAFloat()
Arr::float($test_array, 'string');
}
- public function testItGetsABoolean()
+ public function testItGetsABoolean(): void
{
$test_array = ['string' => 'foo bar', 'boolean' => true];
@@ -737,7 +811,7 @@ public function testItGetsABoolean()
Arr::boolean($test_array, 'string');
}
- public function testItGetsAnArray()
+ public function testItGetsAnArray(): void
{
$test_array = ['string' => 'foo bar', 'array' => ['foo', 'bar']];
@@ -759,7 +833,7 @@ public function testItGetsAnArray()
Arr::array($test_array, 'string');
}
- public function testHas()
+ public function testHas(): void
{
$array = ['products.desk' => ['price' => 100]];
$this->assertTrue(Arr::has($array, 'products.desk'));
@@ -822,7 +896,7 @@ public function testHas()
$this->assertFalse(Arr::has([], ['']));
}
- public function testHasAllMethod()
+ public function testHasAllMethod(): void
{
$array = ['name' => 'Taylor', 'age' => '', 'city' => null];
$this->assertTrue(Arr::hasAll($array, 'name'));
@@ -845,7 +919,7 @@ public function testHasAllMethod()
$this->assertFalse(Arr::hasAll($array, ['foo', 'bar', 'baz', 'bar']));
}
- public function testHasAnyMethod()
+ public function testHasAnyMethod(): void
{
$array = ['name' => 'Taylor', 'age' => '', 'city' => null];
$this->assertTrue(Arr::hasAny($array, 'name'));
@@ -867,21 +941,57 @@ public function testHasAnyMethod()
$this->assertTrue(Arr::hasAny($array, ['foo.bax', 'foo.baz']));
}
- public function testEvery()
+ public function testEvery(): void
{
$this->assertFalse(Arr::every([1, 2], fn ($value, $key) => is_string($value)));
$this->assertFalse(Arr::every(['foo', 2], fn ($value, $key) => is_string($value)));
$this->assertTrue(Arr::every(['foo', 'bar'], fn ($value, $key) => is_string($value)));
}
- public function testSome()
+ public function testEveryAcceptsTraversableValuesAndStopsAtTheFirstFailure(): void
+ {
+ $visited = [];
+ $values = static function (): iterable {
+ yield 'first' => 1;
+ yield 'second' => 2;
+ yield 'third' => 3;
+ };
+
+ $this->assertFalse(Arr::every($values(), function (int $value, string $key) use (&$visited): bool {
+ $visited[] = $key;
+
+ return $value < 2;
+ }));
+ $this->assertSame(['first', 'second'], $visited);
+ $this->assertTrue(Arr::every(new ArrayObject(['first' => 1, 'second' => 2]), static fn (int $value): bool => $value > 0));
+ }
+
+ public function testSome(): void
{
$this->assertFalse(Arr::some([1, 2], fn ($value, $key) => is_string($value)));
$this->assertTrue(Arr::some(['foo', 2], fn ($value, $key) => is_string($value)));
$this->assertTrue(Arr::some(['foo', 'bar'], fn ($value, $key) => is_string($value)));
}
- public function testIsAssoc()
+ public function testSomeAcceptsTraversableValuesAndStopsAtTheFirstMatch(): void
+ {
+ $visited = [];
+ $values = static function (): iterable {
+ yield 'first' => 1;
+ yield 'second' => 2;
+ yield 'third' => 3;
+ };
+
+ $this->assertTrue(Arr::some($values(), function (int $value, string $key) use (&$visited): bool {
+ $visited[] = $key;
+
+ return $value === 2;
+ }));
+ $this->assertSame(['first', 'second'], $visited);
+ $this->assertFalse(Arr::some(new ArrayObject(['first' => 1, 'second' => 2]), static fn (int $value): bool => $value > 2));
+ }
+
+ public function testIsAssoc(): void
{
$this->assertTrue(Arr::isAssoc(['a' => 'a', 0 => 'b']));
$this->assertTrue(Arr::isAssoc([1 => 'a', 0 => 'b']));
@@ -900,7 +1010,7 @@ public function testIsAssoc()
$this->assertTrue(Arr::isAssoc(['foo' => 'bar', 'baz' => 'qux']));
}
- public function testIsList()
+ public function testIsList(): void
{
$this->assertTrue(Arr::isList([]));
$this->assertTrue(Arr::isList([1, 2, 3]));
@@ -918,7 +1028,7 @@ public function testIsList()
$this->assertFalse(Arr::isList(['foo' => 'bar', 'baz' => 'qux']));
}
- public function testOnly()
+ public function testOnly(): void
{
$array = ['name' => 'Desk', 'price' => 100, 'orders' => 10];
$array = Arr::only($array, ['name', 'price']);
@@ -937,7 +1047,7 @@ public function testOnly()
$this->assertEquals(['bar' => 'baz'], Arr::only(['foo', 'bar' => 'baz'], 'bar'));
}
- public function testOnlyValues()
+ public function testOnlyValues(): void
{
$array = ['name' => 'taylor', 'age' => 26, 'city' => 'austin'];
$this->assertEquals(['age' => 26], Arr::onlyValues($array, [26]));
@@ -965,7 +1075,7 @@ public function testOnlyValues()
$this->assertEquals(['a' => true, 'b' => false, 'c' => 1, 'd' => 0], Arr::onlyValues($array, [1, 0]));
}
- public function testPluck()
+ public function testPluck(): void
{
$data = [
'post-1' => [
@@ -1011,7 +1121,7 @@ public function testPluck()
$this->assertEquals(['Taylor', 'Abigail'], $array);
}
- public function testPluckWithArrayValue()
+ public function testPluckWithArrayValue(): void
{
$array = [
['developer' => ['name' => 'Taylor']],
@@ -1021,7 +1131,7 @@ public function testPluckWithArrayValue()
$this->assertEquals(['Taylor', 'Abigail'], $array);
}
- public function testPluckWithKeys()
+ public function testPluckWithKeys(): void
{
$array = [
['name' => 'Taylor', 'role' => 'developer'],
@@ -1042,7 +1152,7 @@ public function testPluckWithKeys()
], $test2);
}
- public function testPluckWithCarbonKeys()
+ public function testPluckWithCarbonKeys(): void
{
$array = [
['start' => new Carbon('2017-07-25 00:00:00'), 'end' => new Carbon('2017-07-30 00:00:00')],
@@ -1051,14 +1161,14 @@ public function testPluckWithCarbonKeys()
$this->assertEquals(['2017-07-25 00:00:00' => '2017-07-30 00:00:00'], $array);
}
- public function testArrayPluckWithArrayAndObjectValues()
+ public function testArrayPluckWithArrayAndObjectValues(): void
{
$array = [(object) ['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']];
$this->assertEquals(['taylor', 'dayle'], Arr::pluck($array, 'name'));
$this->assertEquals(['taylor' => 'foo', 'dayle' => 'bar'], Arr::pluck($array, 'email', 'name'));
}
- public function testArrayPluckWithNestedKeys()
+ public function testArrayPluckWithNestedKeys(): void
{
$array = [['user' => ['taylor', 'otwell']], ['user' => ['dayle', 'rees']]];
$this->assertEquals(['taylor', 'dayle'], Arr::pluck($array, 'user.0'));
@@ -1067,7 +1177,7 @@ public function testArrayPluckWithNestedKeys()
$this->assertEquals(['taylor' => 'otwell', 'dayle' => 'rees'], Arr::pluck($array, ['user', 1], ['user', 0]));
}
- public function testArrayPluckWithNestedArrays()
+ public function testArrayPluckWithNestedArrays(): void
{
$array = [
[
@@ -1090,7 +1200,7 @@ public function testArrayPluckWithNestedArrays()
$this->assertEquals([['taylorotwell@gmail.com'], [null, null]], Arr::pluck($array, 'users.*.email'));
}
- public function testMap()
+ public function testMap(): void
{
$data = ['first' => 'taylor', 'last' => 'otwell'];
$mapped = Arr::map($data, function ($value, $key) {
@@ -1100,7 +1210,7 @@ public function testMap()
$this->assertEquals(['first' => 'taylor', 'last' => 'otwell'], $data);
}
- public function testMapWithEmptyArray()
+ public function testMapWithEmptyArray(): void
{
$mapped = Arr::map([], static function ($value, $key) {
return $key . '-' . $value;
@@ -1108,7 +1218,7 @@ public function testMapWithEmptyArray()
$this->assertEquals([], $mapped);
}
- public function testMapNullValues()
+ public function testMapNullValues(): void
{
$data = ['first' => 'taylor', 'last' => null];
$mapped = Arr::map($data, static function ($value, $key) {
@@ -1117,7 +1227,7 @@ public function testMapNullValues()
$this->assertEquals(['first' => 'first-taylor', 'last' => 'last-'], $mapped);
}
- public function testMapWithKeys()
+ public function testMapWithKeys(): void
{
$data = [
['name' => 'Blastoise', 'type' => 'Water', 'idx' => 9],
@@ -1135,7 +1245,7 @@ public function testMapWithKeys()
);
}
- public function testMapByReference()
+ public function testMapByReference(): void
{
$data = ['first' => 'taylor', 'last' => 'otwell'];
$mapped = Arr::map($data, 'strrev');
@@ -1144,7 +1254,7 @@ public function testMapByReference()
$this->assertEquals(['first' => 'taylor', 'last' => 'otwell'], $data);
}
- public function testMapSpread()
+ public function testMapSpread(): void
{
$c = [[1, 'a'], [2, 'b']];
@@ -1160,7 +1270,7 @@ public function testMapSpread()
}
#[IgnoreDeprecations]
- public function testPrepend()
+ public function testPrepend(): void
{
$array = Arr::prepend(['one', 'two', 'three', 'four'], 'zero');
$this->assertEquals(['zero', 'one', 'two', 'three', 'four'], $array);
@@ -1202,7 +1312,7 @@ public function testPrepend()
$this->assertEquals(['one', 'two', null => ['zero']], $array);
}
- public function testPull()
+ public function testPull(): void
{
$array = ['name' => 'Desk', 'price' => 100];
$name = Arr::pull($array, 'name');
@@ -1228,7 +1338,7 @@ public function testPull()
$this->assertSame([1 => 'Second'], $array);
}
- public function testQuery()
+ public function testQuery(): void
{
$this->assertSame('', Arr::query([]));
$this->assertSame('foo=bar', Arr::query(['foo' => 'bar']));
@@ -1238,7 +1348,7 @@ public function testQuery()
$this->assertSame('foo=bar&bar=', Arr::query(['foo' => 'bar', 'bar' => '']));
}
- public function testRandom()
+ public function testRandom(): void
{
$random = Arr::random(['foo', 'bar', 'baz']);
$this->assertContains($random, ['foo', 'bar', 'baz']);
@@ -1280,13 +1390,13 @@ public function testRandom()
$this->assertCount(2, array_intersect_assoc(['one' => 'foo', 'two' => 'bar', 'three' => 'baz'], $random));
}
- public function testRandomNotIncrementingKeys()
+ public function testRandomNotIncrementingKeys(): void
{
$random = Arr::random(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz']);
$this->assertContains($random, ['foo', 'bar', 'baz']);
}
- public function testRandomOnEmptyArray()
+ public function testRandomOnEmptyArray(): void
{
$random = Arr::random([], 0);
$this->assertIsArray($random);
@@ -1297,7 +1407,7 @@ public function testRandomOnEmptyArray()
$this->assertCount(0, $random);
}
- public function testRandomThrowsAnErrorWhenRequestingMoreItemsThanAreAvailable()
+ public function testRandomThrowsAnErrorWhenRequestingMoreItemsThanAreAvailable(): void
{
$exceptions = 0;
@@ -1322,7 +1432,7 @@ public function testRandomThrowsAnErrorWhenRequestingMoreItemsThanAreAvailable()
$this->assertSame(3, $exceptions);
}
- public function testSet()
+ public function testSet(): void
{
$array = ['products' => ['desk' => ['price' => 100]]];
Arr::set($array, 'products.desk.price', 200);
@@ -1364,7 +1474,7 @@ public function testSet()
$this->assertEquals([1 => 'hAz'], Arr::set($array, 1, 'hAz'));
}
- public function testShuffleProducesDifferentShuffles()
+ public function testShuffleProducesDifferentShuffles(): void
{
$input = range('a', 'z');
@@ -1374,7 +1484,7 @@ public function testShuffleProducesDifferentShuffles()
);
}
- public function testShuffleActuallyShuffles()
+ public function testShuffleActuallyShuffles(): void
{
$input = range('a', 'z');
@@ -1384,7 +1494,7 @@ public function testShuffleActuallyShuffles()
);
}
- public function testShuffleKeepsSameValues()
+ public function testShuffleKeepsSameValues(): void
{
$input = range('a', 'z');
$shuffled = Arr::shuffle($input);
@@ -1393,7 +1503,7 @@ public function testShuffleKeepsSameValues()
$this->assertEquals($input, $shuffled);
}
- public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists()
+ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists(): void
{
$this->assertSame('foo', Arr::sole(['foo']));
@@ -1408,21 +1518,21 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists()
);
}
- public function testSoleThrowsExceptionIfNoItemsExist()
+ public function testSoleThrowsExceptionIfNoItemsExist(): void
{
$this->expectException(ItemNotFoundException::class);
Arr::sole(['foo'], fn (string $value) => $value === 'baz');
}
- public function testSoleThrowsExceptionIfMoreThanOneItemExists()
+ public function testSoleThrowsExceptionIfMoreThanOneItemExists(): void
{
$this->expectExceptionObject(new MultipleItemsFoundException(2));
Arr::sole(['baz', 'foo', 'baz'], fn (string $value) => $value === 'baz');
}
- public function testEmptyShuffle()
+ public function testEmptyShuffle(): void
{
$this->assertEquals([], Arr::shuffle([]));
}
@@ -1487,7 +1597,7 @@ public function testSortDesc(): void
$this->assertEquals([['Desk', 2], ['Chair', 1]], $sortedWithIntegerKey);
}
- public function testSortRecursive()
+ public function testSortRecursive(): void
{
$array = [
'users' => [
@@ -1601,7 +1711,7 @@ public function testSortRecursiveDesc(): void
$this->assertEquals($expect, Arr::sortRecursive($array, SORT_REGULAR, SortDirection::Descending));
}
- public function testToCssClasses()
+ public function testToCssClasses(): void
{
$classes = Arr::toCssClasses([
'font-bold',
@@ -1620,7 +1730,7 @@ public function testToCssClasses()
$this->assertSame('font-bold mt-4 ml-2', $classes);
}
- public function testToCssStyles()
+ public function testToCssStyles(): void
{
$styles = Arr::toCssStyles([
'font-weight: bold',
@@ -1639,7 +1749,7 @@ public function testToCssStyles()
$this->assertSame('font-weight: bold; margin-top: 4px; margin-left: 2px;', $styles);
}
- public function testWhere()
+ public function testWhere(): void
{
$array = [100, '200', 300, '400', 500];
@@ -1650,7 +1760,7 @@ public function testWhere()
$this->assertEquals([1 => '200', 3 => '400'], $array);
}
- public function testWhereKey()
+ public function testWhereKey(): void
{
$array = ['10' => 1, 'foo' => 3, 20 => 2];
@@ -1661,7 +1771,7 @@ public function testWhereKey()
$this->assertEquals(['10' => 1, 20 => 2], $array);
}
- public function testForget()
+ public function testForget(): void
{
$array = ['products' => ['desk' => ['price' => 100]]];
Arr::forget($array, null);
@@ -1718,7 +1828,7 @@ public function testForget()
$this->assertEquals([2 => [1 => 'products']], $array);
}
- public function testFrom()
+ public function testFrom(): void
{
$this->assertSame(['foo' => 'bar'], Arr::from(['foo' => 'bar']));
$this->assertSame(['foo' => 'bar'], Arr::from((object) ['foo' => 'bar']));
@@ -1744,7 +1854,7 @@ public function testFrom()
Arr::from(123);
}
- public function testWrap()
+ public function testWrap(): void
{
$string = 'a';
$array = ['a'];
@@ -1769,7 +1879,7 @@ public function testWrap()
$this->assertSame($obj, Arr::wrap($obj)[0]);
}
- public function testSortByMany()
+ public function testSortByMany(): void
{
$unsorted = [
['name' => 'John', 'age' => 8, 'meta' => ['key' => 3]],
@@ -1822,7 +1932,7 @@ function ($a, $b) {
], $sortedWithCallable);
}
- public function testKeyBy()
+ public function testKeyBy(): void
{
$array = [
['id' => '123', 'data' => 'abc'],
@@ -1837,7 +1947,7 @@ public function testKeyBy()
], Arr::keyBy($array, 'id'));
}
- public function testPrependKeysWith()
+ public function testPrependKeysWith(): void
{
$array = [
'id' => '123',
@@ -1878,7 +1988,7 @@ public function testTake(): void
$this->assertEquals([1, 2, 3, 4, 5, 6], Arr::take($array, -10));
}
- public function testSelect()
+ public function testSelect(): void
{
$array = [
[
@@ -1924,7 +2034,7 @@ public function testSelect()
], Arr::select($array, null));
}
- public function testReject()
+ public function testReject(): void
{
$array = [1, 2, 3, 4, 5, 6];
@@ -1952,7 +2062,7 @@ public function testReject()
], $result);
}
- public function testPartition()
+ public function testPartition(): void
{
$array = ['John', 'Jane', 'Greg'];
From 6e6bc5080e19b601db22815246f4e6b79544aca8 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:55:54 +0000
Subject: [PATCH 24/40] Correct enum helper default handling
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.
---
src/collections/src/functions.php | 2 +-
tests/Support/SupportEnumValueFunctionTest.php | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/collections/src/functions.php b/src/collections/src/functions.php
index 896c51af0..341c3a112 100644
--- a/src/collections/src/functions.php
+++ b/src/collections/src/functions.php
@@ -16,7 +16,7 @@
* @template TDefault
*
* @param TValue $value
- * @param callable(TValue): TDefault|TDefault $default
+ * @param (callable(): TDefault)|TDefault $default
* @return ($value is empty ? TDefault : mixed)
*/
function enum_value(mixed $value, mixed $default = null): mixed
diff --git a/tests/Support/SupportEnumValueFunctionTest.php b/tests/Support/SupportEnumValueFunctionTest.php
index 117f63f9e..cd2e31b9c 100644
--- a/tests/Support/SupportEnumValueFunctionTest.php
+++ b/tests/Support/SupportEnumValueFunctionTest.php
@@ -14,12 +14,12 @@
class SupportEnumValueFunctionTest extends TestCase
{
#[DataProvider('scalarDataProvider')]
- public function testItCanHandleEnumValue($given, $expected)
+ public function testItCanHandleEnumValue($given, $expected): void
{
$this->assertSame($expected, enum_value($given));
}
- public static function scalarDataProvider()
+ public static function scalarDataProvider(): iterable
{
yield [TestEnum::A, 'A'];
yield [TestBackedEnum::A, 1];
@@ -42,7 +42,7 @@ public static function scalarDataProvider()
yield [$collect = collect(), $collect];
}
- public function testItCanFallbackToUseDefaultIfValueIsNull()
+ public function testItCanFallbackToUseDefaultIfValueIsNull(): void
{
$this->assertSame('laravel', enum_value(null, 'laravel'));
$this->assertSame('laravel', enum_value(null, fn () => 'laravel'));
From a32b787d02f82a40bee26eee5892a066914cc02c Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:55:55 +0000
Subject: [PATCH 25/40] Improve conditional helper inference
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.
---
src/collections/src/helpers.php | 12 ++++++++----
types/Collections/helpers.php | 10 ++++++++++
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/collections/src/helpers.php b/src/collections/src/helpers.php
index 6bfcb5a57..6d1fc6c5e 100644
--- a/src/collections/src/helpers.php
+++ b/src/collections/src/helpers.php
@@ -258,7 +258,7 @@ function last($array)
* @template TValue
* @template TArgs
*
- * @param \Closure(TArgs): TValue|TValue $value
+ * @param (\Closure(TArgs): TValue)|TValue $value
* @param TArgs ...$args
* @return TValue
*/
@@ -272,10 +272,14 @@ function value($value, ...$args)
/**
* Return a value if the given condition is true.
*
+ * @template TValue
+ * @template TArgs
+ * @template TDefault
+ *
* @param mixed $condition
- * @param \Closure|mixed $value
- * @param \Closure|mixed $default
- * @return mixed
+ * @param (\Closure(TArgs): TValue)|TValue $value
+ * @param (\Closure(): TDefault)|TDefault $default
+ * @return ($condition is non-empty-array|non-falsy-string|positive-int|true ? TValue : ($condition is callable ? TDefault|TValue : TDefault))
*/
function when($condition, $value, $default = null)
{
diff --git a/types/Collections/helpers.php b/types/Collections/helpers.php
index 01ea45671..3d1d059ae 100644
--- a/types/Collections/helpers.php
+++ b/types/Collections/helpers.php
@@ -2,6 +2,7 @@
declare(strict_types=1);
+use function Hypervel\Support\enum_value;
use function PHPStan\Testing\assertType;
assertType("'foo'", value('foo', 42));
@@ -11,3 +12,12 @@
return 42;
}, true));
+
+assertType("'foo'", when(true, 'foo'));
+assertType("'foo'", when(true, 'foo', 42));
+assertType('null', when(false, 'foo'));
+assertType('42', when(false, 'foo', 42));
+assertType('42|null', when(random_int(0, 1), 42));
+assertType('42|1337', when(random_int(0, 1), 42, 1337));
+
+assertType("'fallback'", enum_value(null, static fn () => 'fallback'));
From fe851f5813d608842786fa9c49279f75994bbd00 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:20 +0000
Subject: [PATCH 26/40] Extend shared collection contracts
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.
---
src/collections/src/Enumerable.php | 60 +++++++++++++----
.../src/Traits/EnumeratesValues.php | 40 ++++++++---
types/Collections/Collection.php | 67 +++++++++++++++++++
3 files changed, 145 insertions(+), 22 deletions(-)
create mode 100644 types/Collections/Collection.php
diff --git a/src/collections/src/Enumerable.php b/src/collections/src/Enumerable.php
index e7e6a061f..1b92944a9 100644
--- a/src/collections/src/Enumerable.php
+++ b/src/collections/src/Enumerable.php
@@ -40,11 +40,18 @@ public static function make(Arrayable|iterable|null $items = []): static;
/**
* Create a new instance by invoking the callback a given amount of times.
+ *
+ * @template TTimesValue
+ *
+ * @param null|(callable(int): TTimesValue) $callback
+ * @return ($callback is null ? static : static)
*/
public static function times(int $number, ?callable $callback = null): static;
/**
* Create a collection with the given range.
+ *
+ * @return static
*/
public static function range(int $from, int $to, int $step = 1): static;
@@ -70,6 +77,8 @@ public static function empty(): static;
/**
* Get all items in the enumerable.
+ *
+ * @return array
*/
public function all(): array;
@@ -395,6 +404,8 @@ public function firstWhere(callable|string $key, mixed $operator = null, mixed $
/**
* Get a flattened array of the items in the collection.
+ *
+ * @return static
*/
public function flatten(int|float $depth = INF);
@@ -503,15 +514,7 @@ public function isEmpty(): bool;
*/
public function isNotEmpty(): bool;
- /**
- * Determine if the collection contains a single item.
- */
- public function containsOneItem(): bool;
-
- /**
- * Determine if the collection contains multiple items.
- */
- public function containsManyItems(): bool;
+ // REMOVED: Laravel's deprecated containsOneItem() and containsManyItems(); use hasSole() and hasMany().
/**
* Determine if the collection contains a single item, optionally matching the given criteria.
@@ -669,14 +672,20 @@ public function union(mixed $items): static;
/**
* Get the min value of a given key.
*
- * @param null|(callable(TValue):mixed)|string $callback
+ * @template TMinResult = mixed
+ *
+ * @param null|(callable(TValue): TMinResult)|string $callback
+ * @return ($callback is callable ? ?TMinResult : ($callback is null ? ?TValue : mixed))
*/
public function min(callable|int|string|null $callback = null): mixed;
/**
* Get the max value of a given key.
*
- * @param null|(callable(TValue):mixed)|string $callback
+ * @template TMaxResult = mixed
+ *
+ * @param null|(callable(TValue): TMaxResult)|string $callback
+ * @return ($callback is callable ? ?TMaxResult : ($callback is null ? ?TValue : mixed))
*/
public function max(callable|int|string|null $callback = null): mixed;
@@ -719,11 +728,12 @@ public function concat(iterable $source): static;
/**
* Get one or a specified number of items randomly from the collection.
*
- * @return static|TValue
+ * @return ($number is null ? TValue : static<($preserveKeys is true ? TKey : int), TValue>)
*
* @throws InvalidArgumentException
*/
- public function random(callable|int|string|null $number = null): mixed;
+ // Hypervel exposes key preservation through the contract because every implementation supports it.
+ public function random(callable|int|string|null $number = null, bool $preserveKeys = false): mixed;
/**
* Reduce the collection to a single value.
@@ -737,6 +747,17 @@ public function random(callable|int|string|null $number = null): mixed;
*/
public function reduce(callable $callback, mixed $initial = null): mixed;
+ /**
+ * Reduce the collection to a single value by mutating an initial value.
+ *
+ * @template TReduceIntoInitial
+ *
+ * @param TReduceIntoInitial $initial
+ * @param callable(TReduceIntoInitial, TValue, TKey): void $callback
+ * @return TReduceIntoInitial
+ */
+ public function reduceInto(mixed $initial, callable $callback): mixed;
+
/**
* Reduce the collection to multiple aggregate values.
*
@@ -882,6 +903,8 @@ public function sort(callable|int|null $callback = null): static;
/**
* Sort items in descending order.
+ *
+ * @param int-mask-of $options
*/
public function sortDesc(int $options = SORT_REGULAR): static;
@@ -889,6 +912,7 @@ public function sortDesc(int $options = SORT_REGULAR): static;
* Sort the collection using the given callback.
*
* @param array|(callable(TValue, TKey): mixed)|int|string $callback
+ * @param int-mask-of $options
*/
public function sortBy(array|callable|int|string $callback, int $options = SORT_REGULAR, SortDirection|bool $descending = false): static;
@@ -896,16 +920,21 @@ public function sortBy(array|callable|int|string $callback, int $options = SORT_
* Sort the collection in descending order using the given callback.
*
* @param array|(callable(TValue, TKey): mixed)|int|string $callback
+ * @param int-mask-of $options
*/
public function sortByDesc(array|callable|int|string $callback, int $options = SORT_REGULAR): static;
/**
* Sort the collection keys.
+ *
+ * @param int-mask-of $options
*/
public function sortKeys(int $options = SORT_REGULAR, SortDirection|bool $descending = false): static;
/**
* Sort the collection keys in descending order.
+ *
+ * @param int-mask-of $options
*/
public function sortKeysDesc(int $options = SORT_REGULAR): static;
@@ -919,7 +948,10 @@ public function sortKeysUsing(callable $callback): static;
/**
* Get the sum of the given values.
*
- * @param null|(callable(TValue): mixed)|string $callback
+ * @template TReturnType
+ *
+ * @param null|(callable(TValue, TKey): TReturnType)|string $callback
+ * @return ($callback is callable ? float|int|TReturnType : mixed)
*/
public function sum(callable|int|string|null $callback = null): mixed;
diff --git a/src/collections/src/Traits/EnumeratesValues.php b/src/collections/src/Traits/EnumeratesValues.php
index 760f7c303..ee1548d0b 100644
--- a/src/collections/src/Traits/EnumeratesValues.php
+++ b/src/collections/src/Traits/EnumeratesValues.php
@@ -170,7 +170,7 @@ public static function empty(mixed ...$args): static
* @template TTimesValue
*
* @param null|(callable(int): TTimesValue) $callback
- * @return static
+ * @return ($callback is null ? static : static)
*/
public static function times(int $number, ?callable $callback = null, mixed ...$args): static
{
@@ -465,7 +465,10 @@ public function mapInto(string $class)
/**
* Get the min value of a given key.
*
- * @param null|(callable(TValue):mixed)|string $callback
+ * @template TMinResult = mixed
+ *
+ * @param null|(callable(TValue): TMinResult)|string $callback
+ * @return ($callback is callable ? ?TMinResult : ($callback is null ? ?TValue : mixed))
*/
public function min(callable|int|string|null $callback = null): mixed
{
@@ -479,7 +482,10 @@ public function min(callable|int|string|null $callback = null): mixed
/**
* Get the max value of a given key.
*
- * @param null|(callable(TValue):mixed)|string $callback
+ * @template TMaxResult = mixed
+ *
+ * @param null|(callable(TValue): TMaxResult)|string $callback
+ * @return ($callback is callable ? ?TMaxResult : ($callback is null ? ?TValue : mixed))
*/
public function max(callable|int|string|null $callback = null): mixed
{
@@ -542,8 +548,8 @@ public function percentage(callable $callback, int $precision = 2): ?float
*
* @template TReturnType
*
- * @param null|(callable(TValue): TReturnType)|string $callback
- * @return ($callback is callable ? TReturnType : mixed)
+ * @param null|(callable(TValue, TKey): TReturnType)|string $callback
+ * @return ($callback is callable ? float|int|TReturnType : mixed)
*/
public function sum(callable|int|string|null $callback = null): mixed
{
@@ -551,7 +557,7 @@ public function sum(callable|int|string|null $callback = null): mixed
? $this->identity()
: $this->valueRetriever($callback);
- return $this->reduce(fn ($result, $item) => $result + $callback($item), 0);
+ return $this->reduce(fn ($result, $item, $key) => $result + $callback($item, $key), 0);
}
/**
@@ -769,7 +775,7 @@ public function pipeThrough(array $callbacks): mixed
*
* @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
* @param TReduceInitial $initial
- * @return TReduceReturnType
+ * @return TReduceInitial|TReduceReturnType
*/
public function reduce(callable $callback, mixed $initial = null): mixed
{
@@ -782,6 +788,24 @@ public function reduce(callable $callback, mixed $initial = null): mixed
return $result;
}
+ /**
+ * Reduce the collection to a single value by mutating an initial value.
+ *
+ * @template TReduceIntoInitial
+ *
+ * @param TReduceIntoInitial $initial
+ * @param callable(TReduceIntoInitial, TValue, TKey): void $callback
+ * @return TReduceIntoInitial
+ */
+ public function reduceInto(mixed $initial, callable $callback): mixed
+ {
+ foreach ($this as $key => $value) {
+ $callback($initial, $value, $key);
+ }
+
+ return $initial;
+ }
+
/**
* Reduce the collection to multiple aggregate values.
*
@@ -814,7 +838,7 @@ class_basename(static::class),
*
* @param callable(TReduceWithKeysInitial|TReduceWithKeysReturnType, TValue, TKey): TReduceWithKeysReturnType $callback
* @param TReduceWithKeysInitial $initial
- * @return TReduceWithKeysReturnType
+ * @return TReduceWithKeysInitial|TReduceWithKeysReturnType
*/
public function reduceWithKeys(callable $callback, mixed $initial = null): mixed
{
diff --git a/types/Collections/Collection.php b/types/Collections/Collection.php
new file mode 100644
index 000000000..9809b9855
--- /dev/null
+++ b/types/Collections/Collection.php
@@ -0,0 +1,67 @@
+ 1, 'second' => 2, 'third' => 3]);
+$lazy = new LazyCollection(['first' => 1, 'second' => 2, 'third' => 3]);
+
+/** @return Generator */
+$lazySource = static function (): Generator {
+ yield 'first' => 1;
+ yield 'second' => 2;
+};
+
+assertType('array', $collection->all());
+assertType('Hypervel\Support\Collection', Collection::range(1, 3));
+assertType('Hypervel\Support\Collection', Collection::times(3));
+assertType('Hypervel\Support\Collection', Collection::times(3, static fn (int $number): bool => $number > 1));
+assertType('Hypervel\Support\LazyCollection', LazyCollection::times(3));
+assertType('Hypervel\Support\LazyCollection', LazyCollection::times(3, static fn (int $number): bool => $number > 1));
+assertType('Hypervel\Support\Collection', $collection->flatten());
+
+assertType('1|2|3|null', $collection->min());
+assertType("'1'|'2'|'3'|null", $collection->min(static fn (int $value): string => (string) $value));
+assertType('1|2|3|null', $collection->max());
+assertType("'1'|'2'|'3'|null", $collection->max(static fn (int $value): string => (string) $value));
+
+assertType('float|int', $collection->sum(function (int $value, string $key): int {
+ assertType('1|2|3', $value);
+ assertType('string', $key);
+
+ return $value;
+}));
+assertType('mixed', $collection->sum('amount'));
+
+assertType('stdClass', $collection->reduceInto(new stdClass, static function (stdClass $result, int $value, string $key): void {
+ $result->{$key} = $value;
+}));
+
+assertType('1|2|3', $collection->random());
+assertType('Hypervel\Support\Collection', $collection->random(2));
+assertType('Hypervel\Support\Collection', $collection->random(2, true));
+assertType('1|2|3', $lazy->random());
+assertType('Hypervel\Support\LazyCollection', $lazy->random(2));
+assertType('Hypervel\Support\LazyCollection', $lazy->random(2, true));
+assertType('Hypervel\Support\LazyCollection', LazyCollection::make($lazySource));
+
+/**
+ * @param Enumerable $enumerable
+ */
+function assertEnumerableTypes(Enumerable $enumerable): void
+{
+ // Hypervel exposes preserveKeys on the contract because both concrete collections support it.
+ assertType('Hypervel\Support\Enumerable', $enumerable->random(2));
+ assertType('Hypervel\Support\Enumerable', $enumerable->random(2, true));
+ assertType('float|int', $enumerable->sum(static fn (int $value): int => $value));
+ assertType('mixed', $enumerable->sum('amount'));
+}
+
+assertEnumerableTypes($collection);
From 52cb775f0ec3674447576bc9640ac1f5b3fb0e5d Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:20 +0000
Subject: [PATCH 27/40] Correct eager collection behavior
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.
---
src/collections/src/Collection.php | 62 +++++++++++-------------------
1 file changed, 22 insertions(+), 40 deletions(-)
diff --git a/src/collections/src/Collection.php b/src/collections/src/Collection.php
index 3995a381c..c75e3d87e 100644
--- a/src/collections/src/Collection.php
+++ b/src/collections/src/Collection.php
@@ -580,12 +580,10 @@ public function keyBy(callable|array|string $keyBy): static
foreach ($this->items as $key => $item) {
$resolvedKey = $keyBy($item, $key);
- if ($resolvedKey instanceof UnitEnum) {
- $resolvedKey = enum_value($resolvedKey);
- }
-
if (is_object($resolvedKey)) {
- $resolvedKey = (string) $resolvedKey;
+ $resolvedKey = $resolvedKey instanceof UnitEnum
+ ? enum_value($resolvedKey)
+ : (string) $resolvedKey;
}
$results[$resolvedKey] = $item;
@@ -711,25 +709,7 @@ public function isEmpty(): bool
return empty($this->items);
}
- /**
- * Determine if the collection contains exactly one item. If a callback is provided, determine if exactly one item matches the condition.
- *
- * @param null|(callable(TValue, TKey): bool) $callback
- */
- public function containsOneItem(?callable $callback = null): bool
- {
- return $this->hasSole($callback);
- }
-
- /**
- * Determine if the collection contains multiple items. If a callback is provided, determine if multiple items match the condition.
- *
- * @param null|(callable(TValue, TKey): bool) $callback
- */
- public function containsManyItems(?callable $callback = null): bool
- {
- return $this->hasMany($callback);
- }
+ // REMOVED: Laravel's deprecated containsOneItem() and containsManyItems(); use hasSole() and hasMany().
/**
* Join all items from the collection using a string. The final items can use a separate glue string.
@@ -921,6 +901,8 @@ public function union(mixed $items): static
/**
* Create a new collection consisting of every n-th element.
*
+ * @return ($step is positive-int ? static : never)
+ *
* @throws InvalidArgumentException
*/
public function nth(int $step, int $offset = 0): static
@@ -1108,7 +1090,7 @@ public function put(mixed $key, mixed $value): static
* Get one or a specified number of items randomly from the collection.
*
* @param null|(callable(self): int)|int|string $number
- * @return ($number is null ? TValue : static)
+ * @return ($number is null ? TValue : static<($preserveKeys is true ? TKey : int), TValue>)
*
* @throws InvalidArgumentException
*/
@@ -1323,7 +1305,7 @@ public function slice(int $offset, ?int $length = null): static
/**
* Split a collection into a certain number of groups.
*
- * @return static
+ * @return ($numberOfGroups is positive-int ? static : never)
*
* @throws InvalidArgumentException
*/
@@ -1365,7 +1347,7 @@ public function split(int $numberOfGroups): static
/**
* Split a collection into a certain number of groups, and fill the first groups completely.
*
- * @return static
+ * @return ($numberOfGroups is positive-int ? static : never)
*
* @throws InvalidArgumentException
*/
@@ -1880,47 +1862,47 @@ public function toBase(): Collection
/**
* Determine if an item exists at an offset.
*
- * @param TKey $key
+ * @param TKey $offset
*/
- public function offsetExists($key): bool
+ public function offsetExists($offset): bool
{
- return isset($this->items[$key]);
+ return isset($this->items[$offset]);
}
/**
* Get an item at a given offset.
*
- * @param TKey $key
+ * @param TKey $offset
* @return TValue
*/
- public function offsetGet($key): mixed
+ public function offsetGet($offset): mixed
{
- return $this->items[$key];
+ return $this->items[$offset];
}
/**
* Set the item at a given offset.
*
- * @param null|TKey $key
+ * @param null|TKey $offset
* @param TValue $value
*/
- public function offsetSet($key, $value): void
+ public function offsetSet($offset, $value): void
{
- if (is_null($key)) {
+ if (is_null($offset)) {
$this->items[] = $value;
} else {
- $this->items[$key] = $value;
+ $this->items[$offset] = $value;
}
}
/**
* Unset the item at a given offset.
*
- * @param TKey $key
+ * @param TKey $offset
*/
- public function offsetUnset($key): void
+ public function offsetUnset($offset): void
{
- unset($this->items[$key]);
+ unset($this->items[$offset]);
}
/**
From 646498b966d24b556c40576d8c4d10e26d0f0a55 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:20 +0000
Subject: [PATCH 28/40] Correct lazy collection behavior
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.
---
src/collections/src/LazyCollection.php | 77 +++++++++++++-------------
1 file changed, 40 insertions(+), 37 deletions(-)
diff --git a/src/collections/src/LazyCollection.php b/src/collections/src/LazyCollection.php
index 259fe0103..6f06f4f9e 100644
--- a/src/collections/src/LazyCollection.php
+++ b/src/collections/src/LazyCollection.php
@@ -43,14 +43,16 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable
/**
* The source from which to generate items.
*
- * @var array|(Closure(): Generator)|static
+ * @var array|(Closure(): iterable)|static
*/
public Closure|self|array $source;
/**
* Create a new lazy collection instance.
*
- * @param null|array|Arrayable|(Closure(): Generator)|iterable|self $source
+ * @param null|array|Arrayable|(Closure(): iterable)|iterable|self $source
+ *
+ * @throws InvalidArgumentException
*/
public function __construct(mixed $source = null)
{
@@ -70,10 +72,10 @@ public function __construct(mixed $source = null)
/**
* Create a new instance of the collection.
*
- * @template TNewKey of array-key
- * @template TNewValue
+ * @template TNewKey of array-key = int
+ * @template TNewValue = mixed
*
- * @param null|array|Arrayable|(Closure(): Generator)|iterable|self $items
+ * @param null|array|Arrayable|(Closure(): iterable)|iterable|self $items
* @return static
*/
protected function newInstance(mixed $items = []): static
@@ -84,10 +86,10 @@ protected function newInstance(mixed $items = []): static
/**
* Create a new collection instance if the value isn't one already.
*
- * @template TMakeKey of array-key
- * @template TMakeValue
+ * @template TMakeKey of array-key = int
+ * @template TMakeValue = mixed
*
- * @param null|array|Arrayable|(Closure(): Generator)|iterable|self $items
+ * @param null|array|Arrayable|(Closure(): iterable)|iterable|self $items
* @return static
*/
public static function make(mixed $items = [], mixed ...$args): static
@@ -98,11 +100,13 @@ public static function make(mixed $items = [], mixed ...$args): static
/**
* Create a collection with the given range.
*
- * @return static
+ * @return ($step is 0 ? never : static)
+ *
+ * @throws InvalidArgumentException
*/
public static function range(int $from, int $to, int $step = 1, mixed ...$args): static
{
- if ($step == 0) {
+ if ($step === 0) {
throw new InvalidArgumentException('Step value cannot be zero.');
}
@@ -125,7 +129,7 @@ public static function range(int $from, int $to, int $step = 1, mixed ...$args):
* @template TTimesValue
*
* @param null|(callable(int): TTimesValue) $callback
- * @return static
+ * @return ($callback is null ? static : static)
*/
public static function times(int|float $number, ?callable $callback = null, mixed ...$args): static
{
@@ -577,7 +581,9 @@ public function keyBy(callable|array|string $keyBy): static
$resolvedKey = $keyBy($item, $key);
if (is_object($resolvedKey)) {
- $resolvedKey = (string) $resolvedKey;
+ $resolvedKey = $resolvedKey instanceof UnitEnum
+ ? enum_value($resolvedKey)
+ : (string) $resolvedKey;
}
yield $resolvedKey => $item;
@@ -591,10 +597,15 @@ public function keyBy(callable|array|string $keyBy): static
public function has(mixed $key): bool
{
$keys = array_flip(is_array($key) ? $key : func_get_args());
- $count = count($keys);
+
+ if ($keys === []) {
+ return true;
+ }
foreach ($this as $key => $value) {
- if (array_key_exists($key, $keys) && --$count == 0) {
+ unset($keys[$key]);
+
+ if ($keys === []) {
return true;
}
}
@@ -666,21 +677,7 @@ public function isEmpty(): bool
return ! $this->getIterator()->valid();
}
- /**
- * Determine if the collection contains a single item.
- */
- public function containsOneItem(?callable $callback = null): bool
- {
- return $this->hasSole($callback);
- }
-
- /**
- * Determine if the collection contains multiple items.
- */
- public function containsManyItems(): bool
- {
- return $this->hasMany();
- }
+ // REMOVED: Laravel's deprecated containsOneItem() and containsManyItems(); use hasSole() and hasMany().
/**
* Join all items from the collection using a string. The final items can use a separate glue string.
@@ -828,9 +825,9 @@ public function multiply(int $multiplier): static
/**
* Create a collection by using this collection for keys and another for its values.
*
- * @template TCombineValue
+ * @template TCombineValue = mixed
*
- * @param Arrayable|(callable(): Generator)|iterable $values
+ * @param Arrayable|(callable(): iterable)|iterable $values
* @return static
* @phpstan-ignore generics.notSubtype (TValue becomes key - only valid when TValue is array-key, but can't express this constraint)
*/
@@ -872,6 +869,8 @@ public function union(mixed $items): static
/**
* Create a new collection consisting of every n-th element.
*
+ * @return ($step is positive-int ? static : never)
+ *
* @throws InvalidArgumentException
*/
public function nth(int $step, int $offset = 0): static
@@ -985,11 +984,11 @@ public function concat(iterable $source): static
/**
* Get one or a specified number of items randomly from the collection.
*
- * @return static|TValue
+ * @return ($number is null ? TValue : static<($preserveKeys is true ? TKey : int), TValue>)
*
* @throws InvalidArgumentException
*/
- public function random(callable|int|string|null $number = null): mixed
+ public function random(callable|int|string|null $number = null, bool $preserveKeys = false): mixed
{
$result = $this->collect()->random(...func_get_args());
@@ -1359,7 +1358,7 @@ public function chunk(int $size, bool $preserveKeys = true): static
/**
* Split a collection into a certain number of groups, and fill the first groups completely.
*
- * @return static
+ * @return ($numberOfGroups is positive-int ? static : never)
*
* @throws InvalidArgumentException
*/
@@ -1768,10 +1767,10 @@ public function count(): int
* @template TIteratorKey of array-key
* @template TIteratorValue
*
- * @param array|(callable(): Generator)|IteratorAggregate $source
+ * @param array|(callable(): mixed)|Iterator|IteratorAggregate $source
* @return Iterator
*/
- protected function makeIterator(IteratorAggregate|array|callable $source): Iterator
+ protected function makeIterator(IteratorAggregate|Iterator|array|callable $source): Iterator
{
if ($source instanceof IteratorAggregate) {
$iterator = $source->getIterator();
@@ -1783,6 +1782,10 @@ protected function makeIterator(IteratorAggregate|array|callable $source): Itera
return new ArrayIterator($source);
}
+ if ($source instanceof Iterator) {
+ return $source;
+ }
+
// Only callable remains at this point
$maybeTraversable = $source();
@@ -1828,7 +1831,7 @@ protected function passthru(string $method, array $params): static
protected function now(): int
{
return class_exists(Carbon::class)
- ? Carbon::now()->timestamp
+ ? Carbon::now()->getTimestamp()
: time();
}
From f9b1c4a4710be202fe4dad9b1571c7f88a7f523c Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:20 +0000
Subject: [PATCH 29/40] Expand shared collection behavior coverage
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.
---
tests/Support/SupportCollectionTest.php | 870 +++++++++++++-----------
1 file changed, 454 insertions(+), 416 deletions(-)
diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php
index afa443b4f..7b61e03b4 100644
--- a/tests/Support/SupportCollectionTest.php
+++ b/tests/Support/SupportCollectionTest.php
@@ -39,14 +39,14 @@
class SupportCollectionTest extends TestCase
{
#[DataProvider('collectionClassProvider')]
- public function testFirstReturnsFirstItemInCollection($collection)
+ public function testFirstReturnsFirstItemInCollection($collection): void
{
$c = new $collection(['foo', 'bar']);
$this->assertSame('foo', $c->first());
}
#[DataProvider('collectionClassProvider')]
- public function testFirstWithCallback($collection)
+ public function testFirstWithCallback($collection): void
{
$data = new $collection(['foo', 'bar', 'baz']);
$result = $data->first(function ($value) {
@@ -56,7 +56,7 @@ public function testFirstWithCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFirstWithCallbackAndDefault($collection)
+ public function testFirstWithCallbackAndDefault($collection): void
{
$data = new $collection(['foo', 'bar']);
$result = $data->first(function ($value) {
@@ -66,7 +66,7 @@ public function testFirstWithCallbackAndDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFirstWithDefaultAndWithoutCallback($collection)
+ public function testFirstWithDefaultAndWithoutCallback($collection): void
{
$data = new $collection;
$result = $data->first(null, 'default');
@@ -78,7 +78,7 @@ public function testFirstWithDefaultAndWithoutCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection)
+ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection): void
{
$collection = new $collection([
['name' => 'foo'],
@@ -91,7 +91,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSoleThrowsExceptionIfNoItemsExist($collection)
+ public function testSoleThrowsExceptionIfNoItemsExist($collection): void
{
$this->expectException(ItemNotFoundException::class);
@@ -104,7 +104,7 @@ public function testSoleThrowsExceptionIfNoItemsExist($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection)
+ public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection): void
{
$this->expectExceptionObject(new MultipleItemsFoundException(2));
@@ -118,7 +118,7 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection)
+ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection): void
{
$data = new $collection(['foo', 'bar', 'baz']);
$result = $data->sole(function ($value) {
@@ -128,7 +128,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback(
}
#[DataProvider('collectionClassProvider')]
- public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection)
+ public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection): void
{
$this->expectException(ItemNotFoundException::class);
@@ -140,7 +140,7 @@ public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($collection)
+ public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($collection): void
{
$this->expectExceptionObject(new MultipleItemsFoundException(2));
@@ -152,7 +152,7 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($coll
}
#[DataProvider('collectionClassProvider')]
- public function testHasSole($collection)
+ public function testHasSole($collection): void
{
$collection = new $collection([
['age' => 2],
@@ -180,7 +180,7 @@ public function testHasSole($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHasMany($collection)
+ public function testHasMany($collection): void
{
$collection = new $collection([
['age' => 2],
@@ -208,7 +208,7 @@ public function testHasMany($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailReturnsFirstItemInCollection($collection)
+ public function testFirstOrFailReturnsFirstItemInCollection($collection): void
{
$collection = new $collection([
['name' => 'foo'],
@@ -221,7 +221,7 @@ public function testFirstOrFailReturnsFirstItemInCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection)
+ public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection): void
{
$this->expectException(ItemNotFoundException::class);
@@ -234,7 +234,7 @@ public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($collection)
+ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($collection): void
{
$collection = new $collection([
['name' => 'foo'],
@@ -246,7 +246,7 @@ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($coll
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection)
+ public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection): void
{
$data = new $collection(['foo', 'bar', 'baz']);
$result = $data->firstOrFail(function ($value) {
@@ -256,7 +256,7 @@ public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCa
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection)
+ public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection): void
{
$this->expectException(ItemNotFoundException::class);
@@ -268,7 +268,7 @@ public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collec
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCallback($collection)
+ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCallback($collection): void
{
$data = new $collection(['foo', 'bar', 'bar']);
@@ -281,7 +281,7 @@ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCa
}
#[DataProvider('collectionClassProvider')]
- public function testFirstOrFailStopsIteratingAtFirstMatch($collection)
+ public function testFirstOrFailStopsIteratingAtFirstMatch($collection): void
{
$data = new $collection([
function () {
@@ -301,7 +301,7 @@ function () {
}
#[DataProvider('collectionClassProvider')]
- public function testFirstWhere($collection)
+ public function testFirstWhere($collection): void
{
$data = new $collection([
['material' => 'paper', 'type' => 'book'],
@@ -320,7 +320,7 @@ public function testFirstWhere($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFirstWhereUsingEnum($collection)
+ public function testFirstWhereUsingEnum($collection): void
{
$data = new $collection([
['id' => 1, 'name' => StaffEnum::Taylor],
@@ -334,7 +334,7 @@ public function testFirstWhereUsingEnum($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testLastReturnsLastItemInCollection($collection)
+ public function testLastReturnsLastItemInCollection($collection): void
{
$c = new $collection(['foo', 'bar']);
$this->assertSame('bar', $c->last());
@@ -344,7 +344,7 @@ public function testLastReturnsLastItemInCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testLastWithCallback($collection)
+ public function testLastWithCallback($collection): void
{
$data = new $collection([100, 200, 300]);
$result = $data->last(function ($value) {
@@ -364,7 +364,7 @@ public function testLastWithCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testLastWithCallbackAndDefault($collection)
+ public function testLastWithCallbackAndDefault($collection): void
{
$data = new $collection(['foo', 'bar']);
$result = $data->last(function ($value) {
@@ -380,14 +380,14 @@ public function testLastWithCallbackAndDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testLastWithDefaultAndWithoutCallback($collection)
+ public function testLastWithDefaultAndWithoutCallback($collection): void
{
$data = new $collection;
$result = $data->last(null, 'default');
$this->assertSame('default', $result);
}
- public function testPopReturnsAndRemovesLastItemInCollection()
+ public function testPopReturnsAndRemovesLastItemInCollection(): void
{
$c = new Collection(['foo', 'bar']);
@@ -395,7 +395,7 @@ public function testPopReturnsAndRemovesLastItemInCollection()
$this->assertSame('foo', $c->first());
}
- public function testPopReturnsAndRemovesLastXItemsInCollection()
+ public function testPopReturnsAndRemovesLastXItemsInCollection(): void
{
$c = new Collection(['foo', 'bar', 'baz']);
@@ -405,7 +405,7 @@ public function testPopReturnsAndRemovesLastXItemsInCollection()
$this->assertEquals(new Collection(['baz', 'bar', 'foo']), (new Collection(['foo', 'bar', 'baz']))->pop(6));
}
- public function testShiftReturnsAndRemovesFirstItemInCollection()
+ public function testShiftReturnsAndRemovesFirstItemInCollection(): void
{
$data = new Collection(['Taylor', 'Otwell']);
@@ -415,7 +415,7 @@ public function testShiftReturnsAndRemovesFirstItemInCollection()
$this->assertNull($data->first());
}
- public function testShiftReturnsAndRemovesFirstXItemsInCollection()
+ public function testShiftReturnsAndRemovesFirstXItemsInCollection(): void
{
$data = new Collection(['foo', 'bar', 'baz']);
@@ -436,7 +436,7 @@ public function testShiftReturnsAndRemovesFirstXItemsInCollection()
(new Collection(['foo', 'bar', 'baz']))->shift(-2);
}
- public function testShiftReturnsNullOnEmptyCollection()
+ public function testShiftReturnsNullOnEmptyCollection(): void
{
$itemFoo = new stdClass;
$itemFoo->text = 'f';
@@ -454,7 +454,7 @@ public function testShiftReturnsNullOnEmptyCollection()
}
#[DataProvider('collectionClassProvider')]
- public function testSliding($collection)
+ public function testSliding($collection): void
{
// Default parameters: $size = 2, $step = 1
$this->assertSame([], $collection::times(0)->sliding()->toArray());
@@ -543,7 +543,7 @@ public function testSliding($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEmptyCollectionIsEmpty($collection)
+ public function testEmptyCollectionIsEmpty($collection): void
{
$c = new $collection;
@@ -551,7 +551,7 @@ public function testEmptyCollectionIsEmpty($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEmptyCollectionIsNotEmpty($collection)
+ public function testEmptyCollectionIsNotEmpty($collection): void
{
$c = new $collection(['foo', 'bar']);
@@ -560,7 +560,7 @@ public function testEmptyCollectionIsNotEmpty($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCollectionIsConstructed($collection)
+ public function testCollectionIsConstructed($collection): void
{
$data = new $collection('foo');
$this->assertSame(['foo'], $data->all());
@@ -579,7 +579,7 @@ public function testCollectionIsConstructed($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSkipMethod($collection)
+ public function testSkipMethod($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6]);
@@ -591,7 +591,7 @@ public function testSkipMethod($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSkipUntil($collection)
+ public function testSkipUntil($collection): void
{
$data = new $collection([1, 1, 2, 2, 3, 3, 4, 4]);
@@ -627,7 +627,7 @@ public function testSkipUntil($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSkipWhile($collection)
+ public function testSkipWhile($collection): void
{
$data = new $collection([1, 1, 2, 2, 3, 3, 4, 4]);
@@ -663,7 +663,7 @@ public function testSkipWhile($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGetArrayableItems($collection)
+ public function testGetArrayableItems($collection): void
{
$data = new $collection;
@@ -701,7 +701,7 @@ public function testGetArrayableItems($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testToArrayCallsToArrayOnEachItemInCollection($collection)
+ public function testToArrayCallsToArrayOnEachItemInCollection($collection): void
{
$item1 = m::mock(Arrayable::class);
$item1->shouldReceive('toArray')->once()->andReturn(['foo.array']);
@@ -713,7 +713,7 @@ public function testToArrayCallsToArrayOnEachItemInCollection($collection)
$this->assertEquals([['foo.array'], ['bar.array']], $results);
}
- public function testLazyReturnsLazyCollection()
+ public function testLazyReturnsLazyCollection(): void
{
$data = new Collection([1, 2, 3, 4, 5]);
@@ -726,7 +726,7 @@ public function testLazyReturnsLazyCollection()
}
#[DataProvider('collectionClassProvider')]
- public function testJsonSerializeCallsToArrayOrJsonSerializeOnEachItemInCollection($collection)
+ public function testJsonSerializeCallsToArrayOrJsonSerializeOnEachItemInCollection($collection): void
{
$item1 = m::mock(JsonSerializable::class);
$item1->shouldReceive('jsonSerialize')->once()->andReturn('foo.json');
@@ -739,7 +739,7 @@ public function testJsonSerializeCallsToArrayOrJsonSerializeOnEachItemInCollecti
}
#[DataProvider('collectionClassProvider')]
- public function testToJsonEncodesTheJsonSerializeResult($collection)
+ public function testToJsonEncodesTheJsonSerializeResult($collection): void
{
$c = $this->getMockBuilder($collection)->onlyMethods(['jsonSerialize'])->getMock();
$c->expects($this->once())->method('jsonSerialize')->willReturn(['foo']);
@@ -748,7 +748,7 @@ public function testToJsonEncodesTheJsonSerializeResult($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testToPrettyJsonEncodesTheJsonSerializeResult($collection)
+ public function testToPrettyJsonEncodesTheJsonSerializeResult($collection): void
{
$c = $this->getMockBuilder($collection)->onlyMethods(['jsonSerialize'])->getMock();
$c->expects($this->once())->method('jsonSerialize')->willReturn(['foo' => 'bar', 'baz' => 'qux']);
@@ -761,7 +761,7 @@ public function testToPrettyJsonEncodesTheJsonSerializeResult($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCastingToStringJsonEncodesTheToArrayResult($collection)
+ public function testCastingToStringJsonEncodesTheToArrayResult($collection): void
{
$c = $this->getMockBuilder($collection)->onlyMethods(['jsonSerialize'])->getMock();
$c->expects($this->once())->method('jsonSerialize')->willReturn(['foo']);
@@ -769,7 +769,7 @@ public function testCastingToStringJsonEncodesTheToArrayResult($collection)
$this->assertJsonStringEqualsJsonString(json_encode(['foo']), (string) $c);
}
- public function testOffsetAccess()
+ public function testOffsetAccess(): void
{
$c = new Collection(['name' => 'taylor']);
$this->assertSame('taylor', $c['name']);
@@ -782,7 +782,7 @@ public function testOffsetAccess()
$this->assertSame('jason', $c[0]);
}
- public function testArrayAccessOffsetExists()
+ public function testArrayAccessOffsetExists(): void
{
$c = new Collection(['foo', 'bar', null]);
$this->assertTrue($c->offsetExists(0));
@@ -790,7 +790,7 @@ public function testArrayAccessOffsetExists()
$this->assertFalse($c->offsetExists(2));
}
- public function testBehavesLikeAnArrayWithArrayAccess()
+ public function testBehavesLikeAnArrayWithArrayAccess(): void
{
// indexed array
$input = ['foo', null];
@@ -811,14 +811,14 @@ public function testBehavesLikeAnArrayWithArrayAccess()
$this->assertEquals($input['k2'], $c['k2']);
}
- public function testArrayAccessOffsetGet()
+ public function testArrayAccessOffsetGet(): void
{
$c = new Collection(['foo', 'bar']);
$this->assertSame('foo', $c->offsetGet(0));
$this->assertSame('bar', $c->offsetGet(1));
}
- public function testArrayAccessOffsetSet()
+ public function testArrayAccessOffsetSet(): void
{
$c = new Collection(['foo', 'foo']);
@@ -829,7 +829,7 @@ public function testArrayAccessOffsetSet()
$this->assertSame('qux', $c[2]);
}
- public function testArrayAccessOffsetUnset()
+ public function testArrayAccessOffsetUnset(): void
{
$c = new Collection(['foo', 'bar']);
@@ -837,7 +837,21 @@ public function testArrayAccessOffsetUnset()
$this->assertFalse(isset($c[1]));
}
- public function testForgetSingleKey()
+ public function testArrayAccessMethodsUseTheInterfaceParameterNames(): void
+ {
+ $collection = new Collection(['name' => 'Taylor']);
+
+ $this->assertTrue($collection->offsetExists(offset: 'name'));
+ $this->assertSame('Taylor', $collection->offsetGet(offset: 'name'));
+
+ $collection->offsetSet(offset: 'framework', value: 'Hypervel');
+ $this->assertSame('Hypervel', $collection->get('framework'));
+
+ $collection->offsetUnset(offset: 'name');
+ $this->assertFalse($collection->has('name'));
+ }
+
+ public function testForgetSingleKey(): void
{
$c = new Collection(['foo', 'bar']);
$c = $c->forget(0)->all();
@@ -851,7 +865,7 @@ public function testForgetSingleKey()
$this->assertTrue(isset($c['baz']));
}
- public function testForgetArrayOfKeys()
+ public function testForgetArrayOfKeys(): void
{
$c = new Collection(['foo', 'bar', 'baz']);
$c = $c->forget([0, 2])->all();
@@ -866,7 +880,7 @@ public function testForgetArrayOfKeys()
$this->assertTrue(isset($c['name']));
}
- public function testForgetCollectionOfKeys()
+ public function testForgetCollectionOfKeys(): void
{
$c = new Collection(['foo', 'bar', 'baz']);
$c = $c->forget(collect([0, 2]))->all();
@@ -882,14 +896,14 @@ public function testForgetCollectionOfKeys()
}
#[DataProvider('collectionClassProvider')]
- public function testCountable($collection)
+ public function testCountable($collection): void
{
$c = new $collection(['foo', 'bar']);
$this->assertCount(2, $c);
}
#[DataProvider('collectionClassProvider')]
- public function testCountByStandalone($collection)
+ public function testCountByStandalone($collection): void
{
$c = new $collection(['foo', 'foo', 'foo', 'bar', 'bar', 'foobar']);
$this->assertEquals(['foo' => 3, 'bar' => 2, 'foobar' => 1], $c->countBy()->all());
@@ -905,7 +919,7 @@ public function testCountByStandalone($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCountByWithKey($collection)
+ public function testCountByWithKey($collection): void
{
$c = new $collection([
['key' => 'a'], ['key' => 'a'], ['key' => 'a'], ['key' => 'a'],
@@ -921,7 +935,7 @@ public function testCountByWithKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCountableByWithCallback($collection)
+ public function testCountableByWithCallback($collection): void
{
$c = new $collection(['alice', 'aaron', 'bob', 'carla']);
$this->assertEquals(['a' => 2, 'b' => 1, 'c' => 1], $c->countBy(function ($name) {
@@ -937,7 +951,7 @@ public function testCountableByWithCallback($collection)
$this->assertEquals(['A' => 3, 'B' => 1], $c->countBy(static fn ($i) => TestStringBackedEnum::from($i))->all());
}
- public function testAdd()
+ public function testAdd(): void
{
$c = new Collection([]);
$this->assertEquals([1], $c->add(1)->values()->all());
@@ -949,33 +963,9 @@ public function testAdd()
$this->assertEquals([1, 2, '', null, false, [], 'name'], $c->add('name')->values()->all());
}
- #[DataProvider('collectionClassProvider')]
- public function testContainsOneItem($collection)
- {
- $this->assertFalse((new $collection([]))->containsOneItem());
- $this->assertTrue((new $collection([1]))->containsOneItem());
- $this->assertFalse((new $collection([1, 2]))->containsOneItem());
-
- $this->assertFalse(collect([1, 2, 2])->containsOneItem(fn ($number) => $number === 2));
- $this->assertTrue(collect(['ant', 'bear', 'cat'])->containsOneItem(fn ($word) => strlen($word) === 4));
- $this->assertFalse(collect(['ant', 'bear', 'cat'])->containsOneItem(fn ($word) => strlen($word) > 4));
- }
+ // REMOVED: Tests for Laravel's deprecated containsOneItem() and containsManyItems(); use hasSole() and hasMany().
- #[DataProvider('collectionClassProvider')]
- public function testContainsManyItems($collection)
- {
- $this->assertFalse((new $collection([]))->containsManyItems());
- $this->assertFalse((new $collection([1]))->containsManyItems());
- $this->assertTrue((new $collection([1, 2]))->containsManyItems());
- $this->assertTrue((new $collection([1, 2, 3]))->containsManyItems());
-
- $this->assertTrue(collect([1, 2, 2])->containsManyItems(fn ($number) => $number === 2));
- $this->assertFalse(collect(['ant', 'bear', 'cat'])->containsManyItems(fn ($word) => strlen($word) === 4));
- $this->assertFalse(collect(['ant', 'bear', 'cat'])->containsManyItems(fn ($word) => strlen($word) > 4));
- $this->assertTrue(collect(['ant', 'bear', 'cat'])->containsManyItems(fn ($word) => strlen($word) === 3));
- }
-
- public function testIterable()
+ public function testIterable(): void
{
$c = new Collection(['foo']);
$this->assertInstanceOf(ArrayIterator::class, $c->getIterator());
@@ -983,14 +973,14 @@ public function testIterable()
}
#[DataProvider('collectionClassProvider')]
- public function testCachingIterator($collection)
+ public function testCachingIterator($collection): void
{
$c = new $collection(['foo']);
$this->assertInstanceOf(CachingIterator::class, $c->getCachingIterator());
}
#[DataProvider('collectionClassProvider')]
- public function testFilter($collection)
+ public function testFilter($collection): void
{
$c = new $collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]);
$this->assertEquals([1 => ['id' => 2, 'name' => 'World']], $c->filter(function ($item) {
@@ -1010,7 +1000,7 @@ public function testFilter($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderKeyBy($collection)
+ public function testHigherOrderKeyBy($collection): void
{
$c = new $collection([
['id' => 'id1', 'name' => 'first'],
@@ -1021,7 +1011,7 @@ public function testHigherOrderKeyBy($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderUnique($collection)
+ public function testHigherOrderUnique($collection): void
{
$c = new $collection([
['id' => '1', 'name' => 'first'],
@@ -1032,7 +1022,7 @@ public function testHigherOrderUnique($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderFilter($collection)
+ public function testHigherOrderFilter($collection): void
{
$c = new $collection([
new class {
@@ -1057,7 +1047,7 @@ public function active()
}
#[DataProvider('collectionClassProvider')]
- public function testWhere($collection)
+ public function testWhere($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
@@ -1195,7 +1185,7 @@ public function testWhere($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereStrict($collection)
+ public function testWhereStrict($collection): void
{
$c = new $collection([['v' => 3], ['v' => '3']]);
@@ -1206,7 +1196,7 @@ public function testWhereStrict($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereInstanceOf($collection)
+ public function testWhereInstanceOf($collection): void
{
$c = new $collection([new stdClass, new stdClass, new $collection, new stdClass, new Str]);
$this->assertCount(3, $c->whereInstanceOf(stdClass::class));
@@ -1215,7 +1205,7 @@ public function testWhereInstanceOf($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereIn($collection)
+ public function testWhereIn($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
$this->assertEquals([['v' => 1], ['v' => 3], ['v' => '3']], $c->whereIn('v', [1, 3])->values()->all());
@@ -1224,14 +1214,14 @@ public function testWhereIn($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereInStrict($collection)
+ public function testWhereInStrict($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
$this->assertEquals([['v' => 1], ['v' => 3]], $c->whereInStrict('v', [1, 3])->values()->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNotIn($collection)
+ public function testWhereNotIn($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
$this->assertEquals([['v' => 2], ['v' => 4]], $c->whereNotIn('v', [1, 3])->values()->all());
@@ -1239,14 +1229,14 @@ public function testWhereNotIn($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNotInStrict($collection)
+ public function testWhereNotInStrict($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
$this->assertEquals([['v' => 2], ['v' => '3'], ['v' => 4]], $c->whereNotInStrict('v', [1, 3])->values()->all());
}
#[DataProvider('collectionClassProvider')]
- public function testValues($collection)
+ public function testValues($collection): void
{
$c = new $collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]);
$this->assertEquals([['id' => 2, 'name' => 'World']], $c->filter(function ($item) {
@@ -1255,14 +1245,14 @@ public function testValues($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testValuesResetKey($collection)
+ public function testValuesResetKey($collection): void
{
$data = new $collection([1 => 'a', 2 => 'b', 3 => 'c']);
$this->assertEquals([0 => 'a', 1 => 'b', 2 => 'c'], $data->values()->all());
}
#[DataProvider('collectionClassProvider')]
- public function testValue($collection)
+ public function testValue($collection): void
{
$c = new $collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]);
@@ -1280,7 +1270,7 @@ public function testValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testValueUsingEnum($collection)
+ public function testValueUsingEnum($collection): void
{
$c = new $collection([['id' => 1, 'name' => StaffEnum::Taylor], ['id' => 2, 'name' => StaffEnum::Joe]]);
@@ -1289,7 +1279,7 @@ public function testValueUsingEnum($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testValueWithNegativeValue($collection)
+ public function testValueWithNegativeValue($collection): void
{
$c = new $collection([['id' => 1, 'balance' => 0], ['id' => 2, 'balance' => 200]]);
@@ -1313,7 +1303,7 @@ public function testValueWithNegativeValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testValueWithObjects($collection)
+ public function testValueWithObjects($collection): void
{
$c = new $collection([
literal(id: 1),
@@ -1333,7 +1323,7 @@ public function testValueWithObjects($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testBetween($collection)
+ public function testBetween($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
@@ -1346,7 +1336,7 @@ public function testBetween($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNotBetween($collection)
+ public function testWhereNotBetween($collection): void
{
$c = new $collection([['v' => 1], ['v' => 2], ['v' => 3], ['v' => '3'], ['v' => 4]]);
@@ -1356,7 +1346,7 @@ public function testWhereNotBetween($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFlatten($collection)
+ public function testFlatten($collection): void
{
// Flat arrays are unaffected
$c = new $collection(['#foo', '#bar', '#baz']);
@@ -1392,7 +1382,7 @@ public function testFlatten($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFlattenWithDepth($collection)
+ public function testFlattenWithDepth($collection): void
{
// No depth flattens recursively
$c = new $collection([['#foo', ['#bar', ['#baz']]], '#zap']);
@@ -1407,7 +1397,7 @@ public function testFlattenWithDepth($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFlattenIgnoresKeys($collection)
+ public function testFlattenIgnoresKeys($collection): void
{
// No depth ignores keys
$c = new $collection(['#foo', ['key' => '#bar'], ['key' => '#baz'], 'key' => '#zap']);
@@ -1419,42 +1409,42 @@ public function testFlattenIgnoresKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMergeNull($collection)
+ public function testMergeNull($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'Hello'], $c->merge(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testMergeArray($collection)
+ public function testMergeArray($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'Hello', 'id' => 1], $c->merge(['id' => 1])->all());
}
#[DataProvider('collectionClassProvider')]
- public function testMergeCollection($collection)
+ public function testMergeCollection($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'World', 'id' => 1], $c->merge(new $collection(['name' => 'World', 'id' => 1]))->all());
}
#[DataProvider('collectionClassProvider')]
- public function testMergeRecursiveNull($collection)
+ public function testMergeRecursiveNull($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'Hello'], $c->mergeRecursive(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testMergeRecursiveArray($collection)
+ public function testMergeRecursiveArray($collection): void
{
$c = new $collection(['name' => 'Hello', 'id' => 1]);
$this->assertEquals(['name' => 'Hello', 'id' => [1, 2]], $c->mergeRecursive(['id' => 2])->all());
}
#[DataProvider('collectionClassProvider')]
- public function testMergeRecursiveCollection($collection)
+ public function testMergeRecursiveCollection($collection): void
{
$c = new $collection(['name' => 'Hello', 'id' => 1, 'meta' => ['tags' => ['a', 'b'], 'roles' => 'admin']]);
$this->assertEquals(
@@ -1464,7 +1454,7 @@ public function testMergeRecursiveCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMultiplyCollection($collection)
+ public function testMultiplyCollection($collection): void
{
$c = new $collection(['Hello', 1, ['tags' => ['a', 'b'], 'admin']]);
@@ -1483,14 +1473,14 @@ public function testMultiplyCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReplaceNull($collection)
+ public function testReplaceNull($collection): void
{
$c = new $collection(['a', 'b', 'c']);
$this->assertEquals(['a', 'b', 'c'], $c->replace(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testReplaceArray($collection)
+ public function testReplaceArray($collection): void
{
$c = new $collection(['a', 'b', 'c']);
$this->assertEquals(['a', 'd', 'e'], $c->replace([1 => 'd', 2 => 'e'])->all());
@@ -1503,7 +1493,7 @@ public function testReplaceArray($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReplaceCollection($collection)
+ public function testReplaceCollection($collection): void
{
$c = new $collection(['a', 'b', 'c']);
$this->assertEquals(
@@ -1525,14 +1515,14 @@ public function testReplaceCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReplaceRecursiveNull($collection)
+ public function testReplaceRecursiveNull($collection): void
{
$c = new $collection(['a', 'b', ['c', 'd']]);
$this->assertEquals(['a', 'b', ['c', 'd']], $c->replaceRecursive(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testReplaceRecursiveArray($collection)
+ public function testReplaceRecursiveArray($collection): void
{
$c = new $collection(['a', 'b', ['c', 'd']]);
$this->assertEquals(['z', 'b', ['c', 'e']], $c->replaceRecursive(['z', 2 => [1 => 'e']])->all());
@@ -1542,7 +1532,7 @@ public function testReplaceRecursiveArray($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReplaceRecursiveCollection($collection)
+ public function testReplaceRecursiveCollection($collection): void
{
$c = new $collection(['a', 'b', ['c', 'd']]);
$this->assertEquals(
@@ -1552,35 +1542,35 @@ public function testReplaceRecursiveCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnionNull($collection)
+ public function testUnionNull($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'Hello'], $c->union(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testUnionArray($collection)
+ public function testUnionArray($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'Hello', 'id' => 1], $c->union(['id' => 1])->all());
}
#[DataProvider('collectionClassProvider')]
- public function testUnionCollection($collection)
+ public function testUnionCollection($collection): void
{
$c = new $collection(['name' => 'Hello']);
$this->assertEquals(['name' => 'Hello', 'id' => 1], $c->union(new $collection(['name' => 'World', 'id' => 1]))->all());
}
#[DataProvider('collectionClassProvider')]
- public function testDiffCollection($collection)
+ public function testDiffCollection($collection): void
{
$c = new $collection(['id' => 1, 'first_word' => 'Hello']);
$this->assertEquals(['id' => 1], $c->diff(new $collection(['first_word' => 'Hello', 'last_word' => 'World']))->all());
}
#[DataProvider('collectionClassProvider')]
- public function testDiffUsingWithCollection($collection)
+ public function testDiffUsingWithCollection($collection): void
{
$c = new $collection(['en_GB', 'fr', 'HR']);
// demonstrate that diff won't support case insensitivity
@@ -1590,21 +1580,21 @@ public function testDiffUsingWithCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDiffUsingWithNull($collection)
+ public function testDiffUsingWithNull($collection): void
{
$c = new $collection(['en_GB', 'fr', 'HR']);
$this->assertEquals(['en_GB', 'fr', 'HR'], $c->diffUsing(null, 'strcasecmp')->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testDiffNull($collection)
+ public function testDiffNull($collection): void
{
$c = new $collection(['id' => 1, 'first_word' => 'Hello']);
$this->assertEquals(['id' => 1, 'first_word' => 'Hello'], $c->diff(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testDiffKeys($collection)
+ public function testDiffKeys($collection): void
{
$c1 = new $collection(['id' => 1, 'first_word' => 'Hello']);
$c2 = new $collection(['id' => 123, 'foo_bar' => 'Hello']);
@@ -1612,7 +1602,7 @@ public function testDiffKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDiffKeysUsing($collection)
+ public function testDiffKeysUsing($collection): void
{
$c1 = new $collection(['id' => 1, 'first_word' => 'Hello']);
$c2 = new $collection(['ID' => 123, 'foo_bar' => 'Hello']);
@@ -1623,7 +1613,7 @@ public function testDiffKeysUsing($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDiffAssoc($collection)
+ public function testDiffAssoc($collection): void
{
$c1 = new $collection(['id' => 1, 'first_word' => 'Hello', 'not_affected' => 'value']);
$c2 = new $collection(['id' => 123, 'foo_bar' => 'Hello', 'not_affected' => 'value']);
@@ -1631,7 +1621,7 @@ public function testDiffAssoc($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDiffAssocUsing($collection)
+ public function testDiffAssocUsing($collection): void
{
$c1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']);
$c2 = new $collection(['A' => 'green', 'yellow', 'red']);
@@ -1642,7 +1632,7 @@ public function testDiffAssocUsing($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDuplicates($collection)
+ public function testDuplicates($collection): void
{
$duplicates = $collection::make([1, 2, 1, 'laravel', null, 'laravel', 'php', null])->duplicates()->all();
$this->assertSame([2 => 1, 5 => 'laravel', 7 => null], $duplicates);
@@ -1662,7 +1652,7 @@ public function testDuplicates($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDuplicatesWithKey($collection)
+ public function testDuplicatesWithKey($collection): void
{
$items = [['framework' => 'vue'], ['framework' => 'laravel'], ['framework' => 'laravel']];
$duplicates = $collection::make($items)->duplicates('framework')->all();
@@ -1675,7 +1665,7 @@ public function testDuplicatesWithKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDuplicatesWithCallback($collection)
+ public function testDuplicatesWithCallback($collection): void
{
$items = [['framework' => 'vue'], ['framework' => 'laravel'], ['framework' => 'laravel']];
$duplicates = $collection::make($items)->duplicates(function ($item) {
@@ -1685,7 +1675,7 @@ public function testDuplicatesWithCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDuplicatesWithStrict($collection)
+ public function testDuplicatesWithStrict($collection): void
{
$duplicates = $collection::make([1, 2, 1, 'laravel', null, 'laravel', 'php', null])->duplicatesStrict()->all();
$this->assertSame([2 => 1, 5 => 'laravel', 7 => null], $duplicates);
@@ -1705,7 +1695,7 @@ public function testDuplicatesWithStrict($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEach($collection)
+ public function testEach($collection): void
{
$c = new $collection($original = [1, 2, 'foo' => 'bar', 'bam' => 'baz']);
@@ -1726,7 +1716,7 @@ public function testEach($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEachSpread($collection)
+ public function testEachSpread($collection): void
{
$c = new $collection([[1, 'a'], [2, 'b']]);
@@ -1759,21 +1749,21 @@ public function testEachSpread($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectNull($collection)
+ public function testIntersectNull($collection): void
{
$c = new $collection(['id' => 1, 'first_word' => 'Hello']);
$this->assertEquals([], $c->intersect(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectCollection($collection)
+ public function testIntersectCollection($collection): void
{
$c = new $collection(['id' => 1, 'first_word' => 'Hello']);
$this->assertEquals(['first_word' => 'Hello'], $c->intersect(new $collection(['first_world' => 'Hello', 'last_word' => 'World']))->all());
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectUsingWithNull($collection)
+ public function testIntersectUsingWithNull($collection): void
{
$collect = new $collection(['green', 'brown', 'blue']);
@@ -1781,7 +1771,7 @@ public function testIntersectUsingWithNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectUsingCollection($collection)
+ public function testIntersectUsingCollection($collection): void
{
$collect = new $collection(['green', 'brown', 'blue']);
@@ -1789,7 +1779,7 @@ public function testIntersectUsingCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectAssocWithNull($collection)
+ public function testIntersectAssocWithNull($collection): void
{
$array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']);
@@ -1797,7 +1787,7 @@ public function testIntersectAssocWithNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectAssocCollection($collection)
+ public function testIntersectAssocCollection($collection): void
{
$array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']);
$array2 = new $collection(['a' => 'green', 'b' => 'yellow', 'blue', 'red']);
@@ -1806,7 +1796,7 @@ public function testIntersectAssocCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectAssocUsingWithNull($collection)
+ public function testIntersectAssocUsingWithNull($collection): void
{
$array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']);
@@ -1814,7 +1804,7 @@ public function testIntersectAssocUsingWithNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectAssocUsingCollection($collection)
+ public function testIntersectAssocUsingCollection($collection): void
{
$array1 = new $collection(['a' => 'green', 'b' => 'brown', 'c' => 'blue', 'red']);
$array2 = new $collection(['a' => 'GREEN', 'B' => 'brown', 'yellow', 'red']);
@@ -1823,14 +1813,14 @@ public function testIntersectAssocUsingCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectByKeysNull($collection)
+ public function testIntersectByKeysNull($collection): void
{
$c = new $collection(['name' => 'Mateus', 'age' => 18]);
$this->assertEquals([], $c->intersectByKeys(null)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testIntersectByKeys($collection)
+ public function testIntersectByKeys($collection): void
{
$c = new $collection(['name' => 'Mateus', 'age' => 18]);
$this->assertEquals(['name' => 'Mateus'], $c->intersectByKeys(new $collection(['name' => 'Mateus', 'surname' => 'Guimaraes']))->all());
@@ -1840,7 +1830,7 @@ public function testIntersectByKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnique($collection)
+ public function testUnique($collection): void
{
$c = new $collection(['Hello', 'World', 'World']);
$this->assertEquals(['Hello', 'World'], $c->unique()->all());
@@ -1850,7 +1840,7 @@ public function testUnique($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUniqueWithCallback($collection)
+ public function testUniqueWithCallback($collection): void
{
$c = new $collection([
1 => ['id' => 1, 'first' => 'Taylor', 'last' => 'Otwell'],
@@ -1883,7 +1873,7 @@ public function testUniqueWithCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUniqueStrict($collection)
+ public function testUniqueStrict($collection): void
{
$c = new $collection([
[
@@ -1913,7 +1903,7 @@ public function testUniqueStrict($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCollapse($collection)
+ public function testCollapse($collection): void
{
// Normal case: a two-dimensional array with different elements
$data = new $collection([[$object1 = new stdClass], [$object2 = new stdClass]]);
@@ -1938,14 +1928,14 @@ public function testCollapse($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCollapseWithNestedCollections($collection)
+ public function testCollapseWithNestedCollections($collection): void
{
$data = new $collection([new $collection([1, 2, 3]), new $collection([4, 5, 6])]);
$this->assertEquals([1, 2, 3, 4, 5, 6], $data->collapse()->all());
}
#[DataProvider('collectionClassProvider')]
- public function testCollapseWithKeys($collection)
+ public function testCollapseWithKeys($collection): void
{
$data = new $collection([[1 => 'a'], [3 => 'c'], [2 => 'b'], 'drop']);
$this->assertEquals([1 => 'a', 3 => 'c', 2 => 'b'], $data->collapseWithKeys()->all());
@@ -1956,14 +1946,14 @@ public function testCollapseWithKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCollapseWithKeysOnNestedCollections($collection)
+ public function testCollapseWithKeysOnNestedCollections($collection): void
{
$data = new $collection([new $collection(['a' => '1a', 'b' => '1b']), new $collection(['b' => '2b', 'c' => '2c']), 'drop']);
$this->assertEquals(['a' => '1a', 'b' => '2b', 'c' => '2c'], $data->collapseWithKeys()->all());
}
#[DataProvider('collectionClassProvider')]
- public function testJoin($collection)
+ public function testJoin($collection): void
{
$this->assertSame('a, b, c', (new $collection(['a', 'b', 'c']))->join(', '));
@@ -1977,7 +1967,7 @@ public function testJoin($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCrossJoin($collection)
+ public function testCrossJoin($collection): void
{
// Cross join with an array
$this->assertEquals(
@@ -2007,7 +1997,7 @@ public function testCrossJoin($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSort($collection)
+ public function testSort($collection): void
{
$data = (new $collection([5, 3, 1, 2, 4]))->sort();
$this->assertEquals([1, 2, 3, 4, 5], $data->values()->all());
@@ -2026,7 +2016,7 @@ public function testSort($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSortDesc($collection)
+ public function testSortDesc($collection): void
{
$data = (new $collection([5, 3, 1, 2, 4]))->sortDesc();
$this->assertEquals([5, 4, 3, 2, 1], $data->values()->all());
@@ -2045,7 +2035,7 @@ public function testSortDesc($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSortWithCallback($collection)
+ public function testSortWithCallback($collection): void
{
$data = (new $collection([5, 3, 1, 2, 4]))->sort(function ($a, $b) {
if ($a === $b) {
@@ -2088,7 +2078,7 @@ function ($x) {
}
#[DataProvider('collectionClassProvider')]
- public function testSortByString($collection)
+ public function testSortByString($collection): void
{
$data = new $collection([['name' => 'taylor'], ['name' => 'dayle']]);
$data = $data->sortBy('name', SORT_STRING);
@@ -2111,7 +2101,7 @@ public function testSortByIntegerKey($collection): void
}
#[DataProvider('collectionClassProvider')]
- public function testSortByCallableString($collection)
+ public function testSortByCallableString($collection): void
{
$data = new $collection([['sort' => 2], ['sort' => 1]]);
$data = $data->sortBy([['sort', 'asc']]);
@@ -2120,7 +2110,7 @@ public function testSortByCallableString($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSortByCallableStringDesc($collection)
+ public function testSortByCallableStringDesc($collection): void
{
$data = new $collection([['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]);
$data = $data->sortByDesc(['id']);
@@ -2135,7 +2125,7 @@ public function testSortByCallableStringDesc($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSortByAlwaysReturnsAssoc($collection)
+ public function testSortByAlwaysReturnsAssoc($collection): void
{
$data = new $collection(['a' => 'taylor', 'b' => 'dayle']);
$data = $data->sortBy(function ($x) {
@@ -2278,7 +2268,7 @@ public function testSortByMany($collection): void
}
#[DataProvider('collectionClassProvider')]
- public function testNaturalSortByManyWithNull($collection)
+ public function testNaturalSortByManyWithNull($collection): void
{
$itemFoo = new stdClass;
$itemFoo->first = 'f';
@@ -2307,7 +2297,7 @@ public function testSortKeys($collection): void
}
#[DataProvider('collectionClassProvider')]
- public function testSortKeysDesc($collection)
+ public function testSortKeysDesc($collection): void
{
$data = new $collection(['a' => 'taylor', 'b' => 'dayle']);
@@ -2315,7 +2305,7 @@ public function testSortKeysDesc($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSortKeysUsing($collection)
+ public function testSortKeysUsing($collection): void
{
$data = new $collection(['B' => 'dayle', 'a' => 'taylor']);
@@ -2323,7 +2313,7 @@ public function testSortKeysUsing($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReverse($collection)
+ public function testReverse($collection): void
{
$data = new $collection(['zaeed', 'alan']);
$reversed = $data->reverse();
@@ -2337,14 +2327,14 @@ public function testReverse($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFlip($collection)
+ public function testFlip($collection): void
{
$data = new $collection(['name' => 'taylor', 'framework' => 'laravel']);
$this->assertEquals(['taylor' => 'name', 'laravel' => 'framework'], $data->flip()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testChunk($collection)
+ public function testChunk($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$data = $data->chunk(3);
@@ -2357,7 +2347,7 @@ public function testChunk($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testChunkWhenGivenZeroAsSize($collection)
+ public function testChunkWhenGivenZeroAsSize($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
@@ -2368,7 +2358,7 @@ public function testChunkWhenGivenZeroAsSize($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testChunkWhenGivenLessThanZero($collection)
+ public function testChunkWhenGivenLessThanZero($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
@@ -2379,7 +2369,7 @@ public function testChunkWhenGivenLessThanZero($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testChunkPreservingKeys($collection)
+ public function testChunkPreservingKeys($collection): void
{
$data = new $collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]);
@@ -2397,7 +2387,7 @@ public function testChunkPreservingKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitIn($collection)
+ public function testSplitIn($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$data = $data->splitIn(3);
@@ -2411,7 +2401,7 @@ public function testSplitIn($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testChunkWhileOnEqualElements($collection)
+ public function testChunkWhileOnEqualElements($collection): void
{
$data = (new $collection(['A', 'A', 'B', 'B', 'C', 'C', 'C']))
->chunkWhile(function ($current, $key, $chunk) {
@@ -2426,7 +2416,7 @@ public function testChunkWhileOnEqualElements($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testChunkWhileOnContiguouslyIncreasingIntegers($collection)
+ public function testChunkWhileOnContiguouslyIncreasingIntegers($collection): void
{
$data = (new $collection([1, 4, 9, 10, 11, 12, 15, 16, 19, 20, 21]))
->chunkWhile(function ($current, $key, $chunk) {
@@ -2443,7 +2433,7 @@ public function testChunkWhileOnContiguouslyIncreasingIntegers($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testChunkWhilePreservingStringKeys($collection)
+ public function testChunkWhilePreservingStringKeys($collection): void
{
$data = (new $collection(['a' => 1, 'b' => 1, 'c' => 2, 'd' => 2, 'e' => 3, 'f' => 3, 'g' => 3]))
->chunkWhile(function ($current, $key, $chunk) {
@@ -2458,7 +2448,7 @@ public function testChunkWhilePreservingStringKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEvery($collection)
+ public function testEvery($collection): void
{
$c = new $collection([]);
$this->assertTrue($c->every('key', 'value'));
@@ -2488,7 +2478,7 @@ public function testEvery($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testExcept($collection)
+ public function testExcept($collection): void
{
$data = new $collection(['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com']);
@@ -2503,14 +2493,14 @@ public function testExcept($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testExceptSelf($collection)
+ public function testExceptSelf($collection): void
{
$data = new $collection(['first' => 'Taylor', 'last' => 'Otwell']);
$this->assertEquals(['first' => 'Taylor', 'last' => 'Otwell'], $data->except($data)->all());
}
#[DataProvider('collectionClassProvider')]
- public function testPluckWithArrayAndObjectValues($collection)
+ public function testPluckWithArrayAndObjectValues($collection): void
{
$data = new $collection([(object) ['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]);
$this->assertEquals(['taylor' => 'foo', 'dayle' => 'bar'], $data->pluck('email', 'name')->all());
@@ -2518,7 +2508,7 @@ public function testPluckWithArrayAndObjectValues($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPluckWithArrayAccessValues($collection)
+ public function testPluckWithArrayAccessValues($collection): void
{
$data = new $collection([
new TestArrayAccessImplementation(['name' => 'taylor', 'email' => 'foo']),
@@ -2530,7 +2520,7 @@ public function testPluckWithArrayAccessValues($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPluckWithDotNotation($collection)
+ public function testPluckWithDotNotation($collection): void
{
$data = new $collection([
[
@@ -2551,7 +2541,7 @@ public function testPluckWithDotNotation($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPluckWithClosure($collection)
+ public function testPluckWithClosure($collection): void
{
$data = new $collection([
[
@@ -2573,7 +2563,7 @@ public function testPluckWithClosure($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPluckDuplicateKeysExist($collection)
+ public function testPluckDuplicateKeysExist($collection): void
{
$data = new $collection([
['brand' => 'Tesla', 'color' => 'red'],
@@ -2586,7 +2576,7 @@ public function testPluckDuplicateKeysExist($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHas($collection)
+ public function testHas($collection): void
{
$data = new $collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']);
$this->assertTrue($data->has('first'));
@@ -2597,7 +2587,7 @@ public function testHas($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHasAny($collection)
+ public function testHasAny($collection): void
{
$data = new $collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']);
@@ -2611,7 +2601,7 @@ public function testHasAny($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testImplode($collection)
+ public function testImplode($collection): void
{
$data = new $collection([['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]);
$this->assertSame('foobar', $data->implode('email'));
@@ -2639,7 +2629,7 @@ public function testImplode($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testImplodeModels($collection)
+ public function testImplodeModels($collection): void
{
$model = new class extends Model {
};
@@ -2654,14 +2644,14 @@ public function testImplodeModels($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTake($collection)
+ public function testTake($collection): void
{
$data = new $collection(['taylor', 'dayle', 'shawn']);
$data = $data->take(2);
$this->assertEquals(['taylor', 'dayle'], $data->all());
}
- public function testGetOrPut()
+ public function testGetOrPut(): void
{
$data = new Collection(['name' => 'taylor', 'email' => 'foo']);
@@ -2692,7 +2682,7 @@ public function testGetOrPut()
$this->assertSame('male', $data->get('gender'));
}
- public function testGetOrPutWithNoKey()
+ public function testGetOrPutWithNoKey(): void
{
$data = new Collection(['taylor', 'shawn']);
$this->assertSame('dayle', $data->getOrPut(null, 'dayle'));
@@ -2704,14 +2694,14 @@ public function testGetOrPutWithNoKey()
$this->assertSame(['taylor', '' => 'shawn'], $data->all());
}
- public function testPut()
+ public function testPut(): void
{
$data = new Collection(['name' => 'taylor', 'email' => 'foo']);
$data = $data->put('name', 'dayle');
$this->assertEquals(['name' => 'dayle', 'email' => 'foo'], $data->all());
}
- public function testPutWithNoKey()
+ public function testPutWithNoKey(): void
{
$data = new Collection(['taylor', 'shawn']);
$data = $data->put(null, 'dayle');
@@ -2719,7 +2709,7 @@ public function testPutWithNoKey()
}
#[DataProvider('collectionClassProvider')]
- public function testRandom($collection)
+ public function testRandom($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6]);
@@ -2767,7 +2757,7 @@ public function testRandom($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testRandomOnEmptyCollection($collection)
+ public function testRandomOnEmptyCollection($collection): void
{
$data = new $collection;
@@ -2781,7 +2771,7 @@ public function testRandomOnEmptyCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeLast($collection)
+ public function testTakeLast($collection): void
{
$data = new $collection(['taylor', 'dayle', 'shawn']);
$data = $data->take(-2);
@@ -2789,7 +2779,7 @@ public function testTakeLast($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeUntilUsingValue($collection)
+ public function testTakeUntilUsingValue($collection): void
{
$data = new $collection([1, 2, 3, 4]);
@@ -2799,7 +2789,7 @@ public function testTakeUntilUsingValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeUntilUsingCallback($collection)
+ public function testTakeUntilUsingCallback($collection): void
{
$data = new $collection([1, 2, 3, 4]);
@@ -2811,7 +2801,7 @@ public function testTakeUntilUsingCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeUntilReturnsAllItemsForUnmetValue($collection)
+ public function testTakeUntilReturnsAllItemsForUnmetValue($collection): void
{
$data = new $collection([1, 2, 3, 4]);
@@ -2827,7 +2817,7 @@ public function testTakeUntilReturnsAllItemsForUnmetValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeUntilCanBeProxied($collection)
+ public function testTakeUntilCanBeProxied($collection): void
{
$data = new $collection([
new TestSupportCollectionHigherOrderItem('Adam'),
@@ -2843,7 +2833,7 @@ public function testTakeUntilCanBeProxied($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeWhileUsingValue($collection)
+ public function testTakeWhileUsingValue($collection): void
{
$data = new $collection([1, 1, 2, 2, 3, 3]);
@@ -2853,7 +2843,7 @@ public function testTakeWhileUsingValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeWhileUsingCallback($collection)
+ public function testTakeWhileUsingCallback($collection): void
{
$data = new $collection([1, 2, 3, 4]);
@@ -2865,7 +2855,7 @@ public function testTakeWhileUsingCallback($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeWhileReturnsNoItemsForUnmetValue($collection)
+ public function testTakeWhileReturnsNoItemsForUnmetValue($collection): void
{
$data = new $collection([1, 2, 3, 4]);
@@ -2881,7 +2871,7 @@ public function testTakeWhileReturnsNoItemsForUnmetValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTakeWhileCanBeProxied($collection)
+ public function testTakeWhileCanBeProxied($collection): void
{
$data = new $collection([
new TestSupportCollectionHigherOrderItem('Adam'),
@@ -2898,7 +2888,7 @@ public function testTakeWhileCanBeProxied($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMacroable($collection)
+ public function testMacroable($collection): void
{
// Foo() macro : unique values starting with A
$collection::macro('foo', function () {
@@ -2915,7 +2905,7 @@ public function testMacroable($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCanAddMethodsToProxy($collection)
+ public function testCanAddMethodsToProxy($collection): void
{
$collection::macro('adults', function ($callback) {
return $this->filter(function ($item) use ($callback) {
@@ -2931,14 +2921,14 @@ public function testCanAddMethodsToProxy($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMakeMethod($collection)
+ public function testMakeMethod($collection): void
{
$data = $collection::make('foo');
$this->assertEquals(['foo'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testMakeMethodFromNull($collection)
+ public function testMakeMethodFromNull($collection): void
{
$data = $collection::make(null);
$this->assertEquals([], $data->all());
@@ -2948,7 +2938,7 @@ public function testMakeMethodFromNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMakeMethodFromCollection($collection)
+ public function testMakeMethodFromCollection($collection): void
{
$firstCollection = $collection::make(['foo' => 'bar']);
$secondCollection = $collection::make($firstCollection);
@@ -2956,56 +2946,56 @@ public function testMakeMethodFromCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMakeMethodFromArray($collection)
+ public function testMakeMethodFromArray($collection): void
{
$data = $collection::make(['foo' => 'bar']);
$this->assertEquals(['foo' => 'bar'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithScalar($collection)
+ public function testWrapWithScalar($collection): void
{
$data = $collection::wrap('foo');
$this->assertEquals(['foo'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithArray($collection)
+ public function testWrapWithArray($collection): void
{
$data = $collection::wrap(['foo']);
$this->assertEquals(['foo'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithArrayable($collection)
+ public function testWrapWithArrayable($collection): void
{
$data = $collection::wrap($o = new TestArrayableObject);
$this->assertEquals([$o], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithJsonable($collection)
+ public function testWrapWithJsonable($collection): void
{
$data = $collection::wrap($o = new TestJsonableObject);
$this->assertEquals([$o], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithJsonSerialize($collection)
+ public function testWrapWithJsonSerialize($collection): void
{
$data = $collection::wrap($o = new TestJsonSerializeObject);
$this->assertEquals([$o], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithCollectionClass($collection)
+ public function testWrapWithCollectionClass($collection): void
{
$data = $collection::wrap($collection::make(['foo']));
$this->assertEquals(['foo'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testWrapWithCollectionSubclass($collection)
+ public function testWrapWithCollectionSubclass($collection): void
{
$data = TestCollectionSubclass::wrap($collection::make(['foo']));
$this->assertEquals(['foo'], $data->all());
@@ -3013,26 +3003,26 @@ public function testWrapWithCollectionSubclass($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnwrapCollection($collection)
+ public function testUnwrapCollection($collection): void
{
$data = new $collection(['foo']);
$this->assertEquals(['foo'], $collection::unwrap($data));
}
#[DataProvider('collectionClassProvider')]
- public function testUnwrapCollectionWithArray($collection)
+ public function testUnwrapCollectionWithArray($collection): void
{
$this->assertEquals(['foo'], $collection::unwrap(['foo']));
}
#[DataProvider('collectionClassProvider')]
- public function testUnwrapCollectionWithScalar($collection)
+ public function testUnwrapCollectionWithScalar($collection): void
{
$this->assertSame('foo', $collection::unwrap('foo'));
}
#[DataProvider('collectionClassProvider')]
- public function testEmptyMethod($collection)
+ public function testEmptyMethod($collection): void
{
$collection = $collection::empty();
@@ -3040,7 +3030,7 @@ public function testEmptyMethod($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTimesMethod($collection)
+ public function testTimesMethod($collection): void
{
$two = $collection::times(2, function ($number) {
return 'slug-' . $number;
@@ -3063,7 +3053,7 @@ public function testTimesMethod($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testRangeMethod($collection)
+ public function testRangeMethod($collection): void
{
$this->assertSame(
[1, 2, 3, 4, 5],
@@ -3097,7 +3087,7 @@ public function testRangeMethod($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFromJson($collection)
+ public function testFromJson($collection): void
{
$json = json_encode($array = ['foo' => 'bar', 'baz' => 'quz']);
@@ -3107,7 +3097,7 @@ public function testFromJson($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFromJsonWithDepth($collection)
+ public function testFromJsonWithDepth($collection): void
{
$json = json_encode(['foo' => ['baz' => ['quz']], 'bar' => 'baz']);
@@ -3118,7 +3108,7 @@ public function testFromJsonWithDepth($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFromJsonWithFlags($collection)
+ public function testFromJsonWithFlags($collection): void
{
$instance = $collection::fromJson('{"int":99999999999999999999999}', 512, JSON_BIGINT_AS_STRING);
@@ -3126,7 +3116,7 @@ public function testFromJsonWithFlags($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMakeFromObject($collection)
+ public function testConstructMakeFromObject($collection): void
{
$object = new stdClass;
$object->foo = 'bar';
@@ -3135,14 +3125,14 @@ public function testConstructMakeFromObject($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMethod($collection)
+ public function testConstructMethod($collection): void
{
$data = new $collection('foo');
$this->assertEquals(['foo'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMethodFromNull($collection)
+ public function testConstructMethodFromNull($collection): void
{
$data = new $collection(null);
$this->assertEquals([], $data->all());
@@ -3152,7 +3142,7 @@ public function testConstructMethodFromNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMethodFromCollection($collection)
+ public function testConstructMethodFromCollection($collection): void
{
$firstCollection = new $collection(['foo' => 'bar']);
$secondCollection = new $collection($firstCollection);
@@ -3160,14 +3150,14 @@ public function testConstructMethodFromCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMethodFromArray($collection)
+ public function testConstructMethodFromArray($collection): void
{
$data = new $collection(['foo' => 'bar']);
$this->assertEquals(['foo' => 'bar'], $data->all());
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMethodFromObject($collection)
+ public function testConstructMethodFromObject($collection): void
{
$object = new stdClass;
$object->foo = 'bar';
@@ -3176,7 +3166,7 @@ public function testConstructMethodFromObject($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConstructMethodFromWeakMap($collection)
+ public function testConstructMethodFromWeakMap($collection): void
{
$map = new WeakMap;
$object = new stdClass;
@@ -3186,7 +3176,7 @@ public function testConstructMethodFromWeakMap($collection)
$this->assertEquals([3], $data->all());
}
- public function testSplice()
+ public function testSplice(): void
{
$data = new Collection(['foo', 'baz']);
$data->splice(1);
@@ -3215,7 +3205,7 @@ public function testSplice()
}
#[DataProvider('collectionClassProvider')]
- public function testGetPluckValueWithAccessors($collection)
+ public function testGetPluckValueWithAccessors($collection): void
{
$model = new TestAccessorEloquentTestStub(['some' => 'foo']);
$modelTwo = new TestAccessorEloquentTestStub(['some' => 'bar']);
@@ -3225,7 +3215,7 @@ public function testGetPluckValueWithAccessors($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMap($collection)
+ public function testMap($collection): void
{
$data = new $collection([1, 2, 3]);
$mapped = $data->map(function ($item, $key) {
@@ -3242,7 +3232,7 @@ public function testMap($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapSpread($collection)
+ public function testMapSpread($collection): void
{
$c = new $collection([[1, 'a'], [2, 'b']]);
@@ -3264,7 +3254,7 @@ public function testMapSpread($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testFlatMap($collection)
+ public function testFlatMap($collection): void
{
$data = new $collection([
['name' => 'taylor', 'hobbies' => ['programming', 'basketball']],
@@ -3277,7 +3267,7 @@ public function testFlatMap($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapToDictionary($collection)
+ public function testMapToDictionary($collection): void
{
$data = new $collection([
['id' => 1, 'name' => 'A'],
@@ -3296,7 +3286,7 @@ public function testMapToDictionary($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapToDictionaryWithNumericKeys($collection)
+ public function testMapToDictionaryWithNumericKeys($collection): void
{
$data = new $collection([1, 2, 3, 2, 1]);
@@ -3308,7 +3298,7 @@ public function testMapToDictionaryWithNumericKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapToGroups($collection)
+ public function testMapToGroups($collection): void
{
$data = new $collection([
['id' => 1, 'name' => 'A'],
@@ -3327,7 +3317,7 @@ public function testMapToGroups($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapToGroupsWithNumericKeys($collection)
+ public function testMapToGroupsWithNumericKeys($collection): void
{
$data = new $collection([1, 2, 3, 2, 1]);
@@ -3340,7 +3330,7 @@ public function testMapToGroupsWithNumericKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapWithKeys($collection)
+ public function testMapWithKeys($collection): void
{
$data = new $collection([
['name' => 'Blastoise', 'type' => 'Water', 'idx' => 9],
@@ -3357,7 +3347,7 @@ public function testMapWithKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapWithKeysIntegerKeys($collection)
+ public function testMapWithKeysIntegerKeys($collection): void
{
$data = new $collection([
['id' => 1, 'name' => 'A'],
@@ -3374,7 +3364,7 @@ public function testMapWithKeysIntegerKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapWithKeysMultipleRows($collection)
+ public function testMapWithKeysMultipleRows($collection): void
{
$data = new $collection([
['id' => 1, 'name' => 'A'],
@@ -3398,7 +3388,7 @@ public function testMapWithKeysMultipleRows($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapWithKeysCallbackKey($collection)
+ public function testMapWithKeysCallbackKey($collection): void
{
$data = new $collection([
3 => ['id' => 1, 'name' => 'A'],
@@ -3415,7 +3405,7 @@ public function testMapWithKeysCallbackKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapInto($collection)
+ public function testMapInto($collection): void
{
$data = new $collection([
'first', 'second',
@@ -3428,7 +3418,7 @@ public function testMapInto($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapIntoWithIntBackedEnums($collection)
+ public function testMapIntoWithIntBackedEnums($collection): void
{
$data = new $collection([
1, 2,
@@ -3441,7 +3431,7 @@ public function testMapIntoWithIntBackedEnums($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapIntoWithStringBackedEnums($collection)
+ public function testMapIntoWithStringBackedEnums($collection): void
{
$data = new $collection([
'A', 'B',
@@ -3454,7 +3444,7 @@ public function testMapIntoWithStringBackedEnums($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testNth($collection)
+ public function testNth($collection): void
{
$data = new $collection([
6 => 'a',
@@ -3479,7 +3469,7 @@ public function testNth($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testNthThrowsExceptionForInvalidStep($collection)
+ public function testNthThrowsExceptionForInvalidStep($collection): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Step value must be at least 1.');
@@ -3488,7 +3478,7 @@ public function testNthThrowsExceptionForInvalidStep($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testNthThrowsExceptionForNegativeStep($collection)
+ public function testNthThrowsExceptionForNegativeStep($collection): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Step value must be at least 1.');
@@ -3497,7 +3487,7 @@ public function testNthThrowsExceptionForNegativeStep($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitThrowsExceptionForInvalidNumberOfGroups($collection)
+ public function testSplitThrowsExceptionForInvalidNumberOfGroups($collection): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Number of groups must be at least 1.');
@@ -3506,7 +3496,7 @@ public function testSplitThrowsExceptionForInvalidNumberOfGroups($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitThrowsExceptionForNegativeNumberOfGroups($collection)
+ public function testSplitThrowsExceptionForNegativeNumberOfGroups($collection): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Number of groups must be at least 1.');
@@ -3515,7 +3505,7 @@ public function testSplitThrowsExceptionForNegativeNumberOfGroups($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitInThrowsExceptionForInvalidNumberOfGroups($collection)
+ public function testSplitInThrowsExceptionForInvalidNumberOfGroups($collection): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Number of groups must be at least 1.');
@@ -3524,7 +3514,7 @@ public function testSplitInThrowsExceptionForInvalidNumberOfGroups($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitInThrowsExceptionForNegativeNumberOfGroups($collection)
+ public function testSplitInThrowsExceptionForNegativeNumberOfGroups($collection): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Number of groups must be at least 1.');
@@ -3533,7 +3523,7 @@ public function testSplitInThrowsExceptionForNegativeNumberOfGroups($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMapWithKeysOverwritingKeys($collection)
+ public function testMapWithKeysOverwritingKeys($collection): void
{
$data = new $collection([
['id' => 1, 'name' => 'A'],
@@ -3552,7 +3542,7 @@ public function testMapWithKeysOverwritingKeys($collection)
);
}
- public function testTransform()
+ public function testTransform(): void
{
$data = new Collection(['first' => 'taylor', 'last' => 'otwell']);
$data->transform(function ($item, $key) {
@@ -3562,7 +3552,7 @@ public function testTransform()
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByAttribute($collection)
+ public function testGroupByAttribute($collection): void
{
$data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]);
@@ -3574,7 +3564,7 @@ public function testGroupByAttribute($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByAttributeWithStringableKey($collection)
+ public function testGroupByAttributeWithStringableKey($collection): void
{
$data = new $collection($payload = [
['name' => new Stringable('Laravel'), 'url' => '1'],
@@ -3595,7 +3585,7 @@ public function __toString()
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByAttributeWithEnumKey($collection)
+ public function testGroupByAttributeWithEnumKey($collection): void
{
$data = new $collection($payload = [
['name' => TestEnum::A, 'url' => '1'],
@@ -3611,7 +3601,7 @@ public function testGroupByAttributeWithEnumKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByCallable($collection)
+ public function testGroupByCallable($collection): void
{
$data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]);
@@ -3622,18 +3612,18 @@ public function testGroupByCallable($collection)
$this->assertEquals([1 => [['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1']], 2 => [['rating' => 2, 'url' => '2']]], $result->toArray());
}
- public function sortByRating(array $value)
+ public function sortByRating(array $value): int
{
return $value['rating'];
}
- public function sortByUrl(array $value)
+ public function sortByUrl(array $value): string
{
return $value['url'];
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByAttributeWithBackedEnumKey($collection)
+ public function testGroupByAttributeWithBackedEnumKey($collection): void
{
$data = new $collection([
['rating' => TestBackedEnum::A, 'url' => '1'],
@@ -3645,7 +3635,7 @@ public function testGroupByAttributeWithBackedEnumKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByAttributePreservingKeys($collection)
+ public function testGroupByAttributePreservingKeys($collection): void
{
$data = new $collection([10 => ['rating' => 1, 'url' => '1'], 20 => ['rating' => 1, 'url' => '1'], 30 => ['rating' => 2, 'url' => '2']]);
@@ -3660,7 +3650,7 @@ public function testGroupByAttributePreservingKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByClosureWhereItemsHaveSingleGroup($collection)
+ public function testGroupByClosureWhereItemsHaveSingleGroup($collection): void
{
$data = new $collection([['rating' => 1, 'url' => '1'], ['rating' => 1, 'url' => '1'], ['rating' => 2, 'url' => '2']]);
@@ -3672,7 +3662,7 @@ public function testGroupByClosureWhereItemsHaveSingleGroup($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByClosureWhereItemsHaveSingleGroupPreservingKeys($collection)
+ public function testGroupByClosureWhereItemsHaveSingleGroupPreservingKeys($collection): void
{
$data = new $collection([10 => ['rating' => 1, 'url' => '1'], 20 => ['rating' => 1, 'url' => '1'], 30 => ['rating' => 2, 'url' => '2']]);
@@ -3689,7 +3679,7 @@ public function testGroupByClosureWhereItemsHaveSingleGroupPreservingKeys($colle
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByClosureWhereItemsHaveMultipleGroups($collection)
+ public function testGroupByClosureWhereItemsHaveMultipleGroups($collection): void
{
$data = new $collection([
['user' => 1, 'roles' => ['Role_1', 'Role_3']],
@@ -3719,7 +3709,7 @@ public function testGroupByClosureWhereItemsHaveMultipleGroups($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($collection)
+ public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($collection): void
{
$data = new $collection([
10 => ['user' => 1, 'roles' => ['Role_1', 'Role_3']],
@@ -3749,7 +3739,7 @@ public function testGroupByClosureWhereItemsHaveMultipleGroupsPreservingKeys($co
}
#[DataProvider('collectionClassProvider')]
- public function testGroupByMultiLevelAndClosurePreservingKeys($collection)
+ public function testGroupByMultiLevelAndClosurePreservingKeys($collection): void
{
$data = new $collection([
10 => ['user' => 1, 'skilllevel' => 1, 'roles' => ['Role_1', 'Role_3']],
@@ -3792,7 +3782,7 @@ function ($item) {
}
#[DataProvider('collectionClassProvider')]
- public function testKeyByAttribute($collection)
+ public function testKeyByAttribute($collection): void
{
$data = new $collection([['rating' => 1, 'name' => '1'], ['rating' => 2, 'name' => '2'], ['rating' => 3, 'name' => '3']]);
@@ -3806,7 +3796,21 @@ public function testKeyByAttribute($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testKeyByClosure($collection)
+ public function testKeyByBackedEnum($collection): void
+ {
+ $data = new $collection([
+ ['id' => 1, 'status' => TestStringBackedEnum::A],
+ ['id' => 2, 'status' => TestStringBackedEnum::B],
+ ]);
+
+ $this->assertEquals([
+ TestStringBackedEnum::A->value => ['id' => 1, 'status' => TestStringBackedEnum::A],
+ TestStringBackedEnum::B->value => ['id' => 2, 'status' => TestStringBackedEnum::B],
+ ], $data->keyBy('status')->all());
+ }
+
+ #[DataProvider('collectionClassProvider')]
+ public function testKeyByClosure($collection): void
{
$data = new $collection([
['firstname' => 'Taylor', 'lastname' => 'Otwell', 'locale' => 'US'],
@@ -3822,7 +3826,7 @@ public function testKeyByClosure($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testKeyByObject($collection)
+ public function testKeyByObject($collection): void
{
$data = new $collection([
['firstname' => 'Taylor', 'lastname' => 'Otwell', 'locale' => 'US'],
@@ -3838,7 +3842,7 @@ public function testKeyByObject($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testContains($collection)
+ public function testContains($collection): void
{
$c = new $collection([1, 3, 5]);
@@ -3897,7 +3901,7 @@ public function testContains($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDoesntContain($collection)
+ public function testDoesntContain($collection): void
{
$c = new $collection([1, 3, 5]);
@@ -3956,7 +3960,7 @@ public function testDoesntContain($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDoesntContainStrict($collection)
+ public function testDoesntContainStrict($collection): void
{
$c = new $collection([1, 3, 5, '02']);
@@ -4004,7 +4008,7 @@ public function testDoesntContainStrict($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSome($collection)
+ public function testSome($collection): void
{
$c = new $collection([1, 3, 5]);
@@ -4043,7 +4047,7 @@ public function testSome($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testContainsStrict($collection)
+ public function testContainsStrict($collection): void
{
$c = new $collection([1, 3, 5, '02']);
@@ -4089,7 +4093,7 @@ public function testContainsStrict($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testContainsWithOperator($collection)
+ public function testContainsWithOperator($collection): void
{
$c = new $collection([['v' => 1], ['v' => 3], ['v' => '4'], ['v' => 5]]);
@@ -4100,7 +4104,7 @@ public function testContainsWithOperator($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGettingSumFromCollection($collection)
+ public function testGettingSumFromCollection($collection): void
{
$c = new $collection([(object) ['foo' => 50], (object) ['foo' => 50]]);
$this->assertEquals(100, $c->sum('foo'));
@@ -4112,21 +4116,21 @@ public function testGettingSumFromCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCanSumValuesWithoutACallback($collection)
+ public function testCanSumValuesWithoutACallback($collection): void
{
$c = new $collection([1, 2, 3, 4, 5]);
$this->assertEquals(15, $c->sum());
}
#[DataProvider('collectionClassProvider')]
- public function testGettingSumFromEmptyCollection($collection)
+ public function testGettingSumFromEmptyCollection($collection): void
{
$c = new $collection;
$this->assertEquals(0, $c->sum('foo'));
}
#[DataProvider('collectionClassProvider')]
- public function testValueRetrieverAcceptsDotNotation($collection)
+ public function testValueRetrieverAcceptsDotNotation($collection): void
{
$c = new $collection([
(object) ['id' => 1, 'foo' => ['bar' => 'B']], (object) ['id' => 2, 'foo' => ['bar' => 'A']],
@@ -4136,7 +4140,7 @@ public function testValueRetrieverAcceptsDotNotation($collection)
$this->assertEquals([2, 1], $c->pluck('id')->all());
}
- public function testPullRetrievesItemFromCollection()
+ public function testPullRetrievesItemFromCollection(): void
{
$c = new Collection(['foo', 'bar']);
@@ -4149,7 +4153,7 @@ public function testPullRetrievesItemFromCollection()
$this->assertNull($c->pull(2));
}
- public function testPullRemovesItemFromCollection()
+ public function testPullRemovesItemFromCollection(): void
{
$c = new Collection(['foo', 'bar']);
$c->pull(0);
@@ -4158,7 +4162,7 @@ public function testPullRemovesItemFromCollection()
$this->assertEquals([], $c->all());
}
- public function testPullRemovesItemFromNestedCollection()
+ public function testPullRemovesItemFromNestedCollection(): void
{
$nestedCollection = new Collection([
new Collection([
@@ -4185,7 +4189,7 @@ public function testPullRemovesItemFromNestedCollection()
$this->assertEquals($expectedArray, $actualArray);
}
- public function testPullReturnsDefault()
+ public function testPullReturnsDefault(): void
{
$c = new Collection([]);
$value = $c->pull(0, 'foo');
@@ -4193,7 +4197,7 @@ public function testPullReturnsDefault()
}
#[DataProvider('collectionClassProvider')]
- public function testRejectRemovesElementsPassingTruthTest($collection)
+ public function testRejectRemovesElementsPassingTruthTest($collection): void
{
$c = new $collection(['foo', 'bar']);
$this->assertEquals(['foo'], $c->reject('bar')->values()->all());
@@ -4221,7 +4225,7 @@ public function testRejectRemovesElementsPassingTruthTest($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testRejectWithoutAnArgumentRemovesTruthyValues($collection)
+ public function testRejectWithoutAnArgumentRemovesTruthyValues($collection): void
{
$data1 = new $collection([
false,
@@ -4242,7 +4246,7 @@ public function testRejectWithoutAnArgumentRemovesTruthyValues($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSearchReturnsIndexOfFirstFoundItem($collection)
+ public function testSearchReturnsIndexOfFirstFoundItem($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 2, 5, 'foo' => 'bar']);
@@ -4258,7 +4262,7 @@ public function testSearchReturnsIndexOfFirstFoundItem($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSearchInStrictMode($collection)
+ public function testSearchInStrictMode($collection): void
{
$c = new $collection([false, 0, 1, [], '']);
$this->assertFalse($c->search('false', true));
@@ -4271,7 +4275,7 @@ public function testSearchInStrictMode($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSearchReturnsFalseWhenItemIsNotFound($collection)
+ public function testSearchReturnsFalseWhenItemIsNotFound($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']);
@@ -4286,7 +4290,7 @@ public function testSearchReturnsFalseWhenItemIsNotFound($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testBeforeReturnsItemBeforeTheGivenItem($collection)
+ public function testBeforeReturnsItemBeforeTheGivenItem($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 2, 5, 'name' => 'taylor', 'framework' => 'laravel']);
@@ -4303,7 +4307,7 @@ public function testBeforeReturnsItemBeforeTheGivenItem($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testBeforeInStrictMode($collection)
+ public function testBeforeInStrictMode($collection): void
{
$c = new $collection([false, 0, 1, [], '']);
$this->assertNull($c->before('false', true));
@@ -4316,7 +4320,7 @@ public function testBeforeInStrictMode($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testBeforeReturnsNullWhenItemIsNotFound($collection)
+ public function testBeforeReturnsNullWhenItemIsNotFound($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']);
@@ -4331,7 +4335,7 @@ public function testBeforeReturnsNullWhenItemIsNotFound($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testBeforeReturnsNullWhenItemOnTheFirstitem($collection)
+ public function testBeforeReturnsNullWhenItemOnTheFirstitem($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']);
@@ -4345,7 +4349,7 @@ public function testBeforeReturnsNullWhenItemOnTheFirstitem($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testAfterReturnsItemAfterTheGivenItem($collection)
+ public function testAfterReturnsItemAfterTheGivenItem($collection): void
{
$c = new $collection([1, 2, 3, 4, 2, 5, 'name' => 'taylor', 'framework' => 'laravel']);
@@ -4365,7 +4369,7 @@ public function testAfterReturnsItemAfterTheGivenItem($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testAfterInStrictMode($collection)
+ public function testAfterInStrictMode($collection): void
{
$c = new $collection([false, 0, 1, [], '']);
@@ -4378,7 +4382,7 @@ public function testAfterInStrictMode($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testAfterReturnsNullWhenItemIsNotFound($collection)
+ public function testAfterReturnsNullWhenItemIsNotFound($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']);
@@ -4393,7 +4397,7 @@ public function testAfterReturnsNullWhenItemIsNotFound($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testAfterReturnsNullWhenItemOnTheLastItem($collection)
+ public function testAfterReturnsNullWhenItemOnTheLastItem($collection): void
{
$c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']);
@@ -4407,7 +4411,7 @@ public function testAfterReturnsNullWhenItemOnTheLastItem($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testKeys($collection)
+ public function testKeys($collection): void
{
$c = new $collection(['name' => 'taylor', 'framework' => 'laravel']);
$this->assertEquals(['name', 'framework'], $c->keys()->all());
@@ -4417,7 +4421,7 @@ public function testKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPaginate($collection)
+ public function testPaginate($collection): void
{
$c = new $collection(['one', 'two', 'three', 'four']);
$this->assertEquals(['one', 'two'], $c->forPage(0, 2)->all());
@@ -4427,7 +4431,7 @@ public function testPaginate($collection)
}
#[IgnoreDeprecations]
- public function testPrepend()
+ public function testPrepend(): void
{
$c = new Collection(['one', 'two', 'three', 'four']);
$this->assertEquals(
@@ -4454,7 +4458,7 @@ public function testPrepend()
);
}
- public function testPushWithOneItem()
+ public function testPushWithOneItem(): void
{
$expected = [
0 => 4,
@@ -4473,7 +4477,7 @@ public function testPushWithOneItem()
$this->assertSame($expected, $actual);
}
- public function testPushWithMultipleItems()
+ public function testPushWithMultipleItems(): void
{
$expected = [
0 => 4,
@@ -4499,7 +4503,7 @@ public function testPushWithMultipleItems()
$this->assertSame($expected, $actual);
}
- public function testUnshiftWithOneItem()
+ public function testUnshiftWithOneItem(): void
{
$expected = [
0 => 'Jonny from Laroe',
@@ -4518,7 +4522,7 @@ public function testUnshiftWithOneItem()
$this->assertSame($expected, $actual);
}
- public function testUnshiftWithMultipleItems()
+ public function testUnshiftWithMultipleItems(): void
{
$expected = [
0 => 'a',
@@ -4545,7 +4549,7 @@ public function testUnshiftWithMultipleItems()
}
#[DataProvider('collectionClassProvider')]
- public function testZip($collection)
+ public function testZip($collection): void
{
$c = new $collection([1, 2, 3]);
$c = $c->zip(new $collection([4, 5, 6]));
@@ -4574,7 +4578,7 @@ public function testZip($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPadPadsArrayWithValue($collection)
+ public function testPadPadsArrayWithValue($collection): void
{
$c = new $collection([1, 2, 3]);
$c = $c->pad(4, 0);
@@ -4594,7 +4598,7 @@ public function testPadPadsArrayWithValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGettingMaxItemsFromCollection($collection)
+ public function testGettingMaxItemsFromCollection($collection): void
{
$c = new $collection([(object) ['foo' => 10], (object) ['foo' => 20]]);
$this->assertEquals(20, $c->max(function ($item) {
@@ -4615,7 +4619,7 @@ public function testGettingMaxItemsFromCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGettingMinItemsFromCollection($collection)
+ public function testGettingMinItemsFromCollection($collection): void
{
$c = new $collection([(object) ['foo' => 10], (object) ['foo' => 20]]);
$this->assertEquals(10, $c->min(function ($item) {
@@ -4646,7 +4650,7 @@ public function testGettingMinItemsFromCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testOnly($collection)
+ public function testOnly($collection): void
{
$data = new $collection(['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com']);
@@ -4661,7 +4665,7 @@ public function testOnly($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSelectWithArrays($collection)
+ public function testSelectWithArrays($collection): void
{
$data = new $collection([
['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com'],
@@ -4690,7 +4694,7 @@ public function testSelectWithArrays($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSelectWithArrayAccess($collection)
+ public function testSelectWithArrayAccess($collection): void
{
$data = new $collection([
new TestArrayAccessImplementation(['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com']),
@@ -4719,7 +4723,7 @@ public function testSelectWithArrayAccess($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSelectWithObjects($collection)
+ public function testSelectWithObjects($collection): void
{
$data = new $collection([
(object) ['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com'],
@@ -4748,7 +4752,7 @@ public function testSelectWithObjects($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testGettingAvgItemsFromCollection($collection)
+ public function testGettingAvgItemsFromCollection($collection): void
{
$c = new $collection([(object) ['foo' => 10], (object) ['foo' => 20]]);
$this->assertEquals(15, $c->avg(function ($item) {
@@ -4793,7 +4797,7 @@ public function testGettingAvgItemsFromCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testJsonSerialize($collection)
+ public function testJsonSerialize($collection): void
{
$c = new $collection([
new TestArrayableObject,
@@ -4813,7 +4817,7 @@ public function testJsonSerialize($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCombineWithArray($collection)
+ public function testCombineWithArray($collection): void
{
$c = new $collection([1, 2, 3]);
$actual = $c->combine([4, 5, 6])->toArray();
@@ -4854,7 +4858,7 @@ public function testCombineWithArray($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCombineWithCollection($collection)
+ public function testCombineWithCollection($collection): void
{
$expected = [
1 => 4,
@@ -4870,7 +4874,7 @@ public function testCombineWithCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConcatWithArray($collection)
+ public function testConcatWithArray($collection): void
{
$expected = [
0 => 4,
@@ -4896,7 +4900,7 @@ public function testConcatWithArray($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testConcatWithCollection($collection)
+ public function testConcatWithCollection($collection): void
{
$expected = [
0 => 4,
@@ -4924,7 +4928,7 @@ public function testConcatWithCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDump($collection)
+ public function testDump($collection): void
{
$log = new Collection;
@@ -4940,7 +4944,7 @@ public function testDump($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReduce($collection)
+ public function testReduce($collection): void
{
$data = new $collection([1, 2, 3]);
$this->assertEquals(6, $data->reduce(function ($carry, $element) {
@@ -4957,7 +4961,43 @@ public function testReduce($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReduceSpread($collection)
+ public function testReduceInto($collection): void
+ {
+ $data = new $collection([1, 2, 3]);
+ $this->assertSame(6, $data->reduceInto(0, function (&$result, $element): void {
+ $result += $element;
+ }));
+
+ $data = new $collection([
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ ]);
+ $this->assertSame('foobarbazqux', $data->reduceInto('', function (&$result, $element, $key): void {
+ $result .= $key . $element;
+ }));
+
+ $data = new $collection([1, 2, 3, 4, 5]);
+ $result = $data->reduceInto([], function (&$result, $value): void {
+ if ($value % 2 === 0) {
+ $result[] = $value;
+ }
+ });
+
+ $this->assertSame([2, 4], $result);
+ }
+
+ #[DataProvider('collectionClassProvider')]
+ public function testSumPassesItemKeysToTheCallback($collection): void
+ {
+ $data = new $collection(['first' => 10, 'second' => 20]);
+
+ $this->assertSame(31, $data->sum(
+ static fn (int $value, string $key): int => $value + ($key === 'first' ? 1 : 0),
+ ));
+ }
+
+ #[DataProvider('collectionClassProvider')]
+ public function testReduceSpread($collection): void
{
$data = new $collection([-1, 0, 1, 2, 3, 4, 5]);
@@ -4975,7 +5015,7 @@ public function testReduceSpread($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($collection)
+ public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($collection): void
{
$data = new $collection([1]);
@@ -4987,7 +5027,7 @@ public function testReduceSpreadThrowsAnExceptionIfReducerDoesNotReturnAnArray($
}
#[DataProvider('collectionClassProvider')]
- public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($collection)
+ public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($collection): void
{
$this->expectException(InvalidArgumentException::class);
@@ -4996,7 +5036,7 @@ public function testRandomThrowsAnExceptionUsingAmountBiggerThanCollectionSize($
}
#[DataProvider('collectionClassProvider')]
- public function testPipe($collection)
+ public function testPipe($collection): void
{
$data = new $collection([1, 2, 3]);
@@ -5006,7 +5046,7 @@ public function testPipe($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPipeInto($collection)
+ public function testPipeInto($collection): void
{
$data = new $collection([
'first', 'second',
@@ -5018,7 +5058,7 @@ public function testPipeInto($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPipeThrough($collection)
+ public function testPipeThrough($collection): void
{
$data = new $collection([1, 2, 3]);
@@ -5035,7 +5075,7 @@ function ($data) {
}
#[DataProvider('collectionClassProvider')]
- public function testMedianValueWithArrayCollection($collection)
+ public function testMedianValueWithArrayCollection($collection): void
{
$data = new $collection([1, 2, 2, 4]);
@@ -5043,7 +5083,7 @@ public function testMedianValueWithArrayCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMedianValueByKey($collection)
+ public function testMedianValueByKey($collection): void
{
$data = new $collection([
(object) ['foo' => 1],
@@ -5055,7 +5095,7 @@ public function testMedianValueByKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMedianOnCollectionWithNull($collection)
+ public function testMedianOnCollectionWithNull($collection): void
{
$data = new $collection([
(object) ['foo' => 1],
@@ -5067,7 +5107,7 @@ public function testMedianOnCollectionWithNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEvenMedianCollection($collection)
+ public function testEvenMedianCollection($collection): void
{
$data = new $collection([
(object) ['foo' => 0],
@@ -5077,7 +5117,7 @@ public function testEvenMedianCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMedianOutOfOrderCollection($collection)
+ public function testMedianOutOfOrderCollection($collection): void
{
$data = new $collection([
(object) ['foo' => 0],
@@ -5088,21 +5128,21 @@ public function testMedianOutOfOrderCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testMedianOnEmptyCollectionReturnsNull($collection)
+ public function testMedianOnEmptyCollectionReturnsNull($collection): void
{
$data = new $collection;
$this->assertNull($data->median());
}
#[DataProvider('collectionClassProvider')]
- public function testModeOnNullCollection($collection)
+ public function testModeOnNullCollection($collection): void
{
$data = new $collection;
$this->assertNull($data->mode());
}
#[DataProvider('collectionClassProvider')]
- public function testMode($collection)
+ public function testMode($collection): void
{
$data = new $collection([1, 2, 3, 4, 4, 5]);
$this->assertIsArray($data->mode());
@@ -5110,7 +5150,7 @@ public function testMode($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testModeValueByKey($collection)
+ public function testModeValueByKey($collection): void
{
$data = new $collection([
(object) ['foo' => 1],
@@ -5129,84 +5169,84 @@ public function testModeValueByKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWithMultipleModeValues($collection)
+ public function testWithMultipleModeValues($collection): void
{
$data = new $collection([1, 2, 2, 1]);
$this->assertEquals([1, 2], $data->mode());
}
#[DataProvider('collectionClassProvider')]
- public function testSliceOffset($collection)
+ public function testSliceOffset($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]);
$this->assertEquals([4, 5, 6, 7, 8], $data->slice(3)->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testSliceNegativeOffset($collection)
+ public function testSliceNegativeOffset($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]);
$this->assertEquals([6, 7, 8], $data->slice(-3)->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testSliceOffsetAndLength($collection)
+ public function testSliceOffsetAndLength($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]);
$this->assertEquals([4, 5, 6], $data->slice(3, 3)->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testSliceOffsetAndNegativeLength($collection)
+ public function testSliceOffsetAndNegativeLength($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]);
$this->assertEquals([4, 5, 6, 7], $data->slice(3, -1)->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testSliceNegativeOffsetAndLength($collection)
+ public function testSliceNegativeOffsetAndLength($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]);
$this->assertEquals([4, 5, 6], $data->slice(-5, 3)->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testSliceNegativeOffsetAndNegativeLength($collection)
+ public function testSliceNegativeOffsetAndNegativeLength($collection): void
{
$data = new $collection([1, 2, 3, 4, 5, 6, 7, 8]);
$this->assertEquals([3, 4, 5, 6], $data->slice(-6, -2)->values()->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testCollectionFromTraversable($collection)
+ public function testCollectionFromTraversable($collection): void
{
$data = new $collection(new ArrayObject([1, 2, 3]));
$this->assertEquals([1, 2, 3], $data->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testCollectionFromTraversableWithKeys($collection)
+ public function testCollectionFromTraversableWithKeys($collection): void
{
$data = new $collection(new ArrayObject(['foo' => 1, 'bar' => 2, 'baz' => 3]));
$this->assertEquals(['foo' => 1, 'bar' => 2, 'baz' => 3], $data->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testCollectionFromEnum($collection)
+ public function testCollectionFromEnum($collection): void
{
$data = new $collection(TestEnum::A);
$this->assertEquals([TestEnum::A], $data->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testCollectionFromBackedEnum($collection)
+ public function testCollectionFromBackedEnum($collection): void
{
$data = new $collection(TestBackedEnum::A);
$this->assertEquals([TestBackedEnum::A], $data->toArray());
}
#[DataProvider('collectionClassProvider')]
- public function testSplitCollectionWithADivisibleCount($collection)
+ public function testSplitCollectionWithADivisibleCount($collection): void
{
$data = new $collection(['a', 'b', 'c', 'd']);
$split = $data->split(2);
@@ -5237,7 +5277,7 @@ public function testSplitCollectionWithADivisibleCount($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitCollectionWithAnUndivisableCount($collection)
+ public function testSplitCollectionWithAnUndivisableCount($collection): void
{
$data = new $collection(['a', 'b', 'c']);
$split = $data->split(2);
@@ -5254,7 +5294,7 @@ public function testSplitCollectionWithAnUndivisableCount($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitCollectionWithCountLessThenDivisor($collection)
+ public function testSplitCollectionWithCountLessThenDivisor($collection): void
{
$data = new $collection(['a']);
$split = $data->split(2);
@@ -5271,7 +5311,7 @@ public function testSplitCollectionWithCountLessThenDivisor($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitCollectionIntoThreeWithCountOfFour($collection)
+ public function testSplitCollectionIntoThreeWithCountOfFour($collection): void
{
$data = new $collection(['a', 'b', 'c', 'd']);
$split = $data->split(3);
@@ -5289,7 +5329,7 @@ public function testSplitCollectionIntoThreeWithCountOfFour($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitCollectionIntoThreeWithCountOfFive($collection)
+ public function testSplitCollectionIntoThreeWithCountOfFive($collection): void
{
$data = new $collection(['a', 'b', 'c', 'd', 'e']);
$split = $data->split(3);
@@ -5307,7 +5347,7 @@ public function testSplitCollectionIntoThreeWithCountOfFive($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitCollectionIntoSixWithCountOfTen($collection)
+ public function testSplitCollectionIntoSixWithCountOfTen($collection): void
{
$data = new $collection(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']);
$split = $data->split(6);
@@ -5328,7 +5368,7 @@ public function testSplitCollectionIntoSixWithCountOfTen($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testSplitEmptyCollection($collection)
+ public function testSplitEmptyCollection($collection): void
{
$data = new $collection;
$split = $data->split(2);
@@ -5345,7 +5385,7 @@ public function testSplitEmptyCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderCollectionGroupBy($collection)
+ public function testHigherOrderCollectionGroupBy($collection): void
{
$data = new $collection([
new TestSupportCollectionHigherOrderItem,
@@ -5366,7 +5406,7 @@ public function testHigherOrderCollectionGroupBy($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderCollectionMap($collection)
+ public function testHigherOrderCollectionMap($collection): void
{
$person1 = (object) ['name' => 'Taylor'];
$person2 = (object) ['name' => 'Yaz'];
@@ -5381,7 +5421,7 @@ public function testHigherOrderCollectionMap($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderCollectionMapFromArrays($collection)
+ public function testHigherOrderCollectionMapFromArrays($collection): void
{
$person1 = ['name' => 'Taylor'];
$person2 = ['name' => 'Yaz'];
@@ -5396,7 +5436,7 @@ public function testHigherOrderCollectionMapFromArrays($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderCollectionStaticCall($collection)
+ public function testHigherOrderCollectionStaticCall($collection): void
{
$class1 = TestSupportCollectionHigherOrderStaticClass1::class;
$class2 = TestSupportCollectionHigherOrderStaticClass2::class;
@@ -5409,7 +5449,7 @@ public function testHigherOrderCollectionStaticCall($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPartition($collection)
+ public function testPartition($collection): void
{
$data = new $collection(range(1, 10));
@@ -5422,7 +5462,7 @@ public function testPartition($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPartitionCallbackWithKey($collection)
+ public function testPartitionCallbackWithKey($collection): void
{
$data = new $collection(['zero', 'one', 'two', 'three']);
@@ -5435,7 +5475,7 @@ public function testPartitionCallbackWithKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPartitionByKey($collection)
+ public function testPartitionByKey($collection): void
{
$courses = new $collection([
['free' => true, 'title' => 'Basic'], ['free' => false, 'title' => 'Premium'],
@@ -5448,7 +5488,7 @@ public function testPartitionByKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPartitionWithOperators($collection)
+ public function testPartitionWithOperators($collection): void
{
$data = new $collection([
['name' => 'Tim', 'age' => 17],
@@ -5483,7 +5523,7 @@ public function testPartitionWithOperators($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPartitionPreservesKeys($collection)
+ public function testPartitionPreservesKeys($collection): void
{
$courses = new $collection([
'a' => ['free' => true], 'b' => ['free' => false], 'c' => ['free' => true],
@@ -5496,7 +5536,7 @@ public function testPartitionPreservesKeys($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPartitionEmptyCollection($collection)
+ public function testPartitionEmptyCollection($collection): void
{
$data = new $collection;
@@ -5506,7 +5546,7 @@ public function testPartitionEmptyCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderPartition($collection)
+ public function testHigherOrderPartition($collection): void
{
$courses = new $collection([
'a' => ['free' => true], 'b' => ['free' => false], 'c' => ['free' => true],
@@ -5520,7 +5560,7 @@ public function testHigherOrderPartition($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testTap($collection)
+ public function testTap($collection): void
{
$data = new $collection([1, 2, 3]);
@@ -5537,7 +5577,7 @@ public function testTap($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhen($collection)
+ public function testWhen($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5557,7 +5597,7 @@ public function testWhen($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhenDefault($collection)
+ public function testWhenDefault($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5571,7 +5611,7 @@ public function testWhenDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhenEmpty($collection)
+ public function testWhenEmpty($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5591,7 +5631,7 @@ public function testWhenEmpty($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhenEmptyDefault($collection)
+ public function testWhenEmptyDefault($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5605,7 +5645,7 @@ public function testWhenEmptyDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhenNotEmpty($collection)
+ public function testWhenNotEmpty($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5625,7 +5665,7 @@ public function testWhenNotEmpty($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhenNotEmptyDefault($collection)
+ public function testWhenNotEmptyDefault($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5639,7 +5679,7 @@ public function testWhenNotEmptyDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderWhenAndUnless($collection)
+ public function testHigherOrderWhenAndUnless($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5661,7 +5701,7 @@ public function testHigherOrderWhenAndUnless($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHigherOrderWhenAndUnlessWithProxy($collection)
+ public function testHigherOrderWhenAndUnlessWithProxy($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5683,7 +5723,7 @@ public function testHigherOrderWhenAndUnlessWithProxy($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnless($collection)
+ public function testUnless($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5703,7 +5743,7 @@ public function testUnless($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnlessDefault($collection)
+ public function testUnlessDefault($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5717,7 +5757,7 @@ public function testUnlessDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnlessEmpty($collection)
+ public function testUnlessEmpty($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5737,7 +5777,7 @@ public function testUnlessEmpty($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnlessEmptyDefault($collection)
+ public function testUnlessEmptyDefault($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5751,7 +5791,7 @@ public function testUnlessEmptyDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnlessNotEmpty($collection)
+ public function testUnlessNotEmpty($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5771,7 +5811,7 @@ public function testUnlessNotEmpty($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUnlessNotEmptyDefault($collection)
+ public function testUnlessNotEmptyDefault($collection): void
{
$data = new $collection(['michael', 'tom']);
@@ -5785,7 +5825,7 @@ public function testUnlessNotEmptyDefault($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHasReturnsValidResults($collection)
+ public function testHasReturnsValidResults($collection): void
{
$data = new $collection(['foo' => 'one', 'bar' => 'two', 1 => 'three']);
$this->assertTrue($data->has('foo'));
@@ -5794,7 +5834,7 @@ public function testHasReturnsValidResults($collection)
$this->assertFalse($data->has('baz'));
}
- public function testPutAddsItemToCollection()
+ public function testPutAddsItemToCollection(): void
{
$data = new Collection;
$this->assertSame([], $data->toArray());
@@ -5807,7 +5847,7 @@ public function testPutAddsItemToCollection()
}
#[DataProvider('collectionClassProvider')]
- public function testItThrowsExceptionWhenTryingToAccessNoProxyProperty($collection)
+ public function testItThrowsExceptionWhenTryingToAccessNoProxyProperty($collection): void
{
$data = new $collection;
$this->expectException(Exception::class);
@@ -5816,21 +5856,21 @@ public function testItThrowsExceptionWhenTryingToAccessNoProxyProperty($collecti
}
#[DataProvider('collectionClassProvider')]
- public function testGetWithNullReturnsNull($collection)
+ public function testGetWithNullReturnsNull($collection): void
{
$data = new $collection([1, 2, 3]);
$this->assertNull($data->get(null));
}
#[DataProvider('collectionClassProvider')]
- public function testGetWithDefaultValue($collection)
+ public function testGetWithDefaultValue($collection): void
{
$data = new $collection(['name' => 'taylor', 'framework' => 'laravel']);
$this->assertEquals('34', $data->get('age', 34));
}
#[DataProvider('collectionClassProvider')]
- public function testGetWithCallbackAsDefaultValue($collection)
+ public function testGetWithCallbackAsDefaultValue($collection): void
{
$data = new $collection(['name' => 'taylor', 'framework' => 'laravel']);
$result = $data->get('email', function () {
@@ -5840,7 +5880,7 @@ public function testGetWithCallbackAsDefaultValue($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNull($collection)
+ public function testWhereNull($collection): void
{
$data = new $collection([
['name' => 'Taylor'],
@@ -5858,7 +5898,7 @@ public function testWhereNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNullWithoutKey($collection)
+ public function testWhereNullWithoutKey($collection): void
{
$collection = new $collection([1, null, 3, 'null', false, true]);
$this->assertSame([
@@ -5867,7 +5907,7 @@ public function testWhereNullWithoutKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNotNull($collection)
+ public function testWhereNotNull($collection): void
{
$data = new $collection($originalData = [
['name' => 'Taylor'],
@@ -5888,7 +5928,7 @@ public function testWhereNotNull($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testWhereNotNullWithoutKey($collection)
+ public function testWhereNotNullWithoutKey($collection): void
{
$data = new $collection([1, null, 3, 'null', false, true]);
@@ -5902,7 +5942,7 @@ public function testWhereNotNullWithoutKey($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testCollect($collection)
+ public function testCollect($collection): void
{
$data = $collection::make([
'a' => 1,
@@ -5920,7 +5960,7 @@ public function testCollect($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testUndot($collection)
+ public function testUndot($collection): void
{
$data = $collection::make([
'name' => 'Taylor',
@@ -5954,7 +5994,7 @@ public function testUndot($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testDot($collection)
+ public function testDot($collection): void
{
$data = $collection::make([
'name' => 'Taylor',
@@ -6009,7 +6049,7 @@ public function testDotWithDepth($collection): void
}
#[DataProvider('collectionClassProvider')]
- public function testEnsureForScalar($collection)
+ public function testEnsureForScalar($collection): void
{
$data = $collection::make([1, 2, 3]);
$data->ensure('int');
@@ -6021,7 +6061,7 @@ public function testEnsureForScalar($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEnsureForObjects($collection)
+ public function testEnsureForObjects($collection): void
{
$data = $collection::make([new stdClass, new stdClass, new stdClass]);
$data->ensure(stdClass::class);
@@ -6033,7 +6073,7 @@ public function testEnsureForObjects($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEnsureForInheritance($collection)
+ public function testEnsureForInheritance($collection): void
{
$data = $collection::make([new Error, new Error]);
$data->ensure(Throwable::class);
@@ -6046,7 +6086,7 @@ public function testEnsureForInheritance($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testEnsureForMultipleTypes($collection)
+ public function testEnsureForMultipleTypes($collection): void
{
$data = $collection::make([new Error, 123]);
$data->ensure([Throwable::class, 'int']);
@@ -6059,7 +6099,7 @@ public function testEnsureForMultipleTypes($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPercentageWithFlatCollection($collection)
+ public function testPercentageWithFlatCollection($collection): void
{
$collection = new $collection([1, 1, 2, 2, 2, 3]);
@@ -6070,7 +6110,7 @@ public function testPercentageWithFlatCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPercentageWithNestedCollection($collection)
+ public function testPercentageWithNestedCollection($collection): void
{
$collection = new $collection([
['name' => 'Taylor', 'foo' => 'foo'],
@@ -6086,7 +6126,7 @@ public function testPercentageWithNestedCollection($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testHighOrderPercentage($collection)
+ public function testHighOrderPercentage($collection): void
{
$collection = new $collection([
['name' => 'Taylor', 'active' => true],
@@ -6099,7 +6139,7 @@ public function testHighOrderPercentage($collection)
}
#[DataProvider('collectionClassProvider')]
- public function testPercentageReturnsNullForEmptyCollections($collection)
+ public function testPercentageReturnsNullForEmptyCollections($collection): void
{
$collection = new $collection([]);
@@ -6108,10 +6148,8 @@ public function testPercentageReturnsNullForEmptyCollections($collection)
/**
* Provides each collection class, respectively.
- *
- * @return array
*/
- public static function collectionClassProvider()
+ public static function collectionClassProvider(): array
{
return [
[Collection::class],
@@ -6372,12 +6410,12 @@ public function __construct($name = 'taylor')
$this->name = $name;
}
- public function uppercase()
+ public function uppercase(): string
{
return $this->name = strtoupper($this->name);
}
- public function is($name)
+ public function is($name): bool
{
return $this->name === $name;
}
@@ -6385,12 +6423,12 @@ public function is($name)
class TestSupportCollectionHigherOrderStaticClass1
{
- public static function transform($name)
+ public static function transform($name): string
{
return strtoupper($name);
}
- public static function matches($name)
+ public static function matches($name): bool
{
return str_starts_with($name, 'T');
}
@@ -6398,12 +6436,12 @@ public static function matches($name)
class TestSupportCollectionHigherOrderStaticClass2
{
- public static function transform($name)
+ public static function transform($name): string
{
return trim(chunk_split($name, 1, ' '));
}
- public static function matches($name)
+ public static function matches($name): bool
{
return str_starts_with($name, 'O');
}
@@ -6418,7 +6456,7 @@ public function __construct($attributes)
$this->attributes = $attributes;
}
- public function __get($attribute)
+ public function __get($attribute): mixed
{
$accessor = 'get' . lcfirst($attribute) . 'Attribute';
if (method_exists($this, $accessor)) {
@@ -6428,7 +6466,7 @@ public function __get($attribute)
return $this->{$attribute};
}
- public function __isset($attribute)
+ public function __isset($attribute): bool
{
$accessor = 'get' . lcfirst($attribute) . 'Attribute';
@@ -6439,7 +6477,7 @@ public function __isset($attribute)
return isset($this->{$attribute});
}
- public function getSomeAttribute()
+ public function getSomeAttribute(): mixed
{
return $this->attributes['some'];
}
From fb1edcebcd1514f4d4fd1031efc5c646b4333f5e Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:20 +0000
Subject: [PATCH 30/40] Expand lazy collection behavior coverage
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.
---
tests/Support/SupportLazyCollectionTest.php | 147 +++++++++++++-------
1 file changed, 99 insertions(+), 48 deletions(-)
diff --git a/tests/Support/SupportLazyCollectionTest.php b/tests/Support/SupportLazyCollectionTest.php
index 4367d7c96..ca58be618 100644
--- a/tests/Support/SupportLazyCollectionTest.php
+++ b/tests/Support/SupportLazyCollectionTest.php
@@ -4,7 +4,9 @@
namespace Hypervel\Tests\Support;
+use ArrayIterator;
use Carbon\CarbonInterval as Duration;
+use Generator;
use Hypervel\Foundation\Testing\Wormhole;
use Hypervel\Support\Carbon;
use Hypervel\Support\Collection;
@@ -16,13 +18,13 @@
class SupportLazyCollectionTest extends TestCase
{
- public function testCanCreateEmptyCollection()
+ public function testCanCreateEmptyCollection(): void
{
$this->assertSame([], LazyCollection::make()->all());
$this->assertSame([], LazyCollection::empty()->all());
}
- public function testCanCreateCollectionFromArray()
+ public function testCanCreateCollectionFromArray(): void
{
$array = [1, 2, 3];
@@ -37,7 +39,7 @@ public function testCanCreateCollectionFromArray()
$this->assertSame($array, $data->all());
}
- public function testCanCreateCollectionFromArrayable()
+ public function testCanCreateCollectionFromArrayable(): void
{
$array = [1, 2, 3];
@@ -52,7 +54,7 @@ public function testCanCreateCollectionFromArrayable()
$this->assertSame($array, $data->all());
}
- public function testCanCreateCollectionFromGeneratorFunction()
+ public function testCanCreateCollectionFromGeneratorFunction(): void
{
$data = LazyCollection::make(function () {
yield 1;
@@ -75,7 +77,7 @@ public function testCanCreateCollectionFromGeneratorFunction()
], $data->all());
}
- public function testCanCreateCollectionFromNonGeneratorFunction()
+ public function testCanCreateCollectionFromNonGeneratorFunction(): void
{
$data = LazyCollection::make(function () {
return 'laravel';
@@ -84,7 +86,7 @@ public function testCanCreateCollectionFromNonGeneratorFunction()
$this->assertSame(['laravel'], $data->all());
}
- public function testDoesNotCreateCollectionFromGenerator()
+ public function testDoesNotCreateCollectionFromGenerator(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -95,7 +97,47 @@ public function testDoesNotCreateCollectionFromGenerator()
LazyCollection::make($generateNumber());
}
- public function testEager()
+ public function testCombineAcceptsPlainIterators(): void
+ {
+ $keys = new LazyCollection(['first', 'second']);
+
+ $this->assertSame(
+ ['first' => 1, 'second' => 2],
+ $keys->combine(new ArrayIterator([1, 2]))->all()
+ );
+
+ $values = (function (): Generator {
+ yield 3;
+ yield 4;
+ })();
+
+ $this->assertSame(
+ ['first' => 3, 'second' => 4],
+ $keys->combine($values)->all()
+ );
+ }
+
+ public function testZipAcceptsPlainIterators(): void
+ {
+ $values = new LazyCollection([1, 2]);
+
+ $this->assertSame(
+ [[1, 3], [2, 4]],
+ $values->zip(new ArrayIterator([3, 4]))->map->all()->all()
+ );
+
+ $items = (function (): Generator {
+ yield 5;
+ yield 6;
+ })();
+
+ $this->assertSame(
+ [[1, 5], [2, 6]],
+ $values->zip($items)->map->all()->all()
+ );
+ }
+
+ public function testEager(): void
{
$source = [1, 2, 3, 4, 5];
@@ -108,7 +150,7 @@ public function testEager()
$this->assertSame([1, 2, 3, 4, 5], $data->all());
}
- public function testRemember()
+ public function testRemember(): void
{
$source = [1, 2, 3, 4];
@@ -123,7 +165,7 @@ public function testRemember()
$this->assertSame([1, 2, 3, 4], $collection->all());
}
- public function testRememberWithTwoRunners()
+ public function testRememberWithTwoRunners(): void
{
$source = [1, 2, 3, 4];
@@ -168,7 +210,7 @@ public function testRememberWithTwoRunners()
$this->assertEquals(4, $b->current());
}
- public function testRememberWithDuplicateKeys()
+ public function testRememberWithDuplicateKeys(): void
{
$collection = LazyCollection::make(function () {
yield 'key' => 1;
@@ -182,7 +224,7 @@ public function testRememberWithDuplicateKeys()
$this->assertSame([['key', 1], ['key', 2]], $results);
}
- public function testTakeUntilTimeout()
+ public function testTakeUntilTimeout(): void
{
$timeout = Carbon::now();
@@ -213,7 +255,7 @@ public function testTakeUntilTimeout()
$this->assertSame([2, 1], $timedOutWith);
}
- public function testTapEach()
+ public function testTapEach(): void
{
$data = LazyCollection::times(10);
@@ -231,7 +273,7 @@ public function testTapEach()
$this->assertSame([1, 2, 3, 4, 5], $tapped);
}
- public function testThrottle()
+ public function testThrottle(): void
{
Sleep::fake();
@@ -254,7 +296,7 @@ public function testThrottle()
Sleep::fake(false);
}
- public function testThrottleAccountsForTimePassed()
+ public function testThrottleAccountsForTimePassed(): void
{
Sleep::fake();
Carbon::setTestNow(now());
@@ -287,7 +329,7 @@ public function testThrottleAccountsForTimePassed()
Carbon::setTestNow();
}
- public function testUniqueDoubleEnumeration()
+ public function testUniqueDoubleEnumeration(): void
{
$data = LazyCollection::times(2)->unique();
@@ -296,7 +338,7 @@ public function testUniqueDoubleEnumeration()
$this->assertSame([1, 2], $data->all());
}
- public function testAfter()
+ public function testAfter(): void
{
$data = new LazyCollection([1, '2', 3, 4]);
@@ -322,7 +364,7 @@ public function testAfter()
$this->assertSame(['name' => 'Mohamed', 'age' => 35], $result);
}
- public function testBefore()
+ public function testBefore(): void
{
// Test finding item before value with non-strict comparison
$data = new LazyCollection([1, 2, '3', 4]);
@@ -345,7 +387,7 @@ public function testBefore()
$this->assertSame(['name' => 'Taylor', 'age' => 35], $result);
}
- public function testShuffle()
+ public function testShuffle(): void
{
$data = new LazyCollection([1, 2, 3, 4, 5]);
$shuffled = $data->shuffle();
@@ -365,7 +407,7 @@ public function testShuffle()
$this->assertTrue($shuffled->contains('name', 'Jeffrey'));
}
- public function testCollapseWithKeys()
+ public function testCollapseWithKeys(): void
{
$collection = new LazyCollection([
['a' => 1, 'b' => 2],
@@ -384,34 +426,9 @@ public function testCollapseWithKeys()
$this->assertEquals(['a' => 1, 'b' => 2], $collapsed->all());
}
- public function testContainsOneItem()
- {
- $collection = new LazyCollection([5]);
- $this->assertTrue($collection->containsOneItem());
-
- $emptyCollection = new LazyCollection([]);
- $this->assertFalse($emptyCollection->containsOneItem());
-
- $multipleCollection = new LazyCollection([1, 2, 3]);
- $this->assertFalse($multipleCollection->containsOneItem());
- }
-
- public function testContainsManyItems()
- {
- $emptyCollection = new LazyCollection([]);
- $this->assertFalse($emptyCollection->containsManyItems());
-
- $singleCollection = new LazyCollection([1]);
- $this->assertFalse($singleCollection->containsManyItems());
-
- $multipleCollection = new LazyCollection([1, 2]);
- $this->assertTrue($multipleCollection->containsManyItems());
+ // REMOVED: Tests for Laravel's deprecated containsOneItem() and containsManyItems(); use hasSole() and hasMany().
- $manyCollection = new LazyCollection([1, 2, 3]);
- $this->assertTrue($manyCollection->containsManyItems());
- }
-
- public function testDoesntContain()
+ public function testDoesntContain(): void
{
$collection = new LazyCollection([1, 2, 3, 4, 5]);
@@ -437,7 +454,7 @@ public function testDoesntContain()
$this->assertFalse($users->doesntContain('name', 'Taylor'));
}
- public function testDot()
+ public function testDot(): void
{
$collection = new LazyCollection([
'foo' => [
@@ -472,7 +489,7 @@ public function testDot()
$this->assertEquals($expected, $dotted->all());
}
- public function testWithHeartbeat()
+ public function testWithHeartbeat(): void
{
$start = Carbon::create(2000, 1, 1);
$after2Minutes = $start->copy()->addMinutes(2);
@@ -518,4 +535,38 @@ public function testWithHeartbeat()
Carbon::setTestNow();
}
+
+ public function testRandomPreservesKeys(): void
+ {
+ $collection = new LazyCollection([
+ 'first' => 1,
+ 'second' => 2,
+ 'third' => 3,
+ ]);
+
+ $this->assertSame([0, 1], array_keys($collection->random(2)->all()));
+
+ foreach (array_keys($collection->random(2, true)->all()) as $key) {
+ $this->assertContains($key, ['first', 'second', 'third']);
+ }
+ }
+
+ public function testHasDoesNotCountDuplicateKeys(): void
+ {
+ $collection = LazyCollection::make(function (): iterable {
+ yield 'a' => 1;
+ yield 'a' => 2;
+ yield 'c' => 3;
+ });
+
+ $this->assertFalse($collection->has('a', 'b'));
+ $this->assertFalse($collection->has(['a', 'b']));
+ $this->assertTrue($collection->has('a'));
+ }
+
+ public function testHasTreatsAnEmptyKeySetAsSatisfied(): void
+ {
+ $this->assertTrue(LazyCollection::empty()->has([]));
+ $this->assertTrue((new LazyCollection([1, 2, 3]))->has([]));
+ }
}
From d972c9f18f1f43f97175549dce2171f407b1283e Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:20 +0000
Subject: [PATCH 31/40] Preserve lazy evaluation coverage
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.
---
.../SupportLazyCollectionIsLazyTest.php | 279 +++++++++---------
1 file changed, 137 insertions(+), 142 deletions(-)
diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php
index 613e7a4c6..e3a60834c 100644
--- a/tests/Support/SupportLazyCollectionIsLazyTest.php
+++ b/tests/Support/SupportLazyCollectionIsLazyTest.php
@@ -18,7 +18,7 @@ class SupportLazyCollectionIsLazyTest extends TestCase
{
use Concerns\CountsEnumerations;
- public function testMakeWithClosureIsLazy()
+ public function testMakeWithClosureIsLazy(): void
{
[$closure, $recorder] = $this->makeGeneratorFunctionWithRecorder();
@@ -27,14 +27,14 @@ public function testMakeWithClosureIsLazy()
$this->assertEquals([], $recorder->all());
}
- public function testMakeWithLazyCollectionIsLazy()
+ public function testMakeWithLazyCollectionIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
LazyCollection::make($collection);
});
}
- public function testEagerEnumeratesOnce()
+ public function testEagerEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection = $collection->eager();
@@ -44,7 +44,7 @@ public function testEagerEnumeratesOnce()
});
}
- public function testChunkIsLazy()
+ public function testChunkIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->chunk(3);
@@ -55,7 +55,7 @@ public function testChunkIsLazy()
});
}
- public function testChunkWhileIsLazy()
+ public function testChunkWhileIsLazy(): void
{
$collection = LazyCollection::make(['A', 'A', 'B', 'B', 'C', 'C', 'C']);
@@ -78,7 +78,7 @@ public function testChunkWhileIsLazy()
});
}
- public function testCollapseIsLazy()
+ public function testCollapseIsLazy(): void
{
$collection = LazyCollection::make([
[1, 2, 3],
@@ -95,7 +95,7 @@ public function testCollapseIsLazy()
});
}
- public function testCombineIsLazy()
+ public function testCombineIsLazy(): void
{
$firstEnumerations = 0;
$secondEnumerations = 0;
@@ -113,7 +113,7 @@ public function testCombineIsLazy()
$this->assertEnumerations(1, $secondEnumerations);
}
- public function testConcatIsLazy()
+ public function testConcatIsLazy(): void
{
$firstEnumerations = 0;
$secondEnumerations = 0;
@@ -139,7 +139,7 @@ public function testConcatIsLazy()
$this->assertEnumerations(1, $secondEnumerations);
}
- public function testMultiplyIsLazy()
+ public function testMultiplyIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->multiply(2);
@@ -153,35 +153,35 @@ function ($collection) {
);
}
- public function testContainsIsLazy()
+ public function testContainsIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->contains(5);
});
}
- public function testDoesntContainIsLazy()
+ public function testDoesntContainIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->doesntContain(5);
});
}
- public function testContainsStrictIsLazy()
+ public function testContainsStrictIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->containsStrict(5);
});
}
- public function testCountEnumeratesOnce()
+ public function testCountEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->count();
});
}
- public function testCountByIsLazy()
+ public function testCountByIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->countBy();
@@ -195,7 +195,7 @@ function ($collection) {
);
}
- public function testCrossJoinIsLazy()
+ public function testCrossJoinIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->crossJoin([1]);
@@ -206,7 +206,7 @@ public function testCrossJoinIsLazy()
});
}
- public function testDiffIsLazy()
+ public function testDiffIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->diff([1, 2]);
@@ -217,7 +217,7 @@ public function testDiffIsLazy()
});
}
- public function testDiffAssocIsLazy()
+ public function testDiffAssocIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->diffAssoc([1, 2]);
@@ -228,7 +228,7 @@ public function testDiffAssocIsLazy()
});
}
- public function testDiffAssocUsingIsLazy()
+ public function testDiffAssocUsingIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->diffAssocUsing([1, 2], 'strcasecmp');
@@ -239,7 +239,7 @@ public function testDiffAssocUsingIsLazy()
});
}
- public function testDiffKeysIsLazy()
+ public function testDiffKeysIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->diffKeys([1, 2]);
@@ -250,7 +250,7 @@ public function testDiffKeysIsLazy()
});
}
- public function testDiffKeysUsingIsLazy()
+ public function testDiffKeysUsingIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->diffKeysUsing([1, 2], 'strcasecmp');
@@ -261,7 +261,7 @@ public function testDiffKeysUsingIsLazy()
});
}
- public function testDiffUsingIsLazy()
+ public function testDiffUsingIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->diffUsing([1, 2], 'strcasecmp');
@@ -272,7 +272,7 @@ public function testDiffUsingIsLazy()
});
}
- public function testDuplicatesIsLazy()
+ public function testDuplicatesIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->duplicates();
@@ -283,7 +283,7 @@ public function testDuplicatesIsLazy()
});
}
- public function testDuplicatesStrictIsLazy()
+ public function testDuplicatesStrictIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->duplicatesStrict();
@@ -294,7 +294,7 @@ public function testDuplicatesStrictIsLazy()
});
}
- public function testEachIsLazy()
+ public function testEachIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->each(function ($value, $key) {
@@ -325,7 +325,7 @@ public function testEachIsLazy()
});
}
- public function testEachSpreadIsLazy()
+ public function testEachSpreadIsLazy(): void
{
$data = $this->make([[1, 2], [3, 4], [5, 6], [7, 8]]);
@@ -344,7 +344,7 @@ public function testEachSpreadIsLazy()
});
}
- public function testEveryIsLazy()
+ public function testEveryIsLazy(): void
{
$this->assertEnumerates(2, function ($collection) {
$collection->every(function ($value) {
@@ -359,7 +359,7 @@ public function testEveryIsLazy()
});
}
- public function testExceptIsLazy()
+ public function testExceptIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->except([1, 2]);
@@ -370,7 +370,7 @@ public function testExceptIsLazy()
});
}
- public function testFilterIsLazy()
+ public function testFilterIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->filter(function ($value) {
@@ -385,7 +385,7 @@ public function testFilterIsLazy()
});
}
- public function testFirstIsLazy()
+ public function testFirstIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->first();
@@ -398,7 +398,7 @@ public function testFirstIsLazy()
});
}
- public function testFirstWhereIsLazy()
+ public function testFirstWhereIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3]]);
@@ -407,7 +407,7 @@ public function testFirstWhereIsLazy()
});
}
- public function testFlatMapIsLazy()
+ public function testFlatMapIsLazy(): void
{
$data = $this->make([1, 2, 3, 4, 5]);
@@ -424,7 +424,7 @@ public function testFlatMapIsLazy()
});
}
- public function testFlattenIsLazy()
+ public function testFlattenIsLazy(): void
{
$data = $this->make([1, [2, 3], [4, 5], [6, 7]]);
@@ -437,7 +437,7 @@ public function testFlattenIsLazy()
});
}
- public function testFlipIsLazy()
+ public function testFlipIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->flip();
@@ -448,7 +448,7 @@ public function testFlipIsLazy()
});
}
- public function testForPageIsLazy()
+ public function testForPageIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->forPage(2, 10);
@@ -459,14 +459,14 @@ public function testForPageIsLazy()
});
}
- public function testGetIsLazy()
+ public function testGetIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->get(4);
});
}
- public function testGroupByIsLazy()
+ public function testGroupByIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->groupBy(function ($value) {
@@ -481,7 +481,7 @@ public function testGroupByIsLazy()
});
}
- public function testHasIsLazy()
+ public function testHasIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->has(4);
@@ -492,7 +492,7 @@ public function testHasIsLazy()
});
}
- public function testHasAnyIsLazy()
+ public function testHasAnyIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->hasAny(4);
@@ -507,14 +507,14 @@ public function testHasAnyIsLazy()
});
}
- public function testImplodeEnumeratesOnce()
+ public function testImplodeEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->implode(', ');
});
}
- public function testIntersectIsLazy()
+ public function testIntersectIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->intersect([1, 2, 3]);
@@ -525,7 +525,7 @@ public function testIntersectIsLazy()
});
}
- public function testIntersectUsingIsLazy()
+ public function testIntersectUsingIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->intersectUsing([1, 2], 'strcasecmp');
@@ -536,7 +536,7 @@ public function testIntersectUsingIsLazy()
});
}
- public function testIntersectAssocIsLazy()
+ public function testIntersectAssocIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->intersectAssoc([1, 2]);
@@ -547,7 +547,7 @@ public function testIntersectAssocIsLazy()
});
}
- public function testIntersectAssocUsingIsLazy()
+ public function testIntersectAssocUsingIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->intersectAssocUsing([1, 2], 'strcasecmp');
@@ -558,7 +558,7 @@ public function testIntersectAssocUsingIsLazy()
});
}
- public function testIntersectByKeysIsLazy()
+ public function testIntersectByKeysIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->intersectByKeys([1, 2, 3]);
@@ -569,28 +569,23 @@ public function testIntersectByKeysIsLazy()
});
}
- public function testIsEmptyIsLazy()
+ public function testIsEmptyIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->isEmpty();
});
}
- public function testIsNotEmptyIsLazy()
+ public function testIsNotEmptyIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->isNotEmpty();
});
}
- public function testContainsOneItemIsLazy()
- {
- $this->assertEnumerates(2, function ($collection) {
- $collection->containsOneItem();
- });
- }
+ // REMOVED: Laziness test for Laravel's deprecated containsOneItem(); use hasSole().
- public function testHasSoleIsLazy()
+ public function testHasSoleIsLazy(): void
{
$this->assertEnumerates(2, function ($collection) {
$collection->hasSole();
@@ -607,7 +602,7 @@ public function testHasSoleIsLazy()
);
}
- public function testHasManyIsLazy()
+ public function testHasManyIsLazy(): void
{
$this->assertEnumerates(2, function ($collection) {
$collection->hasMany();
@@ -624,21 +619,21 @@ public function testHasManyIsLazy()
);
}
- public function testJoinIsLazy()
+ public function testJoinIsLazy(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->join(', ', ' and ');
});
}
- public function testJsonSerializeEnumeratesOnce()
+ public function testJsonSerializeEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->jsonSerialize();
});
}
- public function testKeyByIsLazy()
+ public function testKeyByIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->keyBy(function ($value) {
@@ -653,7 +648,7 @@ public function testKeyByIsLazy()
});
}
- public function testKeysIsLazy()
+ public function testKeysIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->keys();
@@ -664,14 +659,14 @@ public function testKeysIsLazy()
});
}
- public function testLastEnumeratesOnce()
+ public function testLastEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->last();
});
}
- public function testMapIsLazy()
+ public function testMapIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->map(function ($value) {
@@ -686,7 +681,7 @@ public function testMapIsLazy()
});
}
- public function testMapIntoIsLazy()
+ public function testMapIntoIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->mapInto(stdClass::class);
@@ -697,7 +692,7 @@ public function testMapIntoIsLazy()
});
}
- public function testMapSpreadIsLazy()
+ public function testMapSpreadIsLazy(): void
{
$data = $this->make([[1, 2], [3, 4], [5, 6], [7, 8]]);
@@ -714,7 +709,7 @@ public function testMapSpreadIsLazy()
});
}
- public function testMapToDictionaryIsLazy()
+ public function testMapToDictionaryIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->mapToDictionary(function ($value, $key) {
@@ -729,7 +724,7 @@ public function testMapToDictionaryIsLazy()
});
}
- public function testMapToGroupsIsLazy()
+ public function testMapToGroupsIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->mapToGroups(function ($value, $key) {
@@ -744,7 +739,7 @@ public function testMapToGroupsIsLazy()
});
}
- public function testMapWithKeysIsLazy()
+ public function testMapWithKeysIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->mapWithKeys(function ($value, $key) {
@@ -759,28 +754,28 @@ public function testMapWithKeysIsLazy()
});
}
- public function testMaxEnumeratesOnce()
+ public function testMaxEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->max();
});
}
- public function testMedianEnumeratesOnce()
+ public function testMedianEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->median();
});
}
- public function testAvgEnumeratesOnce()
+ public function testAvgEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->avg();
});
}
- public function testMergeIsLazy()
+ public function testMergeIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->merge([1, 2, 3]);
@@ -791,7 +786,7 @@ public function testMergeIsLazy()
});
}
- public function testMergeRecursiveIsLazy()
+ public function testMergeRecursiveIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->mergeRecursive([1, 2, 3]);
@@ -802,21 +797,21 @@ public function testMergeRecursiveIsLazy()
});
}
- public function testMinEnumeratesOnce()
+ public function testMinEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->min();
});
}
- public function testModeEnumeratesOnce()
+ public function testModeEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->mode();
});
}
- public function testNthIsLazy()
+ public function testNthIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->nth(5);
@@ -827,7 +822,7 @@ public function testNthIsLazy()
});
}
- public function testOnlyIsLazy()
+ public function testOnlyIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->only(5, 6, 7);
@@ -838,7 +833,7 @@ public function testOnlyIsLazy()
});
}
- public function testPadIsLazy()
+ public function testPadIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->pad(200, null);
@@ -854,7 +849,7 @@ public function testPadIsLazy()
});
}
- public function testPartitionEnumeratesOnce()
+ public function testPartitionEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->partition(function ($value) {
@@ -863,7 +858,7 @@ public function testPartitionEnumeratesOnce()
});
}
- public function testPipeDoesNotEnumerate()
+ public function testPipeDoesNotEnumerate(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->pipe(function () {
@@ -872,7 +867,7 @@ public function testPipeDoesNotEnumerate()
});
}
- public function testPluckIsLazy()
+ public function testPluckIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -885,7 +880,7 @@ public function testPluckIsLazy()
});
}
- public function testRandomEnumeratesOnce()
+ public function testRandomEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->random();
@@ -896,7 +891,7 @@ public function testRandomEnumeratesOnce()
});
}
- public function testRangeIsLazy()
+ public function testRangeIsLazy(): void
{
$data = LazyCollection::range(10, 1000);
@@ -909,7 +904,7 @@ public function testRangeIsLazy()
});
}
- public function testReduceIsLazy()
+ public function testReduceIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$this->rescue(function () use ($collection) {
@@ -926,7 +921,7 @@ public function testReduceIsLazy()
});
}
- public function testReduceSpreadIsLazy()
+ public function testReduceSpreadIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$this->rescue(function () use ($collection) {
@@ -943,7 +938,7 @@ public function testReduceSpreadIsLazy()
});
}
- public function testRejectIsLazy()
+ public function testRejectIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->reject(function ($value) {
@@ -958,7 +953,7 @@ public function testRejectIsLazy()
});
}
- public function testRememberIsLazy()
+ public function testRememberIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->remember();
@@ -979,7 +974,7 @@ public function testRememberIsLazy()
});
}
- public function testReplaceIsLazy()
+ public function testReplaceIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->replace([5 => 'a', 10 => 'b']);
@@ -990,7 +985,7 @@ public function testReplaceIsLazy()
});
}
- public function testReplaceRecursiveIsLazy()
+ public function testReplaceRecursiveIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->replaceRecursive([5 => 'a', 10 => 'b']);
@@ -1001,7 +996,7 @@ public function testReplaceRecursiveIsLazy()
});
}
- public function testReverseIsLazy()
+ public function testReverseIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->reverse();
@@ -1012,7 +1007,7 @@ public function testReverseIsLazy()
});
}
- public function testSearchIsLazy()
+ public function testSearchIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->search(5);
@@ -1023,7 +1018,7 @@ public function testSearchIsLazy()
});
}
- public function testShuffleIsLazy()
+ public function testShuffleIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->shuffle();
@@ -1034,7 +1029,7 @@ public function testShuffleIsLazy()
});
}
- public function testSlidingIsLazy()
+ public function testSlidingIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sliding();
@@ -1057,7 +1052,7 @@ public function testSlidingIsLazy()
});
}
- public function testSkipIsLazy()
+ public function testSkipIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->skip(10);
@@ -1068,7 +1063,7 @@ public function testSkipIsLazy()
});
}
- public function testSkipUntilIsLazy()
+ public function testSkipUntilIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->skipUntil(INF);
@@ -1085,7 +1080,7 @@ public function testSkipUntilIsLazy()
});
}
- public function testSkipWhileIsLazy()
+ public function testSkipWhileIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->skipWhile(1);
@@ -1102,7 +1097,7 @@ public function testSkipWhileIsLazy()
});
}
- public function testSliceIsLazy()
+ public function testSliceIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->slice(2);
@@ -1123,7 +1118,7 @@ public function testSliceIsLazy()
});
}
- public function testFindFirstOrFailIsLazy()
+ public function testFindFirstOrFailIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->firstOrFail();
@@ -1151,7 +1146,7 @@ public function testFindFirstOrFailIsLazy()
});
}
- public function testSomeIsLazy()
+ public function testSomeIsLazy(): void
{
$this->assertEnumerates(5, function ($collection) {
$collection->some(function ($value) {
@@ -1166,7 +1161,7 @@ public function testSomeIsLazy()
});
}
- public function testSoleIsLazy()
+ public function testSoleIsLazy(): void
{
$this->assertEnumerates(2, function ($collection) {
try {
@@ -1191,7 +1186,7 @@ public function testSoleIsLazy()
});
}
- public function testSortIsLazy()
+ public function testSortIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sort();
@@ -1202,7 +1197,7 @@ public function testSortIsLazy()
});
}
- public function testSortDescIsLazy()
+ public function testSortDescIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sortDesc();
@@ -1213,7 +1208,7 @@ public function testSortDescIsLazy()
});
}
- public function testSortByIsLazy()
+ public function testSortByIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sortBy(function ($value) {
@@ -1228,7 +1223,7 @@ public function testSortByIsLazy()
});
}
- public function testSortByDescIsLazy()
+ public function testSortByDescIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sortByDesc(function ($value) {
@@ -1243,7 +1238,7 @@ public function testSortByDescIsLazy()
});
}
- public function testSortKeysIsLazy()
+ public function testSortKeysIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sortKeys();
@@ -1254,7 +1249,7 @@ public function testSortKeysIsLazy()
});
}
- public function testSortKeysDescIsLazy()
+ public function testSortKeysDescIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->sortKeysDesc();
@@ -1265,7 +1260,7 @@ public function testSortKeysDescIsLazy()
});
}
- public function testSplitIsLazy()
+ public function testSplitIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->split(4);
@@ -1276,14 +1271,14 @@ public function testSplitIsLazy()
});
}
- public function testSumEnumeratesOnce()
+ public function testSumEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->sum();
});
}
- public function testTakeIsLazy()
+ public function testTakeIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->take(10);
@@ -1294,7 +1289,7 @@ public function testTakeIsLazy()
});
}
- public function testTakeUntilIsLazy()
+ public function testTakeUntilIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->takeUntil(INF);
@@ -1311,7 +1306,7 @@ public function testTakeUntilIsLazy()
});
}
- public function testTakeUntilTimeoutIsLazy()
+ public function testTakeUntilTimeoutIsLazy(): void
{
tap(m::mock(LazyCollection::class . '[now]')->times(100), function ($mock) {
$this->assertDoesNotEnumerateCollection($mock, function ($mock) {
@@ -1375,7 +1370,7 @@ public function testTakeUntilTimeoutIsLazy()
});
}
- public function testTakeWhileIsLazy()
+ public function testTakeWhileIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->takeWhile(0);
@@ -1392,7 +1387,7 @@ public function testTakeWhileIsLazy()
});
}
- public function testTapDoesNotEnumerate()
+ public function testTapDoesNotEnumerate(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->tap(function ($collection) {
@@ -1401,7 +1396,7 @@ public function testTapDoesNotEnumerate()
});
}
- public function testTapEachIsLazy()
+ public function testTapEachIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->tapEach(function ($value) {
@@ -1416,7 +1411,7 @@ public function testTapEachIsLazy()
});
}
- public function testThrottleIsLazy()
+ public function testThrottleIsLazy(): void
{
Sleep::fake();
@@ -1435,7 +1430,7 @@ public function testThrottleIsLazy()
Sleep::fake(false);
}
- public function testTimesIsLazy()
+ public function testTimesIsLazy(): void
{
$data = LazyCollection::times(INF);
@@ -1444,21 +1439,21 @@ public function testTimesIsLazy()
});
}
- public function testToArrayEnumeratesOnce()
+ public function testToArrayEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->toArray();
});
}
- public function testToJsonEnumeratesOnce()
+ public function testToJsonEnumeratesOnce(): void
{
$this->assertEnumeratesOnce(function ($collection) {
$collection->toJson();
});
}
- public function testUnionIsLazy()
+ public function testUnionIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->union([4, 5, 6]);
@@ -1469,7 +1464,7 @@ public function testUnionIsLazy()
});
}
- public function testUniqueIsLazy()
+ public function testUniqueIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->unique();
@@ -1480,7 +1475,7 @@ public function testUniqueIsLazy()
});
}
- public function testUniqueStrictIsLazy()
+ public function testUniqueStrictIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->uniqueStrict();
@@ -1491,7 +1486,7 @@ public function testUniqueStrictIsLazy()
});
}
- public function testUnlessDoesNotEnumerate()
+ public function testUnlessDoesNotEnumerate(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->unless(true, function ($collection) {
@@ -1504,7 +1499,7 @@ public function testUnlessDoesNotEnumerate()
});
}
- public function testUnlessEmptyIsLazy()
+ public function testUnlessEmptyIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->unlessEmpty(function ($collection) {
@@ -1513,7 +1508,7 @@ public function testUnlessEmptyIsLazy()
});
}
- public function testUnlessNotEmptyIsLazy()
+ public function testUnlessNotEmptyIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->unlessNotEmpty(function ($collection) {
@@ -1522,14 +1517,14 @@ public function testUnlessNotEmptyIsLazy()
});
}
- public function testUnwrapEnumeratesOne()
+ public function testUnwrapEnumeratesOne(): void
{
$this->assertEnumeratesOnce(function ($collection) {
LazyCollection::unwrap($collection);
});
}
- public function testValuesIsLazy()
+ public function testValuesIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->values();
@@ -1540,7 +1535,7 @@ public function testValuesIsLazy()
});
}
- public function testWhenDoesNotEnumerate()
+ public function testWhenDoesNotEnumerate(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->when(true, function ($collection) {
@@ -1553,7 +1548,7 @@ public function testWhenDoesNotEnumerate()
});
}
- public function testWhenEmptyIsLazy()
+ public function testWhenEmptyIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->whenEmpty(function ($collection) {
@@ -1562,7 +1557,7 @@ public function testWhenEmptyIsLazy()
});
}
- public function testWhenNotEmptyIsLazy()
+ public function testWhenNotEmptyIsLazy(): void
{
$this->assertEnumerates(1, function ($collection) {
$collection->whenNotEmpty(function ($collection) {
@@ -1571,7 +1566,7 @@ public function testWhenNotEmptyIsLazy()
});
}
- public function testWhereIsLazy()
+ public function testWhereIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1584,7 +1579,7 @@ public function testWhereIsLazy()
});
}
- public function testWhereBetweenIsLazy()
+ public function testWhereBetweenIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1597,7 +1592,7 @@ public function testWhereBetweenIsLazy()
});
}
- public function testWhereInIsLazy()
+ public function testWhereInIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1610,7 +1605,7 @@ public function testWhereInIsLazy()
});
}
- public function testWhereInstanceOfIsLazy()
+ public function testWhereInstanceOfIsLazy(): void
{
$data = $this->make(['a' => 0])->concat(
$this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]])
@@ -1626,7 +1621,7 @@ public function testWhereInstanceOfIsLazy()
});
}
- public function testWhereInStrictIsLazy()
+ public function testWhereInStrictIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1639,7 +1634,7 @@ public function testWhereInStrictIsLazy()
});
}
- public function testWhereNotBetweenIsLazy()
+ public function testWhereNotBetweenIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1652,7 +1647,7 @@ public function testWhereNotBetweenIsLazy()
});
}
- public function testWhereNotInIsLazy()
+ public function testWhereNotInIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1665,7 +1660,7 @@ public function testWhereNotInIsLazy()
});
}
- public function testWhereNotInStrictIsLazy()
+ public function testWhereNotInStrictIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1678,7 +1673,7 @@ public function testWhereNotInStrictIsLazy()
});
}
- public function testWhereNotNullIsLazy()
+ public function testWhereNotNullIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => null], ['a' => 2], ['a' => 3]]);
@@ -1701,7 +1696,7 @@ public function testWhereNotNullIsLazy()
});
}
- public function testWhereNullIsLazy()
+ public function testWhereNullIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => null], ['a' => 2], ['a' => 3]]);
@@ -1724,7 +1719,7 @@ public function testWhereNullIsLazy()
});
}
- public function testWhereStrictIsLazy()
+ public function testWhereStrictIsLazy(): void
{
$data = $this->make([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]);
@@ -1737,7 +1732,7 @@ public function testWhereStrictIsLazy()
});
}
- public function testWithHeartbeatIsLazy()
+ public function testWithHeartbeatIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
$collection->withHeartbeat(1, function () {
@@ -1752,7 +1747,7 @@ public function testWithHeartbeatIsLazy()
});
}
- public function testWrapIsLazy()
+ public function testWrapIsLazy(): void
{
$this->assertDoesNotEnumerate(function ($collection) {
LazyCollection::wrap($collection);
@@ -1763,7 +1758,7 @@ public function testWrapIsLazy()
});
}
- public function testZipIsLazy()
+ public function testZipIsLazy(): void
{
$firstEnumerations = 0;
$secondEnumerations = 0;
@@ -1781,12 +1776,12 @@ public function testZipIsLazy()
$this->assertEnumerations(1, $secondEnumerations);
}
- protected function make($source)
+ protected function make($source): LazyCollection
{
return new LazyCollection($source);
}
- protected function rescue($callback)
+ protected function rescue($callback): void
{
try {
$callback();
From 59cffbc122491dea1567e4033c127e6f20e2f5fe Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:45 +0000
Subject: [PATCH 32/40] Standardize collection static-state tests
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.
---
tests/Support/CollectionStaticStateTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/Support/CollectionStaticStateTest.php b/tests/Support/CollectionStaticStateTest.php
index eade4267f..9bd99a3e4 100644
--- a/tests/Support/CollectionStaticStateTest.php
+++ b/tests/Support/CollectionStaticStateTest.php
@@ -13,7 +13,7 @@
class CollectionStaticStateTest extends TestCase
{
#[DataProvider('collectionClassProvider')]
- public function testFlushStateClearsMacrosAndProxies(string $collection)
+ public function testFlushStateClearsMacrosAndProxies(string $collection): void
{
$collection::macro('adults', function (callable $callback) {
return $this->filter(fn (array $item): bool => $callback($item) >= 18);
From e577797c3b026f355213493c74d65caf5cfe663e Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:45 +0000
Subject: [PATCH 33/40] Use the framework collection test base
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.
---
tests/Support/CollectionTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/Support/CollectionTest.php b/tests/Support/CollectionTest.php
index fb0ba4b9a..5dd71be77 100644
--- a/tests/Support/CollectionTest.php
+++ b/tests/Support/CollectionTest.php
@@ -5,7 +5,7 @@
namespace Hypervel\Tests\Support;
use Hypervel\Support\Collection;
-use PHPUnit\Framework\TestCase;
+use Hypervel\Tests\TestCase;
use Stringable;
class CollectionTest extends TestCase
From 6997bc5db2617706b9637599c98c96e019c842e7 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:45 +0000
Subject: [PATCH 34/40] Standardize higher-order proxy tests
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.
---
tests/Support/HigherOrderProxyTest.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/Support/HigherOrderProxyTest.php b/tests/Support/HigherOrderProxyTest.php
index 5bf6b899c..4ddcfd11b 100644
--- a/tests/Support/HigherOrderProxyTest.php
+++ b/tests/Support/HigherOrderProxyTest.php
@@ -11,7 +11,7 @@
class HigherOrderProxyTest extends TestCase
{
- public function testGetProxiesPropertyAccessToItems()
+ public function testGetProxiesPropertyAccessToItems(): void
{
$items = new Collection([
(object) ['name' => 'Alice'],
@@ -25,7 +25,7 @@ public function testGetProxiesPropertyAccessToItems()
$this->assertEquals(['Alice', 'Bob'], $proxy->name->all());
}
- public function testCallProxiesMethodCallToItems()
+ public function testCallProxiesMethodCallToItems(): void
{
$items = new Collection([
new class {
@@ -49,7 +49,7 @@ public function shout($s)
$this->assertEquals(['HEY', 'HEY!'], $result->all());
}
- public function testCallForwardsAndReturnsTarget()
+ public function testCallForwardsAndReturnsTarget(): void
{
$target = new class {
public $count = 0;
From 7063622782b96b97844ec31ef725c6be02c0ea66 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:45 +0000
Subject: [PATCH 35/40] Update the collections guide
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.
---
src/boost/docs/collections.md | 117 +++++++++++++++-------------------
1 file changed, 51 insertions(+), 66 deletions(-)
diff --git a/src/boost/docs/collections.md b/src/boost/docs/collections.md
index 21d06067c..32be82c6e 100644
--- a/src/boost/docs/collections.md
+++ b/src/boost/docs/collections.md
@@ -122,8 +122,6 @@ For the majority of the remaining collection documentation, we'll discuss each m
[combine](#method-combine)
[concat](#method-concat)
[contains](#method-contains)
-[containsManyItems](#method-containsmanyitems)
-[containsOneItem](#method-containsoneitem)
[containsStrict](#method-containsstrict)
[count](#method-count)
[countBy](#method-countBy)
@@ -209,6 +207,7 @@ For the majority of the remaining collection documentation, we'll discuss each m
[random](#method-random)
[range](#method-range)
[reduce](#method-reduce)
+[reduceInto](#method-reduce-into)
[reduceSpread](#method-reduce-spread)
[reduceWithKeys](#method-reduce-with-keys)
[reject](#method-reject)
@@ -605,68 +604,6 @@ The `contains` method uses "loose" comparisons when checking item values, meanin
For the inverse of `contains`, see the [doesntContain](#method-doesntcontain) method.
-
-#### `containsManyItems()` {.collection-method}
-
-The `containsManyItems` method determines whether the collection contains more than one item:
-
-```php
-collect([])->containsManyItems();
-
-// false
-
-collect(['first'])->containsManyItems();
-
-// false
-
-collect(['first', 'second'])->containsManyItems();
-
-// true
-```
-
-When using the `Collection` class, you may pass a closure to determine whether the collection contains more than one item matching a given truth test:
-
-```php
-$collection = collect(['ant', 'bear', 'cat', 'deer']);
-
-$collection->containsManyItems(function (string $value) {
- return strlen($value) === 4;
-});
-
-// true
-```
-
-
-#### `containsOneItem()` {.collection-method}
-
-The `containsOneItem` method determines whether the collection contains exactly one item:
-
-```php
-collect([])->containsOneItem();
-
-// false
-
-collect(['first'])->containsOneItem();
-
-// true
-
-collect(['first', 'second'])->containsOneItem();
-
-// false
-```
-
-You may pass a closure to determine whether the collection contains exactly one item matching a given truth test:
-
-```php
-$collection = collect(['ant', 'bear', 'cat']);
-
-$collection->containsOneItem(function (string $value) {
- return strlen($value) === 4;
-});
-
-// true
-```
-
#### `containsStrict()` {.collection-method}
@@ -2634,6 +2571,17 @@ $random->all();
// [2, 4, 5] - (retrieved randomly)
```
+Pass `true` as the second argument to preserve the original keys when retrieving multiple items:
+
+```php
+$random = collect(['desk' => 100, 'chair' => 50, 'lamp' => 25])
+ ->random(2, preserveKeys: true);
+
+$random->keys()->all();
+
+// ['desk', 'lamp'] - (retrieved randomly)
+```
+
If the collection instance has fewer items than requested, the `random` method will throw an `InvalidArgumentException`.
The `random` method also accepts a closure, which will receive the current collection instance:
@@ -2708,6 +2656,35 @@ $collection->reduce(function (int $carry, int $value, string $key) use ($ratio)
// 4264
```
+
+#### `reduceInto()` {.collection-method}
+
+The `reduceInto` method reduces the collection to a single value by mutating the given initial value. Unlike the `reduce` method, the given callback does not need to return the accumulated value:
+
+```php
+class OrderStats
+{
+ public int $total = 0;
+
+ public int $count = 0;
+}
+
+$orders = collect([
+ ['amount' => 100],
+ ['amount' => 250],
+ ['amount' => 50],
+]);
+
+$stats = $orders->reduceInto(new OrderStats, function (OrderStats $stats, array $order) {
+ $stats->total += $order['amount'];
+ $stats->count++;
+});
+
+$stats->total;
+
+// 400
+```
+
#### `reduceSpread()` {.collection-method}
@@ -3452,6 +3429,15 @@ $collection->sum(function (array $product) {
// 6
```
+The callback receives the item key as its second argument:
+
+```php
+$total = collect(['standard' => 100, 'priority' => 200])
+ ->sum(fn (int $price, string $type) => $type === 'priority' ? $price + 25 : $price);
+
+// 325
+```
+
#### `take()` {.collection-method}
@@ -4425,8 +4411,6 @@ Almost all methods available on the `Collection` class are also available on the
[combine](#method-combine)
[concat](#method-concat)
[contains](#method-contains)
-[containsManyItems](#method-containsmanyitems)
-[containsOneItem](#method-containsoneitem)
[containsStrict](#method-containsstrict)
[count](#method-count)
[countBy](#method-countBy)
@@ -4504,6 +4488,7 @@ Almost all methods available on the `Collection` class are also available on the
[random](#method-random)
[range](#method-range)
[reduce](#method-reduce)
+[reduceInto](#method-reduce-into)
[reduceSpread](#method-reduce-spread)
[reduceWithKeys](#method-reduce-with-keys)
[reject](#method-reject)
From 14e2535a5be691b1b85d74a781db578d8001b91f Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:45 +0000
Subject: [PATCH 36/40] Update collection helper documentation
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.
---
src/boost/docs/helpers.md | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/src/boost/docs/helpers.md b/src/boost/docs/helpers.md
index c02220017..6132a7ee2 100644
--- a/src/boost/docs/helpers.md
+++ b/src/boost/docs/helpers.md
@@ -402,7 +402,7 @@ $flattened = Arr::dot($array);
#### `Arr::every()` {.collection-method}
-The `Arr::every` method ensures that all values in the array pass a given truth test:
+The `Arr::every` method ensures that all values in an iterable pass a given truth test:
```php
use Hypervel\Support\Arr;
@@ -418,6 +418,20 @@ Arr::every($array, fn ($i) => $i > 2);
// false
```
+The method also accepts any iterable, including generators:
+
+```php
+$values = function () {
+ yield 1;
+ yield 2;
+ yield 3;
+};
+
+Arr::every($values(), fn (int $value) => $value > 0);
+
+// true
+```
+
#### `Arr::except()` {.collection-method}
@@ -482,7 +496,7 @@ $exists = Arr::exists($array, 'salary');
#### `Arr::first()` {.collection-method}
-The `Arr::first` method returns the first element of an array passing a given truth test:
+The `Arr::first` method returns the first element of an iterable passing a given truth test:
```php
use Hypervel\Support\Arr;
@@ -496,6 +510,8 @@ $first = Arr::first($array, function (int $value, int $key) {
// 200
```
+The first argument may be any iterable, including a generator.
+
A default value may also be passed as the third parameter to the method. This value will be returned if no value passes the truth test:
```php
@@ -754,7 +770,7 @@ $keyed = Arr::keyBy($array, 'product_id');
#### `Arr::last()` {.collection-method}
-The `Arr::last` method returns the last element of an array passing a given truth test:
+The `Arr::last` method returns the last element of an iterable passing a given truth test:
```php
use Hypervel\Support\Arr;
@@ -776,6 +792,8 @@ use Hypervel\Support\Arr;
$last = Arr::last($array, $callback, $default);
```
+The first argument may be any iterable, including a generator.
+
#### `Arr::map()` {.collection-method}
@@ -1164,7 +1182,7 @@ $value = Arr::sole($array, fn (string $value) => $value === 'Desk');
#### `Arr::some()` {.collection-method}
-The `Arr::some` method ensures that at least one of the values in the array passes a given truth test:
+The `Arr::some` method ensures that at least one of the values in an iterable passes a given truth test:
```php
use Hypervel\Support\Arr;
@@ -1176,6 +1194,8 @@ Arr::some($array, fn ($i) => $i > 2);
// true
```
+The first argument may be any iterable, including a generator.
+
#### `Arr::sort()` {.collection-method}
From cf11d43e341977055e709743b6bd823ace6c030e Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:56:46 +0000
Subject: [PATCH 37/40] Document Collections differences from Laravel
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.
---
src/collections/README.md | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 src/collections/README.md
diff --git a/src/collections/README.md b/src/collections/README.md
new file mode 100644
index 000000000..a5d0b3370
--- /dev/null
+++ b/src/collections/README.md
@@ -0,0 +1,14 @@
+Collections for Hypervel
+===
+
+[](https://deepwiki.com/hypervel/collections)
+
+Ported from: https://github.com/laravel/framework/tree/13.x/src/Illuminate/Collections
+
+## Differences From Laravel
+
+Laravel's deprecated `containsOneItem()` and `containsManyItems()` aliases are intentionally not ported. Use `hasSole()` and `hasMany()` instead.
+
+`Enumerable::random()` exposes the optional `preserveKeys` argument supported by both concrete collection implementations and reports the preserved key type accurately. Laravel exposes this argument only on the concrete classes and always reports integer keys through the contract.
+
+`Arr::push()` accepts arrays only. Laravel also advertises `ArrayAccess`, but dot notation requires nested by-reference mutation that `ArrayAccess` cannot provide.
From 40aa706d15be9490fbf66a9669df4b10fc211dd9 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 09:57:39 +0000
Subject: [PATCH 38/40] Record the completed Collections audit
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.
---
...-coroutine-state-lifecycle-audit-ledger.md | 29 +++++++++++++++++++
...amework-coroutine-state-lifecycle-audit.md | 6 ++--
2 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
index 8376e2955..fa5c72309 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -187,3 +187,32 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Implementation and cleanup:** `Request::flushState()` now resets the lazy format table and both method-override settings directly, resets Symfony's private request-factory slot through its public setter, and then clears macros. No production path, inherited public mutator, trusted-request implementation, or immutable metadata changes.
- **Validation:** A focused real-subscriber regression proves all four inherited values and their visible custom behavior disappear after framework cleanup. Focused HTTP/server coverage and the complete `composer fix` gate pass; final review sign-off and owner pre-commit approval are complete. The later full `http` and `testing` audits must retain and revalidate this boundary.
- **Revalidation:** Full `http` and `testing` audits remain pending.
+
+### Correct Collections parity and iterable contracts
+
+- **Architecture and inspected risk surfaces:** Collections is a provider-free value and iterator package. Its only worker-lifetime state is the intentional Macroable registries on `Arr`, `Collection`, and `LazyCollection` plus the higher-order proxy registry shared by the concrete collections; all are already reset through the centralized test subscriber. The audit covered every package source file, split-package metadata, package-owned and Hypervel-specific tests, static-analysis fixtures, framework callers, public Collections and Arr documentation, current Laravel 13.x source/tests/docs, and each originating upstream change used to discover adjacent surfaces. No coroutine, container-lifetime, native-resource, timer, process, or cleanup owner exists in this package.
+
+| ID | Category | Severity | Confidence | Failure and owning boundary | Final decision |
+|---|---|---|---|---|---|
+| `collections-01` | Defect | Major | High | `LazyCollection::keyBy()` casts backed-enum keys to strings and crashes, while eager collections support them | Normalize enum and stringable object keys through the same object-only branch in both implementations |
+| `collections-02` | Defect | Major | High | `LazyCollection::has()` counts duplicate yielded keys as distinct requested keys and disagrees with eager collections for an empty key set | Track outstanding keys by identity, remove each once, and return vacuous truth before iteration |
+| `collections-03` | Defect | Major | High | `sum()` does not pass item keys to callbacks as Laravel's public behavior promises | Invoke the callback with value and key and correct its callable type |
+| `collections-04` | Improvement | Improvement | High | Hypervel lacks Laravel's `reduceInto()` API, tests, type inference, and documentation | Port the current contract and shared implementation plus focused tests, types, and both documentation indexes |
+| `collections-05` | Improvement | Improvement | High | `LazyCollection::random()` omits key preservation, while the shared contract cannot accurately describe the key type that both concrete implementations return when preservation is enabled | Add the optional argument and conditional key return to the contract and both implementations, with runtime and interface-level type coverage |
+| `collections-06` | Defect | Major | High | `Arr::last()`, `every()`, and `some()` advertise iterables but fail on Traversables; `first()` and the initial `last()` fix collapse duplicate iterator keys; and `push()` advertises an ArrayAccess mutation that cannot work or satisfy its array return | Retain native array fast paths, stream Traversables without losing occurrences, preserve reverse callback semantics for `last()`, and narrow only the impossible `push()` input to arrays with a focused source explanation |
+| `collections-07` | Improvement | Improvement | High | The deprecated `containsOneItem()` and `containsManyItems()` aliases remain after their replacements `hasSole()` and `hasMany()` are available | Remove aliases, stale tests, and public-guide entries; record the intentional omission in source/test markers and the package README |
+| `collections-08` | Defect | Major | High | The split package requires an unused PHP 8.6 polyfill but omits the PHP 8.5 polyfill that provides functions used on Hypervel's PHP 8.4 floor | Replace php86 with php85, add Laravel's VarDumper suggestion for opt-in dump methods, and normalize direct dependency order |
+| `collections-09` | Improvement | Improvement | High | Several public annotations and ArrayAccess parameter names are incomplete or wrong, including a default callable documented with an argument it never receives | Port only source-backed current annotations that improve Hypervel's public inference, correct two upstream-shared PHPDoc forms rejected by the installed PHPStan stack, fix named-argument compatibility, and add compact targeted type fixtures |
+| `collections-10` | Improvement | Improvement | High | One per-item lazy timing path uses Carbon's slow magic property and package-owned tests retain the wrong base class or missing required return types | Use the direct timestamp getter and bring the complete package test surface to repository standards |
+| `collections-11` | Defect | Minor | High | The concrete `sum()` annotation reports one callback result as the accumulated total, producing impossible literal return types for multi-item collections | Report `int|float|TReturnType` through both the implementation and `Enumerable` contract, and regress multi-item callback and selector inference |
+| `collections-12` | Defect | Major | High | `LazyCollection::combine()` and `zip()` advertise iterable inputs but their shared iterator builder rejects every plain `Iterator`, including generators, while its callable PHPDoc excludes a supported scalar-result fallback | Accept the complete explicit Iterator union at the shared builder, document iterable-returning callables precisely and arbitrary internal callable results conservatively, and regress both public callers with plain iterators and generators |
+| `collections-13` | Defect | Minor | High | `times()` without a callback always yields integers but reports an unresolved generic and infers `mixed` through the contract and both implementations | Use one variance-safe conditional return across all three declarations and regress omitted-callback and callback-result inference for eager and lazy collections |
+
+- **Important rejected concerns:** Do not add coroutine context, locks, scoped bindings, cloning, or cleanup around collection instances or lazy iterators; the package has no hidden request state or owned asynchronous lifecycle. Do not blanket-copy Laravel annotations or style-only rewrites, remove deliberate Hypervel behavior such as depth-aware dot flattening and subclass factory hooks, or claim that one mutable lazy iterator is safe for concurrent consumption. Keep array-native paths instead of materializing ordinary arrays merely to support Traversables.
+- **Cross-package implications:** None. The split manifest directly owns its corrected polyfill and optional dumper metadata. Later `support` and `testing` audits must retain the already-complete centralized macro/proxy resets but receive no new contract from this work.
+- **Approved implementation boundary:** Owner approved the additive Laravel APIs, the deprecated-alias removal, truthful `Arr::push()` narrowing, selective public type corrections, test improvements, and intentional-omission README. Owner separately approved extending `Enumerable::random()` with the optional key-preservation argument shared by both implementations, correcting its conditional key type instead of retaining Laravel's inaccurate interface annotation. Owner also approved correcting callback-based `sum()` inference through the shared contract: the result includes the integer identity, floating-point accumulation, and extension arithmetic objects instead of pretending the total is one callback result. Owner approved the measured correctness costs: approximately 3–4 nanoseconds per `sum()` item to forward keys and approximately 4 nanoseconds per ordinary `Arr::last()` call to preserve its iterable contract. Common `keyBy()` scalar work becomes slightly cheaper, array-native `every()`/`some()` paths remain, non-array materialization is confined to `last()` calls that actually receive a Traversable, and the timestamp path becomes faster. Documentation will additionally cover key-preserving random selection, keyed `sum()` callbacks, and iterable Arr helpers even where Laravel's guide is silent.
+- **Implementation:** Eager and lazy keying now normalize enum and stringable objects consistently; lazy key lookup tracks requested keys instead of counting yields; and keyed sums forward both callback arguments. The shared collection contract now includes `reduceInto()`, truthful key-preserving random results, numeric sum accumulation, and conditional integer results for callback-free `times()`. Arr keeps native array fast paths while correctly streaming Traversables for `first()`, `every()`, and `some()`; callback-free `last()` retains only the final value, while callback-based `last()` retains ordered key/value occurrences so it can preserve reverse callback order and short-circuiting without collapsing duplicate keys. The impossible ArrayAccess mutation was removed from `Arr::push()`. Lazy iterator normalization accepts both IteratorAggregate and plain Iterator sources, including generators, without changing scalar-returning callable runtime support. Deprecated aliases and their stale public docs/tests were removed with intentional-omission markers. Split-package metadata, named-argument parameter names, public annotations, test signatures, and user documentation now match the supported behavior.
+- **Regression tests:** Runtime coverage exercises backed-enum keying, duplicate and empty lazy key sets, keyed sums, mutable reduction, preserved random keys, every accepted Arr iterable shape and short-circuit rule, duplicate iterator keys, exact reverse `last()` callback order, ArrayAccess parameter names, and LazyCollection combine/zip with ArrayIterator and generators. Static fixtures cover Arr refinements; eager, lazy, and contract-level random/sum behavior; iterable-returning lazy factories; callback-free and callback-based `times()`; helper conditionals; and the new reduction API. Removal markers keep future parity work from silently restoring deprecated aliases.
+- **Performance and complexity:** Arrays retain their native PHP fast paths. `Arr::first()` improves Traversable behavior from full materialization to a short-circuiting scan; callback-free Traversable `last()` uses O(1) memory; only callback-based Traversable `last()` stores ordered pairs, which is required to preserve the array path's observable reverse callback and exception semantics. Lazy iterator normalization adds one `instanceof` only when constructing an iterator, not per item. The `times()` correction and other type/documentation changes execute no production code. The only measured hot-path costs are the owner-approved key forwarding in `sum()` and ordinary `Arr::last()` iterable guard; enum scalar work and lazy timing become cheaper. No lock, context state, registry, cache, compatibility layer, or speculative abstraction was added.
+- **Validation:** All affected Collections runtime tests, both real PHPStan configurations, strict split-package manifest validation, stale-reference sweeps, and `git diff --check` pass. The complete `composer fix` gate is green. Pre-implementation consensus, every focused unexpected-finding review, fresh self-review, final code-review sign-off, and owner pre-commit approval are complete.
+- **Assessment:** Collections remains a provider-free, lock-free value/iterator package. The changes correct verified parity, iterable, typing, metadata, and test-isolation defects at their existing boundaries, add only current general-purpose APIs with real upstream provenance, and avoid production lifecycle machinery the package does not need.
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
index bf79c521f..92276e0b1 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
@@ -974,8 +974,8 @@ An exceptionally large shared work unit may receive its own linked detail plan w
This compact index routes the completed-work history that must be consulted with the full plan after compaction. Detailed history remains in the [companion ledger](2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md).
-- **Active package or work unit:** `collections`
-- **Ledger entries required for the active work:** `Restore Conditionable proxy truthiness`; `Complete Macroable callable and test-state handling`
+- **Active package or work unit:** `reflection`
+- **Ledger entries required for the active work:** none
- **Pending revalidation carried into the active work:** none
Update these three lines when a package starts, completes, or gains a cross-package dependency. Name exact work-unit headings or shared finding IDs from the companion ledger; never use “see recent entries” or require a full-ledger reread.
@@ -1017,7 +1017,7 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
- [x] `contracts`
- [x] `conditionable`
- [x] `macroable`
-- [ ] `collections`
+- [x] `collections`
- [ ] `reflection`
- [ ] `config`
- [ ] `container`
From 2ed36585a59a2f6c257a3dedd15dc568f4824879 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 10:58:35 +0000
Subject: [PATCH 39/40] Preserve console contracts during Composer scripts
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.
---
...-coroutine-state-lifecycle-audit-ledger.md | 10 ++++
...amework-coroutine-state-lifecycle-audit.md | 1 +
src/console/src/Application.php | 22 +++++++++
.../ConsoleApplicationCompatibilityTest.php | 49 +++++++++++++++++++
tests/Console/ContainerCommandLoaderTest.php | 2 +-
.../Fixtures/LegacySymfonyApplication.php | 44 +++++++++++++++++
.../Scheduling/CacheEventMutexTest.php | 2 +-
.../Scheduling/CacheSchedulingMutexTest.php | 2 +-
tests/Console/Scheduling/FrequencyTest.php | 4 +-
9 files changed, 132 insertions(+), 4 deletions(-)
create mode 100644 tests/Console/ConsoleApplicationCompatibilityTest.php
create mode 100644 tests/Console/Fixtures/LegacySymfonyApplication.php
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
index fa5c72309..3c8fb3a61 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -216,3 +216,13 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Performance and complexity:** Arrays retain their native PHP fast paths. `Arr::first()` improves Traversable behavior from full materialization to a short-circuiting scan; callback-free Traversable `last()` uses O(1) memory; only callback-based Traversable `last()` stores ordered pairs, which is required to preserve the array path's observable reverse callback and exception semantics. Lazy iterator normalization adds one `instanceof` only when constructing an iterator, not per item. The `times()` correction and other type/documentation changes execute no production code. The only measured hot-path costs are the owner-approved key forwarding in `sum()` and ordinary `Arr::last()` iterable guard; enum scalar work and lazy timing become cheaper. No lock, context state, registry, cache, compatibility layer, or speculative abstraction was added.
- **Validation:** All affected Collections runtime tests, both real PHPStan configurations, strict split-package manifest validation, stale-reference sweeps, and `git diff --check` pass. The complete `composer fix` gate is green. Pre-implementation consensus, every focused unexpected-finding review, fresh self-review, final code-review sign-off, and owner pre-commit approval are complete.
- **Assessment:** Collections remains a provider-free, lock-free value/iterator package. The changes correct verified parity, iterable, typing, metadata, and test-isolation defects at their existing boundaries, add only current general-purpose APIs with real upstream provenance, and avoid production lifecycle machinery the package does not need.
+
+### Shared finding `console-01`: preserve typed console contracts during Composer scripts
+
+- **Owner:** `console`, specifically the concrete application implementation of the typed `contracts` console boundary.
+- **Affected packages:** `console` and the completed `contracts` audit.
+- **Failure:** Composer can preload its bundled older Symfony Console `Application` before running Hypervel's post-autoload scripts. Its inherited `all()` and `run()` methods have no return types, so loading `Hypervel\Console\Application` fails when PHP checks them against Hypervel's accurately typed contract.
+- **Decision:** Keep the truthful contract and declare transparent typed `all()` and `run()` bridges on the concrete application. Do not change `addCommand()` or mark it with `#[Override]`, because Composer's older Symfony parent does not provide that method.
+- **Implementation and cleanup:** Added the two parent-delegating methods with a focused explanation of the non-obvious preload boundary. Added a deterministic legacy-parent fixture that reproduces Composer's relevant method surface without depending on an installed Composer version. Migrated every remaining raw Console PHPUnit test to the framework unit-test base so coroutine execution, Mockery verification, and framework teardown remain authoritative.
+- **Validation:** Reflection coverage pins both methods to the concrete class and their contract return types. An isolated subprocess loads the literal Hypervel class against the legacy parent shape, the exact installed Composer PHAR preload succeeds, focused console and corrected-base tests pass, and the complete `composer fix` gate is green. Fresh self-review and independent code-review sign-off are complete.
+- **Revalidation:** The strengthened console contract remains unchanged and valid. The later full `console` audit must retain this class-load boundary.
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
index 92276e0b1..35ac90e68 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
@@ -992,6 +992,7 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `queue-01` | `queue` | `contracts`; later full `queue` audit | `Harden framework contracts and request-scoped state`; shared finding `queue-01` |
| `testbench-01` | `testbench` | `foundation`; later full `testbench` and `foundation` audits | `Restore Conditionable proxy truthiness`; shared finding `testbench-01` |
| `http-01` | `http` | `macroable`, `testing`; later full `http` and `testing` audits | `Complete Macroable callable and test-state handling`; shared finding `http-01` |
+| `console-01` | `console` | `contracts`; later full `console` audit | `Preserve typed console contracts during Composer scripts`; shared finding `console-01` |
## Package checklist
diff --git a/src/console/src/Application.php b/src/console/src/Application.php
index 36e6da087..826280aaf 100644
--- a/src/console/src/Application.php
+++ b/src/console/src/Application.php
@@ -73,6 +73,28 @@ public function __construct(
$this->bootstrap();
}
+ // Composer can preload an older, untyped Symfony Console application before
+ // running Hypervel scripts. Keep these contract methods declared locally so
+ // their return types do not depend on the Symfony version already loaded.
+
+ /**
+ * Get all commands registered with the application.
+ */
+ #[Override]
+ public function all(?string $namespace = null): array
+ {
+ return parent::all($namespace);
+ }
+
+ /**
+ * Run the console application.
+ */
+ #[Override]
+ public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
+ {
+ return parent::run($input, $output);
+ }
+
/**
* Determine the proper PHP executable.
*/
diff --git a/tests/Console/ConsoleApplicationCompatibilityTest.php b/tests/Console/ConsoleApplicationCompatibilityTest.php
new file mode 100644
index 000000000..407543aaf
--- /dev/null
+++ b/tests/Console/ConsoleApplicationCompatibilityTest.php
@@ -0,0 +1,49 @@
+assertSame(Application::class, $all->getDeclaringClass()->getName());
+ $this->assertSame('array', (string) $all->getReturnType());
+ $this->assertSame(Application::class, $run->getDeclaringClass()->getName());
+ $this->assertSame('int', (string) $run->getReturnType());
+ }
+
+ public function testApplicationLoadsWhenComposerPreloadsAnOlderSymfonyConsoleVersion(): void
+ {
+ $process = new PhpProcess(<<<'PHP'
+run();
+
+ $this->assertTrue(
+ $process->isSuccessful(),
+ $process->getErrorOutput() ?: $process->getOutput(),
+ );
+ }
+}
diff --git a/tests/Console/ContainerCommandLoaderTest.php b/tests/Console/ContainerCommandLoaderTest.php
index 4365e98df..176ba580a 100644
--- a/tests/Console/ContainerCommandLoaderTest.php
+++ b/tests/Console/ContainerCommandLoaderTest.php
@@ -6,7 +6,7 @@
use Hypervel\Console\ContainerCommandLoader;
use Hypervel\Contracts\Container\Container;
-use PHPUnit\Framework\TestCase;
+use Hypervel\Tests\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
diff --git a/tests/Console/Fixtures/LegacySymfonyApplication.php b/tests/Console/Fixtures/LegacySymfonyApplication.php
new file mode 100644
index 000000000..3b0d7044c
--- /dev/null
+++ b/tests/Console/Fixtures/LegacySymfonyApplication.php
@@ -0,0 +1,44 @@
+event = new Event(
m::mock(EventMutex::class),
'php foo'
From e03b8a637ec5b8e0c11083e96394f6f376ef7ac8 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Mon, 13 Jul 2026 11:50:36 +0000
Subject: [PATCH 40/40] Refine macro and collection fallbacks
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.
---
...-coroutine-state-lifecycle-audit-ledger.md | 2 +-
src/collections/src/Enumerable.php | 1 -
src/collections/src/LazyCollection.php | 2 +-
src/macroable/src/Traits/Macroable.php | 24 +++++--------------
types/Collections/Collection.php | 1 -
5 files changed, 8 insertions(+), 22 deletions(-)
diff --git a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
index 3c8fb3a61..1c3019bfb 100644
--- a/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -204,7 +204,7 @@ Append package entries in checklist order. Keep each entry compact but complete
| `collections-08` | Defect | Major | High | The split package requires an unused PHP 8.6 polyfill but omits the PHP 8.5 polyfill that provides functions used on Hypervel's PHP 8.4 floor | Replace php86 with php85, add Laravel's VarDumper suggestion for opt-in dump methods, and normalize direct dependency order |
| `collections-09` | Improvement | Improvement | High | Several public annotations and ArrayAccess parameter names are incomplete or wrong, including a default callable documented with an argument it never receives | Port only source-backed current annotations that improve Hypervel's public inference, correct two upstream-shared PHPDoc forms rejected by the installed PHPStan stack, fix named-argument compatibility, and add compact targeted type fixtures |
| `collections-10` | Improvement | Improvement | High | One per-item lazy timing path uses Carbon's slow magic property and package-owned tests retain the wrong base class or missing required return types | Use the direct timestamp getter and bring the complete package test surface to repository standards |
-| `collections-11` | Defect | Minor | High | The concrete `sum()` annotation reports one callback result as the accumulated total, producing impossible literal return types for multi-item collections | Report `int|float|TReturnType` through both the implementation and `Enumerable` contract, and regress multi-item callback and selector inference |
+| `collections-11` | Defect | Minor | High | The concrete `sum()` annotation reports one callback result as the accumulated total, producing impossible literal return types for multi-item collections | Report `int\|float\|TReturnType` through both the implementation and `Enumerable` contract, and regress multi-item callback and selector inference |
| `collections-12` | Defect | Major | High | `LazyCollection::combine()` and `zip()` advertise iterable inputs but their shared iterator builder rejects every plain `Iterator`, including generators, while its callable PHPDoc excludes a supported scalar-result fallback | Accept the complete explicit Iterator union at the shared builder, document iterable-returning callables precisely and arbitrary internal callable results conservatively, and regress both public callers with plain iterators and generators |
| `collections-13` | Defect | Minor | High | `times()` without a callback always yields integers but reports an unresolved generic and infers `mixed` through the contract and both implementations | Use one variance-safe conditional return across all three declarations and regress omitted-callback and callback-result inference for eager and lazy collections |
diff --git a/src/collections/src/Enumerable.php b/src/collections/src/Enumerable.php
index 1b92944a9..7ccae30f5 100644
--- a/src/collections/src/Enumerable.php
+++ b/src/collections/src/Enumerable.php
@@ -732,7 +732,6 @@ public function concat(iterable $source): static;
*
* @throws InvalidArgumentException
*/
- // Hypervel exposes key preservation through the contract because every implementation supports it.
public function random(callable|int|string|null $number = null, bool $preserveKeys = false): mixed;
/**
diff --git a/src/collections/src/LazyCollection.php b/src/collections/src/LazyCollection.php
index 6f06f4f9e..865a36d9a 100644
--- a/src/collections/src/LazyCollection.php
+++ b/src/collections/src/LazyCollection.php
@@ -990,7 +990,7 @@ public function concat(iterable $source): static
*/
public function random(callable|int|string|null $number = null, bool $preserveKeys = false): mixed
{
- $result = $this->collect()->random(...func_get_args());
+ $result = $this->collect()->random($number, $preserveKeys);
return is_null($number) ? $result : $this->newInstance($result);
}
diff --git a/src/macroable/src/Traits/Macroable.php b/src/macroable/src/Traits/Macroable.php
index dcf65de1c..3a1736e1e 100644
--- a/src/macroable/src/Traits/Macroable.php
+++ b/src/macroable/src/Traits/Macroable.php
@@ -9,8 +9,6 @@
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
-use RuntimeException;
-use Throwable;
trait Macroable
{
@@ -90,12 +88,8 @@ public static function __callStatic(string $method, array $parameters): mixed
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
- try {
- // PHP warns when a valid first-class callable cannot be rebound; inspect the nullable result instead.
- $macro = @$macro->bindTo(null, static::class) ?? $macro;
- } catch (Throwable) {
- // Keep the original first-class callable when PHP does not permit rebinding it.
- }
+ // PHP warns when a valid first-class callable cannot be rebound; retain the original callable.
+ $macro = @$macro->bindTo(null, static::class) ?? $macro;
}
return $macro(...$parameters);
@@ -119,16 +113,10 @@ public function __call(string $method, array $parameters): mixed
$macro = static::$macros[$method];
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.
- }
- }
+ // PHP warns when a valid callable cannot use a binding form; select the first form it accepts.
+ $macro = @$macro->bindTo($this, static::class)
+ ?? @$macro->bindTo(null, static::class)
+ ?? $macro;
}
return $macro(...$parameters);
diff --git a/types/Collections/Collection.php b/types/Collections/Collection.php
index 9809b9855..8537ea18d 100644
--- a/types/Collections/Collection.php
+++ b/types/Collections/Collection.php
@@ -57,7 +57,6 @@
*/
function assertEnumerableTypes(Enumerable $enumerable): void
{
- // Hypervel exposes preserveKeys on the contract because both concrete collections support it.
assertType('Hypervel\Support\Enumerable', $enumerable->random(2));
assertType('Hypervel\Support\Enumerable', $enumerable->random(2, true));
assertType('float|int', $enumerable->sum(static fn (int $value): int => $value));