Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
21f9d40
:memo: docs(arraykit): add ArrayKit 5.3.0 implementation and release …
abmmhasan Oct 4, 2026
5095378
test(arraykit): add batch A release regressions
abmmhasan Oct 4, 2026
06ebc53
fix(facade): preserve reference mutations through module proxies
abmmhasan Oct 4, 2026
a12ba06
fix(collection): replay terminal lazy source failures
abmmhasan Oct 4, 2026
72c6445
fix(config): own layered materialization and state mutations
abmmhasan Oct 4, 2026
cf5b3e7
fix(dot): share safe traversal budgets across requested paths
abmmhasan Oct 4, 2026
463267d
fix(dot): carry traversal state through safe resolution
abmmhasan Oct 4, 2026
1823fd6
fix(dot): bound wildcard fan-out and depth traversal
abmmhasan Oct 4, 2026
ee7b1ee
fix(array): count examined elements in guarded traversals
abmmhasan Oct 4, 2026
98834a2
fix(array): bound recursive sort work by element budget
abmmhasan Oct 4, 2026
b7cfac3
docs(facade): document reference-preserving proxy mutators
abmmhasan Oct 4, 2026
b3f575d
docs(facade): clarify mutation reference behavior
abmmhasan Oct 4, 2026
ccd38ba
docs(plan): track batch A implementation and QA
abmmhasan Oct 4, 2026
2f0bb40
fix(array): simplify bounded traversal accounting
abmmhasan Oct 4, 2026
b7454b4
fix(array): align sort budget helper with analysis rules
abmmhasan Oct 4, 2026
ab61aaf
fix(dot): preserve bounded wildcard fallback semantics
abmmhasan Oct 4, 2026
ec34d1d
fix(collection): retain lazy failure state explicitly
abmmhasan Oct 4, 2026
7e02638
fix(facade): validate typed proxy mutation results
abmmhasan Oct 4, 2026
fe70ccf
style(config): align layered ownership with project ordering
abmmhasan Oct 4, 2026
bdd251a
fix(config): keep full layered replacements authoritative
abmmhasan Oct 4, 2026
7cf7974
test(arraykit): complete batch A acceptance coverage
abmmhasan Oct 4, 2026
d28b996
style(arraykit): satisfy PHPForge class separation
abmmhasan Oct 4, 2026
6043566
style(arraykit): satisfy PHPForge class separation
abmmhasan Oct 4, 2026
51787b5
style(arraykit): satisfy PHPForge class separation
abmmhasan Oct 4, 2026
f2f01a3
docs(plan): close batch A and start batch B
abmmhasan Oct 4, 2026
e39005f
test(config): add batch B cache lifecycle regressions
abmmhasan Oct 4, 2026
583a444
fix(config): harden memoization and compiled cache export
abmmhasan Oct 4, 2026
9972347
fix(config): track lazy namespace provenance
abmmhasan Oct 4, 2026
52dd7df
fix(config): publish immutable cache generations
abmmhasan Oct 4, 2026
464568e
fix(config): preserve resilient cache provenance
abmmhasan Oct 4, 2026
bf6e2b1
test(config): resolve active cache generations
abmmhasan Oct 4, 2026
10b2bba
test(config): clean generated benchmark generations
abmmhasan Oct 4, 2026
b0a6973
style(config): align cache lifecycle owners with PHPForge ordering
abmmhasan Oct 4, 2026
88c1954
style(config): align cache lifecycle owners with PHPForge ordering
abmmhasan Oct 4, 2026
d081e6e
style(config): align cache lifecycle owners with PHPForge ordering
abmmhasan Oct 4, 2026
9448433
docs(config): document immutable namespace cache generations
abmmhasan Oct 4, 2026
4ed7100
docs(config): align layered and resilient cache semantics
abmmhasan Oct 4, 2026
6ec7732
docs(config): define export and memoization contracts
abmmhasan Oct 4, 2026
9cefa62
docs(config): update namespace cache layout
abmmhasan Oct 4, 2026
27093ac
test(config): complete batch B acceptance matrix
abmmhasan Oct 4, 2026
371c597
fix(config): consume generated syntax validation result
abmmhasan Oct 4, 2026
d207ce1
refactor(config): move generated cache state ownership to cache trait
abmmhasan Oct 4, 2026
41cadba
fix(config): centralize generated cache lifecycle state
abmmhasan Oct 4, 2026
4ae8251
style(config): finalize cache trait method ordering
abmmhasan Oct 4, 2026
e64e23b
style(config): restore base config class-element structure
abmmhasan Oct 4, 2026
42cae54
style(config): restore lazy config class-element structure
abmmhasan Oct 4, 2026
b1a93f4
style(config): restore cache trait class-element structure
abmmhasan Oct 4, 2026
7bf6be4
test(config): reuse fixture enum for export coverage
abmmhasan Oct 4, 2026
a77f12d
style(config): restore trait member indentation
abmmhasan Oct 4, 2026
be7d277
style(config): remove trailing class separator
abmmhasan Oct 4, 2026
dd68add
docs(plan): close batch A and start batch B
abmmhasan Oct 5, 2026
7315cb9
fix(config): invalidate memos and version lazy flat cache
abmmhasan Oct 5, 2026
7f3f913
fix(config): remove duplicate flat-index constant
abmmhasan Oct 5, 2026
d4a7c60
refactor(array): centralize strict membership lookup semantics
abmmhasan Oct 5, 2026
4ff0844
fix(array): preserve membership and SQL-like match semantics
abmmhasan Oct 5, 2026
520aa43
fix(dot): distinguish wildcard presence from leaf values
abmmhasan Oct 5, 2026
83767a2
fix(dot): use explicit wildcard existence traversal
abmmhasan Oct 5, 2026
0193de4
fix(array): make pagination offset overflow-safe
abmmhasan Oct 5, 2026
9e5dacf
refactor(config): share append and prepend array resolution
abmmhasan Oct 5, 2026
cd8f572
test(array): cover batch C semantic boundaries
abmmhasan Oct 5, 2026
3d87514
test(array): fix strict membership threshold fixtures
abmmhasan Oct 5, 2026
1c3b74e
fix(dot): resolve path owner from concern namespace
abmmhasan Oct 5, 2026
6fa5b69
refactor(dot): align wildcard existence traversal
abmmhasan Oct 5, 2026
47c4436
style(array): order shared membership helpers
abmmhasan Oct 5, 2026
ccd61d1
style(config): order mutation helper after protected API
abmmhasan Oct 5, 2026
ac5260e
test(array): correct SQL wildcard and PCRE failure cases
abmmhasan Oct 5, 2026
0e2a5b0
docs(plan): close batch B and track C-D progress
abmmhasan Oct 5, 2026
7011a2c
perf(collection): avoid replay memo for array lazy sources
abmmhasan Oct 5, 2026
156a6ab
bench(collection): compare lazy replay source costs
abmmhasan Oct 5, 2026
f84b003
feat(dto): add bounded graph traversal guard
abmmhasan Oct 5, 2026
9b1069b
feat(dto): expose bounded nested graph APIs
abmmhasan Oct 5, 2026
e1d44f3
test(dto): cover batch D graph and lazy replay boundaries
abmmhasan Oct 5, 2026
e652172
docs(dto): document guarded graph API signatures
abmmhasan Oct 5, 2026
8b251d7
docs(dto): explain bounded graph ownership
abmmhasan Oct 5, 2026
e7a94b0
docs(runtime): define ownership and trust boundaries
abmmhasan Oct 5, 2026
13fea6d
docs(runtime): add lifecycle guide to manual
abmmhasan Oct 5, 2026
9b2fb67
docs(collection): document replay memory ownership
abmmhasan Oct 5, 2026
b6d3433
feat(collection): add explicit Runwire lazy binding
abmmhasan Oct 5, 2026
c5e857e
fix(collection): bind exact Runwire runtime lifecycle
abmmhasan Oct 5, 2026
7e7ad80
feat(collection): propagate Runwire checkpoints through lazy traversal
abmmhasan Oct 5, 2026
16da7d0
build(arraykit): test optional Runwire 2.1.1 integration
abmmhasan Oct 5, 2026
4311dd9
test(collection): cover Runwire instance binding matrix
abmmhasan Oct 5, 2026
1dfc1bd
docs(api): document DTO guards and Runwire binding
abmmhasan Oct 5, 2026
0277531
docs(collection): document optional Runwire lifecycle binding
abmmhasan Oct 5, 2026
31c6a1e
bench(collection): measure bound Runwire checkpoint overhead
abmmhasan Oct 5, 2026
887984c
docs(plan): sync D-E implementation and QA status
abmmhasan Oct 5, 2026
0c0214b
docs(collection): add intermediary Runwire forwarding example
abmmhasan Oct 5, 2026
b489514
docs(readme): note optional Runwire integration
abmmhasan Oct 5, 2026
dc636ec
docs(plan): record PHPBench dependency outcome
abmmhasan Oct 5, 2026
1afa04f
test(dto): move batch D DTO fixtures under PSR-4
abmmhasan Oct 5, 2026
7d887ee
test(dto): move batch D DTO fixtures under PSR-4
abmmhasan Oct 5, 2026
92e35ad
test(dto): move batch D DTO fixtures under PSR-4
abmmhasan Oct 5, 2026
3f94e40
test(dto): move batch D DTO fixtures under PSR-4
abmmhasan Oct 5, 2026
ac123ee
test(dto): move batch D DTO fixtures under PSR-4
abmmhasan Oct 5, 2026
11a0a24
test(dto): use PSR-4 batch D fixtures
abmmhasan Oct 5, 2026
750f90e
style(collection): consume optional binding parameters explicitly
abmmhasan Oct 5, 2026
cff8325
style(bench): order collection benchmark methods
abmmhasan Oct 5, 2026
f63a3ac
style(bench): order Runwire benchmark methods
abmmhasan Oct 5, 2026
248a6e7
test(collection): assert active-scope cancellation at callback boundary
abmmhasan Oct 5, 2026
bb529cd
style(bench): place setup before benchmark subjects
abmmhasan Oct 5, 2026
ef3df23
style(bench): place Runwire setup before subjects
abmmhasan Oct 5, 2026
2919ba2
test(collection): isolate ArrayKit Runwire scope ownership
abmmhasan Oct 5, 2026
7be527f
docs(release): add 5.3 upgrade guidance
abmmhasan Oct 5, 2026
8ff1804
docs(release): consolidate 5.3 candidate notes
abmmhasan Oct 5, 2026
33a0768
docs(release): link 5.3 candidate notes
abmmhasan Oct 5, 2026
a5e7236
docs(plan): sync tracker through final acceptance
abmmhasan Oct 5, 2026
9dc2ab5
test(collection): observe cancellation state after traversal
abmmhasan Oct 5, 2026
e2982ab
style(bench): normalize benchmark element spacing
abmmhasan Oct 5, 2026
bd962bb
style(bench): normalize Runwire benchmark spacing
abmmhasan Oct 5, 2026
ca5f39a
test(collection): exercise concurrent Runwire cancellation at checkpoint
abmmhasan Oct 5, 2026
2ff8829
style(bench): normalize class closing separation
abmmhasan Oct 5, 2026
5a05c11
style(bench): normalize Runwire class closing separation
abmmhasan Oct 5, 2026
0ad182f
docs(plan): close batches C through E
abmmhasan Oct 5, 2026
4f91c89
test(release): add host and persistent-worker acceptance
abmmhasan Oct 5, 2026
7564f99
test(release): add persistent-worker soak workload
abmmhasan Oct 5, 2026
886b497
test(release): add worker soak entrypoint
abmmhasan Oct 5, 2026
82b9f6f
bench(release): add representative workload worker
abmmhasan Oct 5, 2026
8520f12
bench(release): generate same-run baseline and candidate results
abmmhasan Oct 5, 2026
5ece051
build(release): expose representative benchmark script
abmmhasan Oct 5, 2026
da56999
ci(release): add representative benchmark and worker soak gates
abmmhasan Oct 5, 2026
6303d1d
style(composer): normalize representative benchmark script
abmmhasan Oct 5, 2026
b5e62da
style(bench): satisfy CLI function policy
abmmhasan Oct 5, 2026
a5b7aba
ci(release): use PHPForge Pest path for acceptance
abmmhasan Oct 5, 2026
04cf58d
ci(release): mirror PHPForge Pest invocation
abmmhasan Oct 5, 2026
3e0e770
docs(plan): record completed ArrayKit release acceptance
abmmhasan Oct 5, 2026
5c1b2c6
docs(plan): record final ArrayKit acceptance evidence
abmmhasan Oct 5, 2026
5958774
:wrench: build(ci): increase security soak worker duration and warmup…
abmmhasan Oct 5, 2026
5fbfeaa
:rocket: ci(workflow): update security standards workflow triggers
abmmhasan Oct 5, 2026
9cda2c0
:memo: docs(release): update release documentation for version 5.3.0
abmmhasan Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/security-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,75 @@ on:
- cron: "0 0 * * 0"
push:
branches: [ "main", "master" ]
tags: [ "v*", "[0-9]*" ]
pull_request:
branches: [ "main", "master", "develop", "development" ]

jobs:
release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
uses: infocyph/phpforge/.github/workflows/release.yml@main
permissions:
contents: write
secrets:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}

phpforge:
if: github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')
uses: infocyph/phpforge/.github/workflows/security-standards.yml@main
with:
integration_services: '[]'
service_topologies: '{}'
benchmark_composer_script: "benchmark:representative"
benchmark_result_file: "build/release-candidate.json"
benchmark_baseline_file: "build/release-baseline.json"
benchmark_max_regression_percent: 2
benchmark_stable_environment: false
permissions:
security-events: write
actions: read
contents: read

release-acceptance:
if: github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')
name: "Release Acceptance"
needs: phpforge
runs-on: ubuntu-latest
env:
XDEBUG_MODE: off
steps:
- uses: actions/checkout@v7
- name: Setup PHP 8.5
uses: shivammathur/setup-php@v2
with:
php-version: "8.5"
tools: composer:v2
extensions: opcache
ini-values: opcache.enable_cli=1
coverage: none
- name: Install acceptance dependencies
run: composer install --no-interaction --prefer-dist --no-progress
- name: Run ArrayKit release acceptance tests
run: >-
php vendor/bin/pest
--configuration vendor/infocyph/phpforge/resources/pest.xml
--bootstrap vendor/autoload.php
tests/Feature/Release530BatchFTest.php
- name: Run persistent-worker soak
run: |
mkdir -p build
composer ic:soak:worker \
--duration=300 \
--warmup=10 \
--sample-interval=1 \
--max-growth-mb=16 \
--report=build/release-worker-soak.json \
-- php -d opcache.enable_cli=1 tests/Support/release530-persistent-worker.php
- name: Upload worker-soak evidence
if: always() && hashFiles('build/release-worker-soak.json') != ''
uses: actions/upload-artifact@v7
with:
name: release-worker-soak
path: build/release-worker-soak.json
if-no-files-found: error
retention-days: 61
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ configuration or object models.
| **Collection** | OOP array wrapper implementing `ArrayAccess`, `IteratorAggregate`, `Countable`, `JsonSerializable`. |
| **HookedCollection** | Extends `Collection` with **on-get/on-set hooks** for real-time transformation of values. |
| **Pipeline** | Functional-style pipeline for chaining operations on collections. |
| **LazyCollection** | Repeatable lazy operations (`mapLazy`, `filterLazy`, `chunkLazy`, `take`, `takeUntil`), including one-shot generators and renewable factories. |
| **LazyCollection** | Repeatable lazy operations (`mapLazy`, `filterLazy`, `chunkLazy`, `take`, `takeUntil`), including one-shot generators, renewable factories, and optional passed-instance Runwire cancellation/yield checkpoints. |
| **BaseCollectionTrait** | Shared collection behavior. |


Expand Down Expand Up @@ -90,6 +90,8 @@ configuration or object models.

* **PHP 8.4** or higher

Runwire is optional. ArrayKit 5.3 tests its lazy-runtime integration against `infocyph/runwire` 2.1.1; ordinary ArrayKit installation has no Runwire runtime dependency.


## Installation

Expand Down Expand Up @@ -295,7 +297,7 @@ $row = ArrayShape::require(
$config = new LazyFileConfig(__DIR__ . '/config', namespaceCacheDirectory: __DIR__ . '/bootstrap/cache/config');
$config->warmNamespaceCache(['db', 'cache']);

// Exact scalar leaf reads can hit bootstrap/cache/config/__flat.php first.
// Exact scalar leaf reads can hit the active generation's .arraykit-flat.php first.
$host = $config->get('db.host');
```

Expand All @@ -308,7 +310,7 @@ $host = $config->get('db.host');
- `DotNotation` treats existing `null` keys/properties as present (does not fall back to defaults).
- `DotNotation::hasWildcard()`, `paths()`, `matches()`, `rename()`, and `move()` are available for wildcard/path operations.
- For untrusted/deep payloads, use bounded traversal variants: `DotNotation::getSafe()`, `ArrayMulti::depthGuarded()`, `flattenGuarded()`, and `sortRecursiveGuarded()`.
- `LazyFileConfig` namespace cache writes one cache file per namespace plus a shared `__flat.php` file containing only final scalar/null leaf values for exact-key fast paths.
- `LazyFileConfig` publishes immutable cache generations selected by `.arraykit-generation`; each generation contains namespace files plus `.arraykit-flat.php` for exact scalar/null fast paths.

## Security

Expand Down
20 changes: 20 additions & 0 deletions benchmarks/CollectionBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,40 @@ public function benchCollectionMap(): void
Collection::make($this->data)->map(static fn(int $value): int => $value * 2);
}

public function benchLazyArrayReplayMaterialization(): void
{
LazyCollection::from($this->data)->all();
}

public function benchLazyChunkMaterialization(): void
{
LazyCollection::fromFactory(fn(): array => $this->data)
->chunkLazy(100)
->all();
}

public function benchLazyFactoryArrayMaterialization(): void
{
LazyCollection::fromFactory(fn(): array => $this->data)->all();
}

public function benchLazyFilterMaterialization(): void
{
LazyCollection::fromFactory(fn(): array => $this->data)
->filterLazy(static fn(int $value): bool => ($value % 2) === 0)
->all();
}

public function benchLazyGeneratorReplayMaterialization(): void
{
$data = $this->data;
$source = (static function () use ($data): \Generator {
yield from $data;
})();

LazyCollection::from($source)->all();
}

public function benchLazyMapFilterTake(): void
{
LazyCollection::fromFactory(fn(): array => $this->data)
Expand Down
38 changes: 19 additions & 19 deletions benchmarks/LazyFileConfigBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,29 @@ public function setUp(): void

public function tearDown(): void
{
foreach (['app.php', '__flat.php'] as $file) {
$cachePath = $this->cacheDirectory . DIRECTORY_SEPARATOR . $file;
if (is_file($cachePath)) {
unlink($cachePath);
$remove = static function (string $path) use (&$remove): void {
if (is_file($path)) {
unlink($path);

return;
}
}

$sourcePath = $this->sourceDirectory . DIRECTORY_SEPARATOR . 'app.php';
if (is_file($sourcePath)) {
unlink($sourcePath);
}
if (!is_dir($path)) {
return;
}

if (is_dir($this->cacheDirectory)) {
rmdir($this->cacheDirectory);
}
if (is_dir($this->sourceDirectory)) {
rmdir($this->sourceDirectory);
}
foreach (scandir($path) ?: [] as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}

$base = dirname($this->sourceDirectory);
if (is_dir($base)) {
rmdir($base);
}
$remove($path . DIRECTORY_SEPARATOR . $entry);
}

rmdir($path);
};

$remove(dirname($this->sourceDirectory));
}

public function benchAlreadyLoadedNamespace(): void
Expand Down
74 changes: 74 additions & 0 deletions benchmarks/RunwireLazyCollectionBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Infocyph\ArrayKit\Benchmarks;

use Infocyph\ArrayKit\Collection\LazyCollection;
use Infocyph\Runwire\RequestContext;
use Infocyph\Runwire\RuntimeContext;
use PhpBench\Attributes\BeforeMethods;
use PhpBench\Attributes\Iterations;
use PhpBench\Attributes\ParamProviders;
use PhpBench\Attributes\Revs;

#[Revs(1)]
#[Iterations(3)]
#[BeforeMethods('setUp')]
#[ParamProviders('provideSizes')]
final class RunwireLazyCollectionBench
{
/** @var array<int, int> */
private array $data = [];

private RequestContext $request;

private RuntimeContext $runtime;

/** @param array{size:int} $params */
public function setUp(array $params): void
{
$this->data = range(1, $params['size']);
$this->runtime = RuntimeContext::standalone();
$this->request = RequestContext::create($this->runtime);
}

public function benchBoundRequestMapFilter(): void
{
LazyCollection::from($this->data)
->withRunwire($this->runtime, $this->request, checkpointEvery: 256)
->mapLazy(static fn(int $value): int => $value * 2)
->filterLazy(static fn(int $value): bool => ($value % 3) === 0)
->all();
}

public function benchBoundRequestMaterialization(): void
{
LazyCollection::from($this->data)
->withRunwire($this->runtime, $this->request, checkpointEvery: 256)
->all();
}

public function benchUnboundMapFilter(): void
{
LazyCollection::from($this->data)
->mapLazy(static fn(int $value): int => $value * 2)
->filterLazy(static fn(int $value): bool => ($value % 3) === 0)
->all();
}

public function benchUnboundMaterialization(): void
{
LazyCollection::from($this->data)->all();
}

/** @return array<string, array{size:int}> */
public function provideSizes(): array
{
return [
'1k' => ['size' => 1000],
'10k' => ['size' => 10000],
'100k' => ['size' => 100000],
];
}
}
Loading
Loading