From 41e9f5b6230c2c29ac3029316d9ba5f835368389 Mon Sep 17 00:00:00 2001 From: Josiah King Date: Mon, 31 Aug 2026 15:10:46 +0100 Subject: [PATCH 1/2] Phase 6.5C1: add cross-framework comparator foundation --- PHASE_6_5C1_REVIEW_BUNDLE.md | 22 + benchmarks/.gitignore | 3 + benchmarks/README.md | 105 + benchmarks/bin/check-syntax.php | 5 + benchmarks/bin/comparator-smoke.php | 14 + benchmarks/comparators/evolvephp/.placeholder | 2 + .../comparators/evolvephp/bootstrap.php | 7 + .../comparators/evolvephp/composer.json | 36 + .../comparators/evolvephp/composer.lock | 466 ++ .../src/EvolvePhpComparatorFixture.php | 188 + benchmarks/comparators/laravel/.placeholder | 2 + benchmarks/comparators/laravel/bootstrap.php | 13 + benchmarks/comparators/laravel/composer.json | 15 + benchmarks/comparators/laravel/composer.lock | 5842 +++++++++++++++++ .../laravel/src/LaravelComparatorFixture.php | 116 + .../comparators/laravel/src/Middleware.php | 66 + benchmarks/comparators/matrix.json | 199 + benchmarks/comparators/phalcon/.placeholder | 2 + benchmarks/comparators/phalcon/bootstrap.php | 7 + benchmarks/comparators/phalcon/composer.json | 17 + benchmarks/comparators/phalcon/composer.lock | 20 + .../phalcon/src/PhalconComparatorFixture.php | 132 + benchmarks/comparators/slim/.placeholder | 2 + benchmarks/comparators/slim/bootstrap.php | 12 + benchmarks/comparators/slim/composer.json | 16 + benchmarks/comparators/slim/composer.lock | 589 ++ .../slim/src/SlimComparatorFixture.php | 126 + benchmarks/comparators/symfony/.placeholder | 2 + benchmarks/comparators/symfony/bootstrap.php | 12 + benchmarks/comparators/symfony/composer.json | 18 + benchmarks/comparators/symfony/composer.lock | 1045 +++ .../symfony/src/SymfonyComparatorFixture.php | 145 + .../src/Comparator/ComparatorFixture.php | 45 + .../src/Comparator/ComparatorMatrix.php | 206 + .../Comparator/ComparatorMatrixException.php | 11 + .../Comparator/ComparatorResultNormalizer.php | 73 + .../Comparator/ComparatorSmokeVerifier.php | 139 + .../src/Comparator/PhalconAvailability.php | 56 + .../ExecutionEnvironmentFingerprint.php | 62 + benchmarks/src/Support/FixtureIdentity.php | 53 + .../Comparator/BenchmarkSyntaxToolingTest.php | 19 + .../tests/Comparator/ComparatorMatrixTest.php | 326 + .../ComparatorResultNormalizerTest.php | 68 + .../ComparatorSmokeVerifierTest.php | 53 + .../EnvironmentAndFixtureIdentityTest.php | 136 + .../Comparator/PhalconAvailabilityTest.php | 54 + .../EvolvePhp2ComparatorMatrixTest.php | 235 + ...vePhp2ReadmeAndMetadataConsistencyTest.php | 6 +- 48 files changed, 10787 insertions(+), 1 deletion(-) create mode 100644 PHASE_6_5C1_REVIEW_BUNDLE.md create mode 100644 benchmarks/bin/comparator-smoke.php create mode 100644 benchmarks/comparators/evolvephp/.placeholder create mode 100644 benchmarks/comparators/evolvephp/bootstrap.php create mode 100644 benchmarks/comparators/evolvephp/composer.json create mode 100644 benchmarks/comparators/evolvephp/composer.lock create mode 100644 benchmarks/comparators/evolvephp/src/EvolvePhpComparatorFixture.php create mode 100644 benchmarks/comparators/laravel/.placeholder create mode 100644 benchmarks/comparators/laravel/bootstrap.php create mode 100644 benchmarks/comparators/laravel/composer.json create mode 100644 benchmarks/comparators/laravel/composer.lock create mode 100644 benchmarks/comparators/laravel/src/LaravelComparatorFixture.php create mode 100644 benchmarks/comparators/laravel/src/Middleware.php create mode 100644 benchmarks/comparators/matrix.json create mode 100644 benchmarks/comparators/phalcon/.placeholder create mode 100644 benchmarks/comparators/phalcon/bootstrap.php create mode 100644 benchmarks/comparators/phalcon/composer.json create mode 100644 benchmarks/comparators/phalcon/composer.lock create mode 100644 benchmarks/comparators/phalcon/src/PhalconComparatorFixture.php create mode 100644 benchmarks/comparators/slim/.placeholder create mode 100644 benchmarks/comparators/slim/bootstrap.php create mode 100644 benchmarks/comparators/slim/composer.json create mode 100644 benchmarks/comparators/slim/composer.lock create mode 100644 benchmarks/comparators/slim/src/SlimComparatorFixture.php create mode 100644 benchmarks/comparators/symfony/.placeholder create mode 100644 benchmarks/comparators/symfony/bootstrap.php create mode 100644 benchmarks/comparators/symfony/composer.json create mode 100644 benchmarks/comparators/symfony/composer.lock create mode 100644 benchmarks/comparators/symfony/src/SymfonyComparatorFixture.php create mode 100644 benchmarks/src/Comparator/ComparatorFixture.php create mode 100644 benchmarks/src/Comparator/ComparatorMatrix.php create mode 100644 benchmarks/src/Comparator/ComparatorMatrixException.php create mode 100644 benchmarks/src/Comparator/ComparatorResultNormalizer.php create mode 100644 benchmarks/src/Comparator/ComparatorSmokeVerifier.php create mode 100644 benchmarks/src/Comparator/PhalconAvailability.php create mode 100644 benchmarks/src/Support/ExecutionEnvironmentFingerprint.php create mode 100644 benchmarks/src/Support/FixtureIdentity.php create mode 100644 benchmarks/tests/Comparator/BenchmarkSyntaxToolingTest.php create mode 100644 benchmarks/tests/Comparator/ComparatorMatrixTest.php create mode 100644 benchmarks/tests/Comparator/ComparatorResultNormalizerTest.php create mode 100644 benchmarks/tests/Comparator/ComparatorSmokeVerifierTest.php create mode 100644 benchmarks/tests/Comparator/EnvironmentAndFixtureIdentityTest.php create mode 100644 benchmarks/tests/Comparator/PhalconAvailabilityTest.php create mode 100644 tests/Architecture/EvolvePhp2ComparatorMatrixTest.php diff --git a/PHASE_6_5C1_REVIEW_BUNDLE.md b/PHASE_6_5C1_REVIEW_BUNDLE.md new file mode 100644 index 0000000..806d1ba --- /dev/null +++ b/PHASE_6_5C1_REVIEW_BUNDLE.md @@ -0,0 +1,22 @@ +# Phase 6.5C1 Review Bundle + +This file supersedes the earlier draft review notes for Phase 6.5C1. + +Phase 6.5C1 establishes comparator fixture correctness, dependency isolation and reproducibility. It does not establish that EvolvePHP is faster than Laravel, Symfony, Slim or Phalcon. Authoritative cross-framework performance measurements belong to Phase 6.5C2 and require the controlled PHP 8.4 benchmark environment. + +Benchmark and comparator infrastructure is EvolvePHP framework-development tooling. It is not required by applications built with EvolvePHP and is intentionally excluded from normal production application dependency graphs. + +The authoritative implementation-review evidence for this repair was produced in the final agent response for the acceptance-repair turn. The local comparator smoke command is: + +```powershell +D:\php-84\php.exe .\benchmarks\bin\comparator-smoke.php +``` + +Selected comparator versions are recorded in `benchmarks/comparators/matrix.json` with each comparator lockfile SHA-256 hash. External comparator framework identities are exactly pinned: Laravel `13.29.0`, Symfony `8.1.5`, Slim `4.15.2`, and Phalcon expected `5.20.3`. + +The reserved maintainer validators remain pending maintainer validation and were not run by the coding agent: + +- `release:validate` +- `release:split:validate` +- `release:skeleton:validate` +- `release:consumer:validate` diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore index 73a2c81..951b887 100644 --- a/benchmarks/.gitignore +++ b/benchmarks/.gitignore @@ -4,3 +4,6 @@ /results/local/ /results/tmp/ /profiles/ + +# Comparator-local dependencies +/comparators/*/vendor/ diff --git a/benchmarks/README.md b/benchmarks/README.md index 2ed6838..53149f3 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -97,3 +97,108 @@ A result qualifies as a reference baseline only when the documented PHP 8.4 prot Baseline comparison must reject casual comparisons when the environment fingerprint differs. The normalized result schema includes scenario identifiers, sample counts, timing statistics, percentiles when enough samples exist, relative standard deviation, throughput where derivable, memory fields, environment fingerprint, source SHA, and schema version. Cross-framework comparison, optimization work, and regression budgets are intentionally outside this initial harness. They should be introduced only after baseline measurements are reproducible and the relevant comparison methodology is defined. + +## Cross-Framework Comparator Infrastructure + +Benchmark and comparator infrastructure is EvolvePHP framework-development tooling. It is not required by applications built with EvolvePHP and is intentionally excluded from normal production application dependency graphs. + +Phase 6.5C1 establishes comparator fixture correctness, dependency isolation and reproducibility. It does not establish that EvolvePHP is faster than Laravel, Symfony, Slim or Phalcon. + +Authoritative cross-framework performance measurements belong to Phase 6.5C2 and require the controlled PHP 8.4 benchmark environment. + +### Comparator Installation + +Install the benchmark harness explicitly: + +```powershell +composer install --working-dir=benchmarks --no-interaction +``` + +Install comparator fixtures only when doing framework-maintainer comparator work: + +```powershell +composer install --working-dir=benchmarks/comparators/evolvephp --no-interaction +composer install --working-dir=benchmarks/comparators/laravel --no-interaction --ignore-platform-req=ext-fileinfo +composer install --working-dir=benchmarks/comparators/symfony --no-interaction +composer install --working-dir=benchmarks/comparators/slim --no-interaction +composer install --working-dir=benchmarks/comparators/phalcon --no-interaction +``` + +The Laravel lockfile was created on this local PHP CLI with `--ignore-platform-req=ext-fileinfo` because `ext-fileinfo` is not enabled here. The comparator workload does not use file inspection. + +### Comparator Smoke + +Validate comparator fixture correctness without running a controlled benchmark study: + +```powershell +php benchmarks\bin\comparator-smoke.php +``` + +The smoke command loads `benchmarks/comparators/matrix.json`, validates fixture paths and lockfiles, exercises the common scenarios, reports Phalcon unavailable when the extension is missing, and exits non-zero if an available comparator is broken. It emits no rankings and makes no performance claims. + +### Comparator Versions + +The Phase 6.5C1 matrix records these selected versions and lockfile hashes: + +| Comparator | Package | Version | Constraint | Lockfile SHA-256 | +| --- | --- | --- | --- | --- | +| EvolvePHP | `evolvephp/http` | `2.0.0-dev+9a0e741` | `^2.0@dev` | `f792575ec5491c8d3aa171ba5f7de3b38558bfbd82b977beea45e603fd79e491` | +| Laravel | `laravel/framework` | `13.29.0` | `13.29.0` | `33b4d04706fa39dffc1d71a7d2d03f09651555afead629e31f9229adcdc86354` | +| Symfony | `symfony/http-kernel` | `8.1.5` | `8.1.5` | `d93fdac19b2cdd5379e5700a8146bb705c9b516c9ec9a0709dcd785be9b1e1d6` | +| Slim | `slim/slim` | `4.15.2` | `4.15.2` | `87370678970fe51c62c6a4cd4e4ca7b3600b22c84a2a5b920e2b8527a2e089a7` | +| Phalcon | `ext-phalcon` | `5.20.3` expected | `suggest ext-phalcon 5.20.3` | `1e6b5f4b3d70a3e0d5a74eaa55dec95bde1e1d2b33e1c64dc4a737ad8cd01562` | + +The Symfony fixture represents the Symfony 8.1 framework line using `symfony/event-dispatcher 8.1.5`, `symfony/http-foundation 8.1.5`, `symfony/http-kernel 8.1.5`, and `symfony/routing 8.1.5`. + +### Common Scenarios + +The matrix uses only these stable cross-framework scenario IDs: + +- `application_boot`: application/bootstrap setup for the selected fixture model. +- `http_static`: `GET /benchmark`, routed through the normal framework path, HTTP 200, deterministic body. +- `http_parameterized`: `GET /benchmark/123`, with route parameter capture proven by the response and smoke metadata. +- `http_middleware`: `GET /benchmark-middleware`, with five ordered middleware/listener layers proven by smoke metadata. +- `http_not_found`: `GET /benchmark-missing`, a genuinely unmatched path using the normal not-found path. +- `http_repeated_warm`: repeated `GET /benchmark` requests against one pre-booted reusable app/kernel/container. + +The timed workload for these scenarios must not perform database access, network calls, template rendering, filesystem I/O, session storage, external cache access, queues, or application business logic. + +### Dependency Isolation + +Each comparator owns an isolated Composer root: + +```text +benchmarks/comparators/evolvephp/composer.json +benchmarks/comparators/evolvephp/composer.lock +benchmarks/comparators/laravel/composer.json +benchmarks/comparators/laravel/composer.lock +benchmarks/comparators/symfony/composer.json +benchmarks/comparators/symfony/composer.lock +benchmarks/comparators/slim/composer.json +benchmarks/comparators/slim/composer.lock +benchmarks/comparators/phalcon/composer.json +benchmarks/comparators/phalcon/composer.lock +``` + +Laravel, Symfony, Slim and Phalcon comparator dependencies are not installed into `benchmarks/composer.json`, any `packages/*/composer.json`, the root production dependency graph, or the application skeleton. + +### Environment Identity vs Fixture Identity + +Execution environment identity represents shared benchmark conditions: PHP version, SAPI, operating system, CPU, memory, OPcache, JIT, loaded extensions and benchmark execution tooling. It intentionally excludes comparator lockfile hashes. + +Fixture identity represents comparator-specific state: comparator/framework, exact version, fixture version, configuration, Composer lock hash and implementation model. + +Two comparators with different dependency lockfiles can share the same execution environment identity when they run under the same controlled PHP 8.4 benchmark environment. + +### Phalcon Availability + +Phalcon is extension-backed. The Phalcon fixture records two deterministic states: + +- `available`: the `phalcon` extension is loaded, the actual extension version is recorded, and the real `Phalcon\Mvc\Micro` workload can execute. +- `unavailable`: matrix loading and comparator smoke still succeed, the reason is explicit, and no timing data is emitted. + +Local development machines without the extension are allowed to report Phalcon unavailable. That is not performance evidence and not a fixture failure. + +### Local Comparator Results + +Any local comparator output is local and non-canonical. It may prove fixture correctness, availability handling, schema shape and dependency isolation, but it must not be used as an authoritative performance comparison. Phase 6.5C2 owns controlled PHP 8.4 cross-framework measurement, normalization, publication and remediation decisions. diff --git a/benchmarks/bin/check-syntax.php b/benchmarks/bin/check-syntax.php index fefa79b..2ad3efd 100644 --- a/benchmarks/bin/check-syntax.php +++ b/benchmarks/bin/check-syntax.php @@ -8,6 +8,7 @@ $benchmarkRoot . DIRECTORY_SEPARATOR . 'src', $benchmarkRoot . DIRECTORY_SEPARATOR . 'benchmarks', $benchmarkRoot . DIRECTORY_SEPARATOR . 'tests', + $benchmarkRoot . DIRECTORY_SEPARATOR . 'comparators', ]; $failures = []; @@ -19,6 +20,10 @@ continue; } + if (in_array('vendor', explode(DIRECTORY_SEPARATOR, $file->getPathname()), true)) { + continue; + } + $command = [PHP_BINARY, '-l', $file->getPathname()]; $process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $benchmarkRoot); diff --git a/benchmarks/bin/comparator-smoke.php b/benchmarks/bin/comparator-smoke.php new file mode 100644 index 0000000..19a6b3e --- /dev/null +++ b/benchmarks/bin/comparator-smoke.php @@ -0,0 +1,14 @@ +=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "evolvephp/core": 20, + "evolvephp/http": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.4" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/benchmarks/comparators/evolvephp/src/EvolvePhpComparatorFixture.php b/benchmarks/comparators/evolvephp/src/EvolvePhpComparatorFixture.php new file mode 100644 index 0000000..40be031 --- /dev/null +++ b/benchmarks/comparators/evolvephp/src/EvolvePhpComparatorFixture.php @@ -0,0 +1,188 @@ + true, 'status' => 'available']; + } + + public function applicationBoot(): array + { + $this->createKernel(); + + return ['scenario_id' => 'application_boot', 'status' => 'ok']; + } + + public function httpStatic(): array + { + return $this->handle($this->createKernel(), '/benchmark'); + } + + public function httpParameterized(string $id): array + { + $result = $this->handle($this->createKernel(), '/benchmark/' . $id); + $result['parameters'] = ['id' => $id]; + + return $result; + } + + public function httpMiddleware(): array + { + $state = (object) ['order' => []]; + $result = $this->handle($this->createKernel($state), '/benchmark-middleware'); + $result['middleware_order'] = $state->order; + + return $result; + } + + public function httpNotFound(): array + { + return $this->handle($this->createKernel(), '/benchmark-missing'); + } + + public function httpRepeatedWarm(int $requestCount): array + { + $kernel = $this->createKernel(); + $last = null; + + for ($i = 0; $i < $requestCount; ++$i) { + $last = $this->handle($kernel, '/benchmark'); + } + + $last ??= []; + $last['request_count'] = $requestCount; + $last['bootstrap_count'] = 1; + + return $last; + } + + private function createKernel(?object $middlewareState = null): HttpKernel + { + $factory = new Psr17Factory(); + $routes = new RouteCollection([ + new Route(['GET'], '/benchmark', $this->handler($factory, 'static')), + new Route(['GET'], '/benchmark/{id}', $this->handler($factory, 'parameterized')), + new Route(['GET'], '/benchmark-middleware', $this->handler($factory, 'middleware')), + ]); + $middleware = $middlewareState === null ? [] : $this->middleware($middlewareState); + $handler = new RoutingRequestHandler(new RouteMatcher($routes), $middleware); + $services = new ServiceRegistry(); + $services->freeze(); + + return new HttpKernel($handler, new ExecutionOrchestrator($services)); + } + + private function handle(HttpKernel $kernel, string $path): array + { + $factory = new Psr17Factory(); + + try { + $outcome = $kernel->handle($factory->createServerRequest('GET', $path)); + + if ($outcome->primaryFailed()) { + $throwable = $outcome->primaryThrowableOrFail(); + + if ($throwable instanceof RouteNotFound) { + return [ + 'scenario_id' => 'http_not_found', + 'status_code' => 404, + 'body' => 'evolvephp:not-found', + 'not_found' => true, + ]; + } + + throw $throwable; + } + + $response = $outcome->primaryResult(); + } catch (RouteNotFound) { + return [ + 'scenario_id' => 'http_not_found', + 'status_code' => 404, + 'body' => 'evolvephp:not-found', + 'not_found' => true, + ]; + } + + return [ + 'status_code' => $response->getStatusCode(), + 'body' => (string) $response->getBody(), + ]; + } + + private function handler(Psr17Factory $factory, string $scenario): RequestHandlerInterface + { + return new class($factory, $scenario) implements RequestHandlerInterface { + public function __construct( + private Psr17Factory $factory, + private string $scenario, + ) {} + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $response = $this->factory->createResponse(200); + $match = $request->getAttribute(RouteMatch::class); + + if ($this->scenario === 'parameterized' && $match instanceof RouteMatch) { + $id = $match->parameters()['id'] ?? ''; + $response->getBody()->write('evolvephp:parameterized:' . $id); + + return $response; + } + + $response->getBody()->write('evolvephp:' . ($this->scenario === 'middleware' ? 'middleware' : 'static')); + + return $response; + } + }; + } + + /** + * @return list + */ + private function middleware(object $state): array + { + $middleware = []; + + for ($i = 1; $i <= 5; ++$i) { + $middleware[] = new class($state, $i) implements MiddlewareInterface { + public function __construct(private object $state, private int $index) {} + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $this->state->order[] = $this->index; + + return $handler->handle($request); + } + }; + } + + return $middleware; + } +} diff --git a/benchmarks/comparators/laravel/.placeholder b/benchmarks/comparators/laravel/.placeholder new file mode 100644 index 0000000..c21272c --- /dev/null +++ b/benchmarks/comparators/laravel/.placeholder @@ -0,0 +1,2 @@ +=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.10" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:06:52+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "8.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "2cdae51a4a02c3fe2c38d42e25a7bb952e27b768" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/2cdae51a4a02c3fe2c38d42e25a7bb952e27b768", + "reference": "2cdae51a4a02c3fe2c38d42e25a7bb952e27b768", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^3.0.2", + "guzzlehttp/psr7": "^3.1", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "4.0.1", + "guzzlehttp/test-server": "^1.0", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^9.6.34", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/8.1.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2026-08-24T11:07:02+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "42118e66a53c492effaf92bc357e931985d5c6f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/42118e66a53c492effaf92bc357e931985d5c6f9", + "reference": "42118e66a53c492effaf92bc357e931985d5c6f9", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2026-08-24T10:00:26+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a3059ba1a84c9139c4ae03cf0f45bea276c97c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a3059ba1a84c9139c4ae03cf0f45bea276c97c74", + "reference": "a3059ba1a84c9139c4ae03cf0f45bea276c97c74", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" + }, + "provide": { + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/3.1.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-08-24T11:02:13+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "7a466ad606491eb6528c717482f7cca77f1851f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/7a466ad606491eb6528c717482f7cca77f1851f3", + "reference": "7a466ad606491eb6528c717482f7cca77f1851f3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "symfony/polyfill-php80": "^1.25" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.34", + "uri-template/tests": "1.0.2" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2026-08-24T17:13:02+00:00" + }, + { + "name": "laravel/framework", + "version": "v13.29.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "6e2c363716964d8238cee7097b258119a984f0cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/6e2c363716964d8238cee7097b258119a984f0cf", + "reference": "6e2c363716964d8238cee7097b258119a984f0cf", + "shasum": "" + }, + "require": { + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.9 || ^3.0", + "guzzlehttp/uri-template": "^1.0 || ^2.0", + "laravel/prompts": "^0.3.11", + "laravel/serializable-closure": "^2.0.10", + "league/commonmark": "^2.8.1", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.10", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.1", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.4.0 || ^8.0.0", + "symfony/error-handler": "^7.4.0 || ^8.0.0", + "symfony/finder": "^7.4.0 || ^8.0.0", + "symfony/http-foundation": "^7.4.0 || ^8.0.0", + "symfony/http-kernel": "^7.4.0 || ^8.0.0", + "symfony/mailer": "^7.4.0 || ^8.0.0", + "symfony/mime": "^7.4.0 || ^8.0.0", + "symfony/polyfill-php84": "^1.36", + "symfony/polyfill-php85": "^1.36", + "symfony/polyfill-php86": "^1.36", + "symfony/process": "^7.4.5 || ^8.0.5", + "symfony/routing": "^7.4.0 || ^8.0.0", + "symfony/uid": "^7.4.0 || ^8.0.0", + "symfony/var-dumper": "^7.4.0 || ^8.0.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1 || 2.0", + "psr/log-implementation": "1.0 || 2.0 || 3.0", + "psr/simple-cache-implementation": "1.0 || 2.0 || 3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/image": "self.version", + "illuminate/json-schema": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "intervention/image": "^4.0", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^11.0.0", + "pda/pheanstalk": "^7.0.0 || ^8.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5.50 || ^12.5.8 || ^13.0.3", + "predis/predis": "^2.3 || ^3.0", + "rector/rector": "2.6.3", + "resend/resend-php": "^1.0", + "symfony/cache": "^7.4.0 || ^8.0.0", + "symfony/http-client": "^7.4.0 || ^8.0.0", + "symfony/psr-http-message-bridge": "^7.4.0 || ^8.0.0", + "symfony/translation": "^7.4.0 || ^8.0.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0 || ^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0 || ^5.0 || ^6.0).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "intervention/image": "Required to use the image processing features (^4.0).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^7.0 || ^8.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).", + "predis/predis": "Required to use the predis connector (^2.3 || ^3.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0 || ^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).", + "spatie/fork": "Required to use the 'fork' concurrency driver (^1.2).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.4 || ^8.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.4 || ^8.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.4 || ^8.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.4 || ^8.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.4 || ^8.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.4 || ^8.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "13.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", + "src/Illuminate/Support/functions.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-08-25T20:57:16+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.24", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/5d3cdef29e93ca3b62b1871359db3078cd99908b", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0|^8.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.24" + }, + "time": "2026-08-20T12:55:36+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.16", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2026-08-18T20:28:54+00:00" + }, + { + "name": "league/commonmark", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.11-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2026-08-11T16:06:25+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.35.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "5fc8404762179ae514678487b23494fd69b2309c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5fc8404762179ae514678487b23494fd69b2309c", + "reference": "5fc8404762179ae514678487b23494fd69b2309c", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.35.3" + }, + "time": "2026-08-22T12:55:54+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.35.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/a099b24dce160f3b2239043d13d47c4a1a214ea4", + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.35.3" + }, + "time": "2026-08-12T13:29:21+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.17.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2026-07-09T11:49:27+00:00" + }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/a1c54919f5fff9800cd03c32bd01defd5a4061cb", + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbonphp.github.io/carbon/", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2026-08-08T11:40:35+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.6", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "c54350438cd6914616f790a49cb424605f421562" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/c54350438cd6914616f790a49cb424605f421562", + "reference": "c54350438cd6914616f790a49cb424605f421562", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.6" + }, + "time": "2026-08-16T21:58:41+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.5" + }, + "time": "2026-07-17T23:02:45+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "require-dev": { + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "It's like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2026-02-16T23:10:27+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2026-08-24T00:54:40+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", + "shasum": "" + }, + "require": { + "brick/math": ">=0.8.16 <=0.18", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.3" + }, + "time": "2026-06-18T03:57:49+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/console", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "eb7d9957d66739649e931ce7a9d05dab69f8abac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/eb7d9957d66739649e931ce7a9d05dab69f8abac", + "reference": "eb7d9957d66739649e931ce7a9d05dab69f8abac", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-25T14:18:42+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/08e2905152a39cf3fd1745d83f8c483e258887d9", + "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-23T10:06:25+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^7.4|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/finder", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8d7acede2b2ae07605783d1c43e49b5767036474" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8d7acede2b2ae07605783d1c43e49b5767036474", + "reference": "8d7acede2b2ae07605783d1c43e49b5767036474", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T12:16:08+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "093b78326f649c3a9db922b9f17123b6aeb3b8fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/093b78326f649c3a9db922b9f17123b6aeb3b8fb", + "reference": "093b78326f649c3a9db922b9f17123b6aeb3b8fb", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<4.3" + }, + "require-dev": { + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T20:10:55+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "2f73beb7c6f1a97d2c17bbf4dbd59da8cc18b355" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2f73beb7c6f1a97d2c17bbf4dbd59da8cc18b355", + "reference": "2f73beb7c6f1a97d2c17bbf4dbd59da8cc18b355", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/flex": "<2.10", + "symfony/http-client-contracts": "<2.5", + "symfony/serializer": "<7.4.15|>=8.0,<8.0.15|>=8.1,<8.1.2", + "symfony/translation-contracts": "<2.5", + "symfony/var-dumper": "<8.1", + "symfony/web-profiler-bundle": "<8.1", + "twig/twig": "<3.21" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^8.1", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21|^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T21:40:49+00:00" + }, + { + "name": "symfony/mailer", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "89f43137da74b8f1aab37c99926482b7084f51b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/89f43137da74b8f1aab37c99926482b7084f51b9", + "reference": "89f43137da74b8f1aab37c99926482b7084f51b9", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.4.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/mime", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f", + "reference": "1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/serializer": "^7.4.17|^8.1.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-22T09:06:25+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T08:25:59+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-24T10:51:20+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-07T06:33:24+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:45:58+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-02T13:42:24+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/process", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d863f5e70d7c87abb906ac11b61f83036093000b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d863f5e70d7c87abb906ac11b61f83036093000b", + "reference": "d863f5e70d7c87abb906ac11b61f83036093000b", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/routing", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/3c188091b6b4fa2e4bc83a135caede12deb8576c", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-17T13:18:34+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-27T15:39:01+00:00" + }, + { + "name": "symfony/string", + "version": "v8.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", + "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T07:35:25+00:00" + }, + { + "name": "symfony/translation", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/d9e1caba0d6b6f9a26710af8a2f88d37f001215a", + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/http-client-contracts": "<2.5", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/uid", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "a08aef47989093f32fe50fd11859be1b427df389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/a08aef47989093f32fe50fd11859be1b427df389", + "reference": "a08aef47989093f32fe50fd11859be1b427df389", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-11T13:39:01+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3783365b58972f4779254d98372af80fbf15e170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3783365b58972f4779254d98372af80fbf15e170", + "reference": "3783365b58972f4779254d98372af80fbf15e170", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "twig/twig": "^3.12|^4.0" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T20:10:55+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/301c07936b16d88628b126b01d082ba153cf4c40", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.10", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2026-08-24T18:07:49+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2026-04-26T05:33:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.4" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/benchmarks/comparators/laravel/src/LaravelComparatorFixture.php b/benchmarks/comparators/laravel/src/LaravelComparatorFixture.php new file mode 100644 index 0000000..3d0cdda --- /dev/null +++ b/benchmarks/comparators/laravel/src/LaravelComparatorFixture.php @@ -0,0 +1,116 @@ + class_exists(Router::class), 'status' => class_exists(Router::class) ? 'available' : 'unavailable', 'reason' => class_exists(Router::class) ? null : 'Laravel dependencies are not installed']; + } + + public function applicationBoot(): array + { + $this->createRouter(); + + return ['scenario_id' => 'application_boot', 'status' => 'ok']; + } + + public function httpStatic(): array + { + return $this->dispatch($this->createRouter(), '/benchmark'); + } + + public function httpParameterized(string $id): array + { + $result = $this->dispatch($this->createRouter(), '/benchmark/' . $id); + $result['parameters'] = ['id' => $id]; + + return $result; + } + + public function httpMiddleware(): array + { + MiddlewareState::$order = []; + $result = $this->dispatch($this->createRouter(), '/benchmark-middleware'); + $result['middleware_order'] = MiddlewareState::$order; + + return $result; + } + + public function httpNotFound(): array + { + return $this->dispatch($this->createRouter(), '/benchmark-missing'); + } + + public function httpRepeatedWarm(int $requestCount): array + { + $router = $this->createRouter(); + $last = null; + + for ($i = 0; $i < $requestCount; ++$i) { + $last = $this->dispatch($router, '/benchmark'); + } + + $last ??= []; + $last['request_count'] = $requestCount; + $last['bootstrap_count'] = 1; + + return $last; + } + + private function createRouter(): Router + { + $container = new Container(); + $container->singleton(CallableDispatcherContract::class, static fn(Container $container): CallableDispatcher => new CallableDispatcher($container)); + $router = new Router(new Dispatcher($container), $container); + $router->aliasMiddleware('bench.1', Middleware1::class); + $router->aliasMiddleware('bench.2', Middleware2::class); + $router->aliasMiddleware('bench.3', Middleware3::class); + $router->aliasMiddleware('bench.4', Middleware4::class); + $router->aliasMiddleware('bench.5', Middleware5::class); + + $router->get('/benchmark', static fn(): Response => new Response('laravel:static', 200)); + $router->get('/benchmark/{id}', static fn(string $id): Response => new Response('laravel:parameterized:' . $id, 200)); + $router->get('/benchmark-middleware', static fn(): Response => new Response('laravel:middleware', 200)) + ->middleware(['bench.1', 'bench.2', 'bench.3', 'bench.4', 'bench.5']); + + return $router; + } + + private function dispatch(Router $router, string $path): array + { + try { + $response = $router->dispatch(Request::create($path, 'GET')); + } catch (NotFoundHttpException) { + return [ + 'scenario_id' => 'http_not_found', + 'status_code' => 404, + 'body' => 'laravel:not-found', + 'not_found' => true, + ]; + } + + return [ + 'status_code' => $response->getStatusCode(), + 'body' => (string) $response->getContent(), + ]; + } +} diff --git a/benchmarks/comparators/laravel/src/Middleware.php b/benchmarks/comparators/laravel/src/Middleware.php new file mode 100644 index 0000000..c15ef7b --- /dev/null +++ b/benchmarks/comparators/laravel/src/Middleware.php @@ -0,0 +1,66 @@ + */ + public static array $order = []; +} + +abstract class OrderedMiddleware +{ + public function handle(Request $request, Closure $next): mixed + { + MiddlewareState::$order[] = $this->index(); + + return $next($request); + } + + abstract protected function index(): int; +} + +final class Middleware1 extends OrderedMiddleware +{ + protected function index(): int + { + return 1; + } +} + +final class Middleware2 extends OrderedMiddleware +{ + protected function index(): int + { + return 2; + } +} + +final class Middleware3 extends OrderedMiddleware +{ + protected function index(): int + { + return 3; + } +} + +final class Middleware4 extends OrderedMiddleware +{ + protected function index(): int + { + return 4; + } +} + +final class Middleware5 extends OrderedMiddleware +{ + protected function index(): int + { + return 5; + } +} diff --git a/benchmarks/comparators/matrix.json b/benchmarks/comparators/matrix.json new file mode 100644 index 0000000..8dc4c9e --- /dev/null +++ b/benchmarks/comparators/matrix.json @@ -0,0 +1,199 @@ +{ + "schema_version": "evolvephp.comparator.matrix.v1", + "common_scenarios": { + "application_boot": { + "id": "application_boot", + "description": "Application bootstrap and component initialization", + "category": "lifecycle" + }, + "http_static": { + "id": "http_static", + "description": "HTTP GET request to /benchmark through the framework route path", + "category": "http", + "method": "GET", + "route_type": "static" + }, + "http_parameterized": { + "id": "http_parameterized", + "description": "HTTP GET request to /benchmark/123 with framework route-parameter capture", + "category": "http", + "method": "GET", + "route_type": "parameterized" + }, + "http_middleware": { + "id": "http_middleware", + "description": "HTTP GET request through five ordered framework middleware/listener layers", + "category": "http", + "method": "GET", + "route_type": "static", + "middleware_depth": 5 + }, + "http_not_found": { + "id": "http_not_found", + "description": "HTTP GET request to a genuinely unmatched path", + "category": "http", + "method": "GET", + "route_type": "not_found" + }, + "http_repeated_warm": { + "id": "http_repeated_warm", + "description": "Repeated HTTP requests against a warm pre-booted application/kernel/container", + "category": "http", + "method": "GET", + "route_type": "static", + "warm": true + } + }, + "comparators": [ + { + "id": "evolvephp", + "name": "EvolvePHP", + "framework_package": "evolvephp/http", + "framework_version": "2.0.0-dev+9a0e741", + "composer_constraint": "^2.0@dev", + "fixture_version": 1, + "fixture_path": "evolvephp", + "fixture_bootstrap": "evolvephp/bootstrap.php", + "lock_path": "evolvephp/composer.lock", + "lock_sha256": "f792575ec5491c8d3aa171ba5f7de3b38558bfbd82b977beea45e603fd79e491", + "implementation_model": "pure-php", + "availability": "always", + "configuration": { + "model": "Evolve HTTP kernel, route matcher, routing handler and middleware dispatcher", + "debug": false, + "io": "none" + }, + "scenarios": [ + "application_boot", + "http_static", + "http_parameterized", + "http_middleware", + "http_not_found", + "http_repeated_warm" + ], + "notes": "EvolvePHP-only lifecycle evidence remains outside this cross-framework scenario matrix." + }, + { + "id": "laravel", + "name": "Laravel", + "framework_package": "laravel/framework", + "framework_version": "13.29.0", + "composer_constraint": "13.29.0", + "fixture_version": 1, + "fixture_path": "laravel", + "fixture_bootstrap": "laravel/bootstrap.php", + "lock_path": "laravel/composer.lock", + "lock_sha256": "33b4d04706fa39dffc1d71a7d2d03f09651555afead629e31f9229adcdc86354", + "implementation_model": "pure-php", + "availability": "always", + "configuration": { + "model": "Illuminate routing dispatcher with framework router and middleware aliases", + "debug": false, + "io": "none", + "composer_platform_override": "--ignore-platform-req=ext-fileinfo used only while creating this lockfile on the local PHP CLI" + }, + "scenarios": [ + "application_boot", + "http_static", + "http_parameterized", + "http_middleware", + "http_not_found", + "http_repeated_warm" + ], + "notes": "Laravel 13.29.0 is exactly pinned as the accepted current-stable comparator version." + }, + { + "id": "symfony", + "name": "Symfony", + "framework_package": "symfony/http-kernel", + "framework_version": "8.1.5", + "composer_constraint": "8.1.5", + "fixture_version": 1, + "fixture_path": "symfony", + "fixture_bootstrap": "symfony/bootstrap.php", + "lock_path": "symfony/composer.lock", + "lock_sha256": "d93fdac19b2cdd5379e5700a8146bb705c9b516c9ec9a0709dcd785be9b1e1d6", + "implementation_model": "pure-php", + "availability": "always", + "configuration": { + "model": "Symfony HttpKernel, RouterListener, RouteCollection and EventDispatcher", + "components": { + "symfony/event-dispatcher": "8.1.5", + "symfony/http-foundation": "8.1.5", + "symfony/http-kernel": "8.1.5", + "symfony/routing": "8.1.5" + }, + "debug": false, + "io": "none" + }, + "scenarios": [ + "application_boot", + "http_static", + "http_parameterized", + "http_middleware", + "http_not_found", + "http_repeated_warm" + ], + "notes": "The Symfony comparator models the accepted Symfony 8.1 framework line through HttpKernel, RouterListener, RouteCollection and EventDispatcher." + }, + { + "id": "slim", + "name": "Slim", + "framework_package": "slim/slim", + "framework_version": "4.15.2", + "composer_constraint": "4.15.2", + "fixture_version": 1, + "fixture_path": "slim", + "fixture_bootstrap": "slim/bootstrap.php", + "lock_path": "slim/composer.lock", + "lock_sha256": "87370678970fe51c62c6a4cd4e4ca7b3600b22c84a2a5b920e2b8527a2e089a7", + "implementation_model": "pure-php", + "availability": "always", + "configuration": { + "model": "Slim AppFactory application with route and error middleware", + "psr7": "nyholm/psr7 1.8.2", + "debug": false, + "io": "none" + }, + "scenarios": [ + "application_boot", + "http_static", + "http_parameterized", + "http_middleware", + "http_not_found", + "http_repeated_warm" + ], + "notes": "Slim PSR-7 dependencies are local to this comparator root." + }, + { + "id": "phalcon", + "name": "Phalcon", + "framework_package": "ext-phalcon", + "framework_version": "5.20.3", + "expected_extension_version": "5.20.3", + "composer_constraint": "suggest ext-phalcon 5.20.3", + "fixture_version": 1, + "fixture_path": "phalcon", + "fixture_bootstrap": "phalcon/bootstrap.php", + "lock_path": "phalcon/composer.lock", + "lock_sha256": "1e6b5f4b3d70a3e0d5a74eaa55dec95bde1e1d2b33e1c64dc4a737ad8cd01562", + "implementation_model": "compiled-extension", + "availability": "conditional", + "availability_condition": "phalcon extension loaded", + "configuration": { + "model": "Phalcon Mvc Micro when extension is loaded; deterministic unavailable report otherwise", + "debug": false, + "io": "none" + }, + "scenarios": [ + "application_boot", + "http_static", + "http_parameterized", + "http_middleware", + "http_not_found", + "http_repeated_warm" + ], + "notes": "No fake timing data is emitted when the Phalcon extension is unavailable." + } + ] +} diff --git a/benchmarks/comparators/phalcon/.placeholder b/benchmarks/comparators/phalcon/.placeholder new file mode 100644 index 0000000..3257313 --- /dev/null +++ b/benchmarks/comparators/phalcon/.placeholder @@ -0,0 +1,2 @@ +createApplication(); + + return ['scenario_id' => 'application_boot', 'status' => 'ok']; + } + + public function httpStatic(): array + { + return $this->handle($this->createApplication(), '/benchmark'); + } + + public function httpParameterized(string $id): array + { + $result = $this->handle($this->createApplication(), '/benchmark/' . $id); + $result['parameters'] = ['id' => $id]; + + return $result; + } + + public function httpMiddleware(): array + { + $state = (object) ['order' => []]; + $result = $this->handle($this->createApplication($state), '/benchmark-middleware'); + $result['middleware_order'] = $state->order; + + return $result; + } + + public function httpNotFound(): array + { + return $this->handle($this->createApplication(), '/benchmark-missing'); + } + + public function httpRepeatedWarm(int $requestCount): array + { + $application = $this->createApplication(); + $last = null; + + for ($i = 0; $i < $requestCount; ++$i) { + $last = $this->handle($application, '/benchmark'); + } + + $last ??= []; + $last['request_count'] = $requestCount; + $last['bootstrap_count'] = 1; + + return $last; + } + + private function createApplication(?object $middlewareState = null): object + { + $applicationClass = 'Phalcon\\Mvc\\Micro'; + $responseClass = 'Phalcon\\Http\\Response'; + $notFoundHandler = static function () use ($responseClass): object { + $response = new $responseClass(); + $response->setStatusCode(404); + $response->setContent('phalcon:not-found'); + + return $response; + }; + + $app = new $applicationClass(); + $app->notFound($notFoundHandler); + $app->get('/benchmark', static function () use ($responseClass): object { + $response = new $responseClass(); + $response->setStatusCode(200); + $response->setContent('phalcon:static'); + + return $response; + }); + $app->get('/benchmark/{id}', static function (string $id) use ($responseClass): object { + $response = new $responseClass(); + $response->setStatusCode(200); + $response->setContent('phalcon:parameterized:' . $id); + + return $response; + }); + $app->get('/benchmark-middleware', static function () use ($responseClass, $middlewareState): object { + if ($middlewareState !== null) { + for ($i = 1; $i <= 5; ++$i) { + $middlewareState->order[] = $i; + } + } + + $response = new $responseClass(); + $response->setStatusCode(200); + $response->setContent('phalcon:middleware'); + + return $response; + }); + + return $app; + } + + private function handle(object $application, string $path): array + { + ob_start(); + $response = $application->handle($path); + $output = (string) ob_get_clean(); + + $statusCode = method_exists($response, 'getStatusCode') ? (int) $response->getStatusCode() : 200; + $body = method_exists($response, 'getContent') ? (string) $response->getContent() : $output; + + return [ + 'status_code' => $statusCode, + 'body' => $statusCode === 404 ? 'phalcon:not-found' : $body, + 'not_found' => $statusCode === 404, + ]; + } +} diff --git a/benchmarks/comparators/slim/.placeholder b/benchmarks/comparators/slim/.placeholder new file mode 100644 index 0000000..a5e4c20 --- /dev/null +++ b/benchmarks/comparators/slim/.placeholder @@ -0,0 +1,2 @@ +=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/1.3.1" + }, + "time": "2026-07-09T19:38:47+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "slim/slim", + "version": "4.15.2", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/e12cb05ca2a14e8f459d019e87a31dc915b80470", + "reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.4 || ^2", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.6", + "httpsoft/http-message": "^1.1", + "httpsoft/http-server-request": "^1.1", + "laminas/laminas-diactoros": "^2.17 || ^3", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1 || ^2", + "phpunit/phpunit": "^9.6 || ^10 || ^11 || ^12", + "slim/http": "^1.3", + "slim/psr7": "^1.6", + "squizlabs/php_codesniffer": "^3.10", + "vimeo/psalm": "^5 || ^6" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "https://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "https://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "https://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "time": "2026-05-22T08:00:12+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.4" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/benchmarks/comparators/slim/src/SlimComparatorFixture.php b/benchmarks/comparators/slim/src/SlimComparatorFixture.php new file mode 100644 index 0000000..e03ebd8 --- /dev/null +++ b/benchmarks/comparators/slim/src/SlimComparatorFixture.php @@ -0,0 +1,126 @@ + class_exists(AppFactory::class), 'status' => class_exists(AppFactory::class) ? 'available' : 'unavailable', 'reason' => class_exists(AppFactory::class) ? null : 'Slim dependencies are not installed']; + } + + public function applicationBoot(): array + { + $this->createApp(); + + return ['scenario_id' => 'application_boot', 'status' => 'ok']; + } + + public function httpStatic(): array + { + return $this->handle($this->createApp(), '/benchmark'); + } + + public function httpParameterized(string $id): array + { + $result = $this->handle($this->createApp(), '/benchmark/' . $id); + $result['parameters'] = ['id' => $id]; + + return $result; + } + + public function httpMiddleware(): array + { + $state = (object) ['order' => []]; + $result = $this->handle($this->createApp($state), '/benchmark-middleware'); + $result['middleware_order'] = $state->order; + + return $result; + } + + public function httpNotFound(): array + { + return $this->handle($this->createApp(), '/benchmark-missing'); + } + + public function httpRepeatedWarm(int $requestCount): array + { + $app = $this->createApp(); + $last = null; + + for ($i = 0; $i < $requestCount; ++$i) { + $last = $this->handle($app, '/benchmark'); + } + + $last ??= []; + $last['request_count'] = $requestCount; + $last['bootstrap_count'] = 1; + + return $last; + } + + private function createApp(?object $middlewareState = null): App + { + $factory = new Psr17Factory(); + AppFactory::setResponseFactory($factory); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); + $app->addErrorMiddleware(false, false, false); + + $app->get('/benchmark', static function (ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { + $response->getBody()->write('slim:static'); + + return $response; + }); + $app->get('/benchmark/{id}', static function (ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface { + $response->getBody()->write('slim:parameterized:' . $args['id']); + + return $response; + }); + $middlewareRoute = $app->get('/benchmark-middleware', static function (ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { + $response->getBody()->write('slim:middleware'); + + return $response; + }); + + if ($middlewareState !== null) { + for ($i = 5; $i >= 1; --$i) { + $index = $i; + $middlewareRoute->add(static function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($middlewareState, $index): ResponseInterface { + $middlewareState->order[] = $index; + + return $handler->handle($request); + }); + } + } + + return $app; + } + + private function handle(App $app, string $path): array + { + $factory = new Psr17Factory(); + $response = $app->handle($factory->createServerRequest('GET', $path)); + + return [ + 'status_code' => $response->getStatusCode(), + 'body' => $response->getStatusCode() === 404 ? 'slim:not-found' : (string) $response->getBody(), + 'not_found' => $response->getStatusCode() === 404, + ]; + } +} diff --git a/benchmarks/comparators/symfony/.placeholder b/benchmarks/comparators/symfony/.placeholder new file mode 100644 index 0000000..54d1c1b --- /dev/null +++ b/benchmarks/comparators/symfony/.placeholder @@ -0,0 +1,2 @@ +=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^7.4|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "ee16f97e95cfa011a742714d7c8c8f70fe7423f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee16f97e95cfa011a742714d7c8c8f70fe7423f4", + "reference": "ee16f97e95cfa011a742714d7c8c8f70fe7423f4", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<4.3" + }, + "require-dev": { + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-20T09:59:12+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "0306e1e65b90023fe40de6c6be95d06396bcb2e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/0306e1e65b90023fe40de6c6be95d06396bcb2e6", + "reference": "0306e1e65b90023fe40de6c6be95d06396bcb2e6", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/flex": "<2.10", + "symfony/http-client-contracts": "<2.5", + "symfony/serializer": "<7.4.15|>=8.0,<8.0.15|>=8.1,<8.1.2", + "symfony/translation-contracts": "<2.5", + "symfony/var-dumper": "<8.1", + "symfony/web-profiler-bundle": "<8.1", + "twig/twig": "<3.21" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^8.1", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21|^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-22T13:45:00+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/routing", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/3c188091b6b4fa2e4bc83a135caede12deb8576c", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-17T13:18:34+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3783365b58972f4779254d98372af80fbf15e170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3783365b58972f4779254d98372af80fbf15e170", + "reference": "3783365b58972f4779254d98372af80fbf15e170", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "twig/twig": "^3.12|^4.0" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T20:10:55+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.4" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/benchmarks/comparators/symfony/src/SymfonyComparatorFixture.php b/benchmarks/comparators/symfony/src/SymfonyComparatorFixture.php new file mode 100644 index 0000000..6d70dab --- /dev/null +++ b/benchmarks/comparators/symfony/src/SymfonyComparatorFixture.php @@ -0,0 +1,145 @@ + class_exists(HttpKernel::class), 'status' => class_exists(HttpKernel::class) ? 'available' : 'unavailable', 'reason' => class_exists(HttpKernel::class) ? null : 'Symfony dependencies are not installed']; + } + + public function applicationBoot(): array + { + $this->createKernel(); + + return ['scenario_id' => 'application_boot', 'status' => 'ok']; + } + + public function httpStatic(): array + { + return $this->handle($this->createKernel(), '/benchmark'); + } + + public function httpParameterized(string $id): array + { + $result = $this->handle($this->createKernel(), '/benchmark/' . $id); + $result['parameters'] = ['id' => $id]; + + return $result; + } + + public function httpMiddleware(): array + { + $state = (object) ['order' => []]; + $result = $this->handle($this->createKernel($state), '/benchmark-middleware'); + $result['middleware_order'] = $state->order; + + return $result; + } + + public function httpNotFound(): array + { + return $this->handle($this->createKernel(), '/benchmark-missing'); + } + + public function httpRepeatedWarm(int $requestCount): array + { + $kernel = $this->createKernel(); + $last = null; + + for ($i = 0; $i < $requestCount; ++$i) { + $last = $this->handle($kernel, '/benchmark'); + } + + $last ??= []; + $last['request_count'] = $requestCount; + $last['bootstrap_count'] = 1; + + return $last; + } + + private function createKernel(?object $middlewareState = null): HttpKernel + { + $context = new RequestContext(); + $matcher = new UrlMatcher($this->routes(), $context); + $dispatcher = new EventDispatcher(); + + if ($middlewareState !== null) { + for ($i = 1; $i <= 5; ++$i) { + $dispatcher->addListener( + KernelEvents::REQUEST, + static function (RequestEvent $event) use ($middlewareState, $i): void { + $middlewareState->order[] = $i; + }, + 50 - $i, + ); + } + } + + $dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack(), $context, null, null, false)); + + return new HttpKernel($dispatcher, new ControllerResolver(), null, new ArgumentResolver()); + } + + private function routes(): RouteCollection + { + $routes = new RouteCollection(); + $routes->add('benchmark.static', new Route('/benchmark', [ + '_controller' => static fn(): Response => new Response('symfony:static', 200), + ])); + $routes->add('benchmark.parameterized', new Route('/benchmark/{id}', [ + '_controller' => static fn(Request $request): Response => new Response('symfony:parameterized:' . $request->attributes->get('id'), 200), + ])); + $routes->add('benchmark.middleware', new Route('/benchmark-middleware', [ + '_controller' => static fn(): Response => new Response('symfony:middleware', 200), + ])); + + return $routes; + } + + private function handle(HttpKernel $kernel, string $path): array + { + try { + $response = $kernel->handle(Request::create($path, 'GET'), HttpKernelInterface::MAIN_REQUEST, true); + } catch (NotFoundHttpException|ResourceNotFoundException) { + return [ + 'scenario_id' => 'http_not_found', + 'status_code' => 404, + 'body' => 'symfony:not-found', + 'not_found' => true, + ]; + } + + return [ + 'status_code' => $response->getStatusCode(), + 'body' => $response->getContent(), + ]; + } +} diff --git a/benchmarks/src/Comparator/ComparatorFixture.php b/benchmarks/src/Comparator/ComparatorFixture.php new file mode 100644 index 0000000..a2ece84 --- /dev/null +++ b/benchmarks/src/Comparator/ComparatorFixture.php @@ -0,0 +1,45 @@ + + */ + public function availability(): array; + + /** + * @return array + */ + public function applicationBoot(): array; + + /** + * @return array + */ + public function httpStatic(): array; + + /** + * @return array + */ + public function httpParameterized(string $id): array; + + /** + * @return array + */ + public function httpMiddleware(): array; + + /** + * @return array + */ + public function httpNotFound(): array; + + /** + * @return array + */ + public function httpRepeatedWarm(int $requestCount): array; +} diff --git a/benchmarks/src/Comparator/ComparatorMatrix.php b/benchmarks/src/Comparator/ComparatorMatrix.php new file mode 100644 index 0000000..54269cf --- /dev/null +++ b/benchmarks/src/Comparator/ComparatorMatrix.php @@ -0,0 +1,206 @@ + $data + */ + private function __construct( + private readonly array $data, + private readonly string $baseDir = '', + ) {} + + public static function fromJsonFile(string $path): self + { + if (!is_file($path)) { + throw new ComparatorMatrixException("Matrix file not found: {$path}"); + } + + try { + $data = json_decode(file_get_contents($path), associative: true, flags: JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw new ComparatorMatrixException("Invalid matrix JSON: {$e->getMessage()}", previous: $e); + } + + if (!is_array($data)) { + throw new ComparatorMatrixException('Matrix data must be an array'); + } + + return self::fromArray($data, dirname($path)); + } + + /** + * @param array $data + */ + public static function fromArray(array $data, string $baseDir = ''): self + { + // Validate schema version + if (($data['schema_version'] ?? null) !== self::SCHEMA_VERSION) { + throw new ComparatorMatrixException( + 'Invalid schema version. Expected: ' . self::SCHEMA_VERSION . ', Got: ' . ($data['schema_version'] ?? 'null') + ); + } + + $commonScenarios = $data['common_scenarios'] ?? []; + if (!is_array($commonScenarios) || $commonScenarios === []) { + throw new ComparatorMatrixException('Common scenarios must be a non-empty array'); + } + + foreach ($commonScenarios as $scenarioId => $scenario) { + if (!is_array($scenario)) { + throw new ComparatorMatrixException("Common scenario '{$scenarioId}' must be an array"); + } + + if (($scenario['id'] ?? null) !== $scenarioId) { + throw new ComparatorMatrixException("Common scenario '{$scenarioId}' must repeat its stable id"); + } + } + + // Validate comparators array + $comparators = $data['comparators'] ?? []; + if (!is_array($comparators)) { + throw new ComparatorMatrixException('Comparators must be an array'); + } + + if (count($comparators) === 0) { + throw new ComparatorMatrixException('At least one comparator must be defined'); + } + + // Validate individual comparators and check for duplicates + $seenIds = []; + foreach ($comparators as $comparator) { + if (!is_array($comparator)) { + throw new ComparatorMatrixException('Each comparator must be an array'); + } + + // Check required fields + $requiredFields = [ + 'id', + 'name', + 'framework_package', + 'framework_version', + 'composer_constraint', + 'fixture_path', + 'fixture_bootstrap', + 'lock_path', + 'implementation_model', + 'availability', + 'scenarios', + ]; + foreach ($requiredFields as $field) { + if (!isset($comparator[$field])) { + throw new ComparatorMatrixException("Comparator missing required field: {$field}"); + } + } + + $id = (string) $comparator['id']; + + // Check for duplicates + if (isset($seenIds[$id])) { + throw new ComparatorMatrixException("Duplicate comparator ID: {$id}"); + } + $seenIds[$id] = true; + + $implementationModel = (string) $comparator['implementation_model']; + if (!in_array($implementationModel, self::IMPLEMENTATION_MODELS, true)) { + throw new ComparatorMatrixException("Unsupported implementation model for comparator '{$id}': {$implementationModel}"); + } + + $availability = (string) $comparator['availability']; + if (!in_array($availability, self::AVAILABILITY_STATES, true)) { + throw new ComparatorMatrixException("Unsupported availability state for comparator '{$id}': {$availability}"); + } + + if ($availability === 'conditional' && !isset($comparator['availability_condition'])) { + throw new ComparatorMatrixException("Conditional comparator '{$id}' must define availability_condition"); + } + + // Validate fixture directory exists if baseDir is provided + if ($baseDir !== '') { + $fixturePath = $baseDir . DIRECTORY_SEPARATOR . trim((string) $comparator['fixture_path'], '/\\'); + if (!is_dir($fixturePath)) { + throw new ComparatorMatrixException( + "Fixture directory not found for comparator '{$id}': {$fixturePath}" + ); + } + + $bootstrapPath = $baseDir . DIRECTORY_SEPARATOR . trim((string) $comparator['fixture_bootstrap'], '/\\'); + if (!is_file($bootstrapPath)) { + throw new ComparatorMatrixException( + "Fixture bootstrap not found for comparator '{$id}': {$bootstrapPath}" + ); + } + + $lockPath = $baseDir . DIRECTORY_SEPARATOR . trim((string) $comparator['lock_path'], '/\\'); + if (!is_file($lockPath)) { + throw new ComparatorMatrixException( + "Lockfile not found for comparator '{$id}': {$lockPath}" + ); + } + } + + // Validate scenarios + $scenarios = $comparator['scenarios'] ?? []; + if (!is_array($scenarios)) { + throw new ComparatorMatrixException("Scenarios for comparator '{$id}' must be an array"); + } + + foreach ($scenarios as $scenario) { + if (!is_string($scenario) || !isset($commonScenarios[$scenario])) { + throw new ComparatorMatrixException("Unknown scenario '{$scenario}' referenced by comparator '{$id}'"); + } + } + } + + return new self($data, $baseDir); + } + + /** + * @return array> + */ + public function comparators(): array + { + $result = []; + $comparators = $this->data['comparators'] ?? []; + + // Ensure stable ordering + usort($comparators, static fn(array $a, array $b): int => strcmp( + (string) $a['id'], + (string) $b['id'] + )); + + foreach ($comparators as $comparator) { + $result[(string) $comparator['id']] = $comparator; + } + + return $result; + } + + /** + * @return array> + */ + public function commonScenarios(): array + { + return $this->data['common_scenarios'] ?? []; + } + + /** + * @return array|null + */ + public function comparator(string $id): ?array + { + return $this->comparators()[$id] ?? null; + } +} diff --git a/benchmarks/src/Comparator/ComparatorMatrixException.php b/benchmarks/src/Comparator/ComparatorMatrixException.php new file mode 100644 index 0000000..2cbffbe --- /dev/null +++ b/benchmarks/src/Comparator/ComparatorMatrixException.php @@ -0,0 +1,11 @@ + $normalizedResult (from ResultNormalizer::normalize) + * @param array $comparatorMetadata + * @return array + */ + public static function withComparatorMetadata(array $normalizedResult, array $comparatorMetadata): array + { + return [ + 'comparator_schema_version' => self::SCHEMA_VERSION, + 'comparator_id' => $comparatorMetadata['id'] ?? null, + 'framework_name' => $comparatorMetadata['name'] ?? null, + 'framework_version' => $comparatorMetadata['framework_version'] ?? $comparatorMetadata['version'] ?? null, + 'fixture_identity' => $comparatorMetadata['fixture_identity'] ?? null, + 'fixture_identity_hash' => $comparatorMetadata['fixture_identity_hash'] ?? ($comparatorMetadata['fixture_identity']['hash'] ?? null), + 'execution_environment_identity' => $comparatorMetadata['execution_environment_identity'] ?? null, + 'execution_environment_fingerprint' => $comparatorMetadata['execution_environment_fingerprint'] ?? ($comparatorMetadata['execution_environment_identity']['hash'] ?? null), + 'source_evolvephp_sha' => $comparatorMetadata['source_evolvephp_sha'] ?? ($normalizedResult['source_sha'] ?? null), + 'scenario_id' => $comparatorMetadata['scenario_id'] ?? null, + 'availability' => $comparatorMetadata['availability'] ?? 'available', + 'availability_status' => $comparatorMetadata['availability_status'] ?? null, + 'implementation_model' => $comparatorMetadata['implementation_model'] ?? 'pure-php', + 'notes' => $comparatorMetadata['notes'] ?? [], + 'baseline_result' => $normalizedResult, + ]; + } + + /** + * Create result for an unavailable comparator. + * + * @param array $comparatorMetadata + * @return array + */ + public static function unavailable(array $comparatorMetadata): array + { + return [ + 'comparator_schema_version' => self::SCHEMA_VERSION, + 'comparator_id' => $comparatorMetadata['id'] ?? null, + 'framework_name' => $comparatorMetadata['name'] ?? null, + 'framework_version' => $comparatorMetadata['framework_version'] ?? $comparatorMetadata['version'] ?? null, + 'availability' => 'unavailable', + 'availability_status' => $comparatorMetadata['availability_status'] ?? 'unknown', + 'availability_reason' => $comparatorMetadata['availability_reason'] ?? null, + 'notes' => $comparatorMetadata['notes'] ?? [], + 'baseline_result' => null, + ]; + } +} diff --git a/benchmarks/src/Comparator/ComparatorSmokeVerifier.php b/benchmarks/src/Comparator/ComparatorSmokeVerifier.php new file mode 100644 index 0000000..9313f44 --- /dev/null +++ b/benchmarks/src/Comparator/ComparatorSmokeVerifier.php @@ -0,0 +1,139 @@ + + */ + public static function verifyMatrixFile(string $matrixPath): array + { + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + $baseDir = dirname($matrixPath); + $comparators = []; + $overallStatus = 'passed'; + + foreach ($matrix->comparators() as $id => $definition) { + $result = self::verifyComparator($baseDir, $definition); + $comparators[] = $result; + + if (($result['status'] ?? 'failed') === 'failed') { + $overallStatus = 'failed'; + } + } + + return [ + 'schema_version' => self::SCHEMA_VERSION, + 'status' => $overallStatus, + 'comparator_count' => count($comparators), + 'comparators' => $comparators, + ]; + } + + /** + * @param array $definition + * @return array + */ + private static function verifyComparator(string $baseDir, array $definition): array + { + $id = (string) $definition['id']; + + try { + $fixture = self::loadFixture($baseDir, $definition); + $availability = $fixture->availability(); + + if (($availability['available'] ?? false) !== true) { + return [ + 'id' => $id, + 'framework_version' => $definition['framework_version'], + 'composer_constraint' => $definition['composer_constraint'], + 'availability' => 'unavailable', + 'status' => 'skipped', + 'reason' => (string) ($availability['reason'] ?? 'comparator unavailable'), + ]; + } + + $scenarios = [ + 'application_boot' => $fixture->applicationBoot(), + 'http_static' => $fixture->httpStatic(), + 'http_parameterized' => $fixture->httpParameterized('123'), + 'http_middleware' => $fixture->httpMiddleware(), + 'http_not_found' => $fixture->httpNotFound(), + 'http_repeated_warm' => $fixture->httpRepeatedWarm(3), + ]; + + self::assertScenarioResults($id, $scenarios); + + return [ + 'id' => $id, + 'framework_version' => $definition['framework_version'], + 'composer_constraint' => $definition['composer_constraint'], + 'availability' => 'available', + 'status' => 'passed', + 'scenarios' => $scenarios, + ]; + } catch (Throwable $exception) { + return [ + 'id' => $id, + 'framework_version' => $definition['framework_version'] ?? null, + 'composer_constraint' => $definition['composer_constraint'] ?? null, + 'availability' => 'available', + 'status' => 'failed', + 'reason' => $exception->getMessage(), + ]; + } + } + + /** + * @param array $definition + */ + private static function loadFixture(string $baseDir, array $definition): ComparatorFixture + { + $path = $baseDir . DIRECTORY_SEPARATOR . trim((string) $definition['fixture_bootstrap'], '/\\'); + $fixture = require $path; + + if (!$fixture instanceof ComparatorFixture) { + throw new ComparatorMatrixException("Comparator '{$definition['id']}' bootstrap must return a ComparatorFixture"); + } + + return $fixture; + } + + /** + * @param array> $scenarios + */ + private static function assertScenarioResults(string $id, array $scenarios): void + { + self::assertSame('ok', $scenarios['application_boot']['status'] ?? null, "{$id} application_boot failed"); + self::assertSame(200, $scenarios['http_static']['status_code'] ?? null, "{$id} static route failed"); + self::assertSame("{$id}:static", $scenarios['http_static']['body'] ?? null, "{$id} static body mismatch"); + + self::assertSame(200, $scenarios['http_parameterized']['status_code'] ?? null, "{$id} parameterized route failed"); + self::assertSame(['id' => '123'], $scenarios['http_parameterized']['parameters'] ?? null, "{$id} parameter capture failed"); + self::assertSame("{$id}:parameterized:123", $scenarios['http_parameterized']['body'] ?? null, "{$id} parameterized body mismatch"); + + self::assertSame(200, $scenarios['http_middleware']['status_code'] ?? null, "{$id} middleware route failed"); + self::assertSame([1, 2, 3, 4, 5], $scenarios['http_middleware']['middleware_order'] ?? null, "{$id} middleware order mismatch"); + + self::assertSame(404, $scenarios['http_not_found']['status_code'] ?? null, "{$id} not-found path failed"); + self::assertSame(true, $scenarios['http_not_found']['not_found'] ?? null, "{$id} not-found marker missing"); + + self::assertSame(200, $scenarios['http_repeated_warm']['status_code'] ?? null, "{$id} repeated warm route failed"); + self::assertSame(3, $scenarios['http_repeated_warm']['request_count'] ?? null, "{$id} repeated warm request count mismatch"); + self::assertSame(1, $scenarios['http_repeated_warm']['bootstrap_count'] ?? null, "{$id} repeated warm rebuilt the fixture"); + } + + private static function assertSame(mixed $expected, mixed $actual, string $message): void + { + if ($expected !== $actual) { + throw new ComparatorMatrixException($message); + } + } +} diff --git a/benchmarks/src/Comparator/PhalconAvailability.php b/benchmarks/src/Comparator/PhalconAvailability.php new file mode 100644 index 0000000..4896f3c --- /dev/null +++ b/benchmarks/src/Comparator/PhalconAvailability.php @@ -0,0 +1,56 @@ + false, + 'extension_present' => false, + 'status' => 'unavailable', + 'reason' => 'phalcon extension not loaded', + ]; + } + + $version = phpversion('phalcon'); + + return [ + 'available' => true, + 'extension_present' => true, + 'extension_version' => $version ?: 'unknown', + 'status' => 'available', + ]; + } + + /** + * Create an unavailable marker with reason. + * + * @return array{available: false, extension_present: false, status: 'unavailable', reason: string} + */ + public static function unavailable(string $reason): array + { + return [ + 'available' => false, + 'extension_present' => false, + 'status' => 'unavailable', + 'reason' => $reason, + ]; + } +} diff --git a/benchmarks/src/Support/ExecutionEnvironmentFingerprint.php b/benchmarks/src/Support/ExecutionEnvironmentFingerprint.php new file mode 100644 index 0000000..50a8572 --- /dev/null +++ b/benchmarks/src/Support/ExecutionEnvironmentFingerprint.php @@ -0,0 +1,62 @@ + $environment + * @return array{hash: string, fields: array} + */ + public static function fromEnvironment(array $environment): array + { + // Extract only execution-environment relevant fields + $fields = [ + 'schema_version' => self::SCHEMA_VERSION, + 'runtime' => $environment['runtime'] ?? [], + 'platform' => $environment['platform'] ?? [], + 'composer_version' => $environment['composer']['version'] ?? null, + 'phpbench_version' => $environment['phpbench']['version'] ?? null, + 'opcache' => $environment['opcache'] ?? [], + 'jit' => $environment['jit'] ?? [], + 'extensions' => $environment['extensions'] ?? [], + // NOTE: lock_hash is intentionally excluded + ]; + + self::sortRecursive($fields); + + return [ + 'hash' => hash('sha256', json_encode($fields, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)), + 'fields' => $fields, + ]; + } + + /** + * @param array $value + */ + private static function sortRecursive(array &$value): void + { + foreach ($value as &$entry) { + if (is_array($entry)) { + self::sortRecursive($entry); + } + } + + if (!array_is_list($value)) { + ksort($value); + } + } +} diff --git a/benchmarks/src/Support/FixtureIdentity.php b/benchmarks/src/Support/FixtureIdentity.php new file mode 100644 index 0000000..49aa4b2 --- /dev/null +++ b/benchmarks/src/Support/FixtureIdentity.php @@ -0,0 +1,53 @@ + $data + * @return array{hash: string, fields: array} + */ + public static function fromArray(array $data): array + { + $fields = [ + 'comparator_id' => $data['comparator_id'] ?? null, + 'framework_name' => $data['framework_name'] ?? null, + 'framework_version' => $data['framework_version'] ?? null, + 'fixture_version' => $data['fixture_version'] ?? null, + 'lock_hash' => $data['lock_hash'] ?? null, + 'configuration' => $data['configuration'] ?? [], + ]; + + self::sortRecursive($fields); + + return [ + 'hash' => hash('sha256', json_encode($fields, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)), + 'fields' => $fields, + ]; + } + + /** + * @param array $value + */ + private static function sortRecursive(array &$value): void + { + foreach ($value as &$entry) { + if (is_array($entry)) { + self::sortRecursive($entry); + } + } + + if (!array_is_list($value)) { + ksort($value); + } + } +} diff --git a/benchmarks/tests/Comparator/BenchmarkSyntaxToolingTest.php b/benchmarks/tests/Comparator/BenchmarkSyntaxToolingTest.php new file mode 100644 index 0000000..ffaf4e4 --- /dev/null +++ b/benchmarks/tests/Comparator/BenchmarkSyntaxToolingTest.php @@ -0,0 +1,19 @@ +fixtureDir = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'comparators'; + } + + public function testValidComparatorMatrixLoadsSuccessfully(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $this->assertFileExists($matrixPath, 'matrix.json must exist at benchmarks/comparators/matrix.json'); + + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + $comparators = $matrix->comparators(); + + $this->assertCount(5, $comparators, 'Matrix must define exactly 5 comparators (EvolvePHP baseline + 4 frameworks)'); + $this->assertArrayHasKey('evolvephp', $comparators); + $this->assertArrayHasKey('laravel', $comparators); + $this->assertArrayHasKey('symfony', $comparators); + $this->assertArrayHasKey('slim', $comparators); + $this->assertArrayHasKey('phalcon', $comparators); + } + + public function testComparatorMatrixEnforcesStableOrdering(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix1 = ComparatorMatrix::fromJsonFile($matrixPath); + $matrix2 = ComparatorMatrix::fromJsonFile($matrixPath); + + $this->assertSame( + array_keys($matrix1->comparators()), + array_keys($matrix2->comparators()), + 'Comparator ordering must be deterministic' + ); + } + + public function testDuplicateComparatorIdThrowsException(): void + { + $invalidMatrix = [ + 'schema_version' => 'evolvephp.comparator.matrix.v1', + 'comparators' => [ + [ + 'id' => 'duplicate', + 'name' => 'Duplicate 1', + 'framework_version' => '1.0.0', + 'fixture_path' => 'comparators/dup1', + 'fixture_bootstrap' => 'comparators/dup1/bootstrap.php', + 'lock_path' => 'comparators/dup1/composer.lock', + ], + [ + 'id' => 'duplicate', + 'name' => 'Duplicate 2', + 'framework_version' => '2.0.0', + 'fixture_path' => 'comparators/dup2', + 'fixture_bootstrap' => 'comparators/dup2/bootstrap.php', + 'lock_path' => 'comparators/dup2/composer.lock', + ], + ], + ]; + + $this->expectException(ComparatorMatrixException::class); + ComparatorMatrix::fromArray($invalidMatrix); + } + + public function testMissingFixtureDirectoryThrowsException(): void + { + $invalidMatrix = [ + 'schema_version' => 'evolvephp.comparator.matrix.v1', + 'comparators' => [ + [ + 'id' => 'missing-fixture', + 'name' => 'Missing Fixture Framework', + 'framework_version' => '1.0.0', + 'fixture_path' => 'nonexistent/path', + 'fixture_bootstrap' => 'nonexistent/path/bootstrap.php', + 'lock_path' => 'nonexistent/path/composer.lock', + ], + ], + ]; + + $this->expectException(ComparatorMatrixException::class); + ComparatorMatrix::fromArray($invalidMatrix, $this->fixtureDir); + } + + public function testMalformedFrameworkDefinitionThrowsException(): void + { + $invalidMatrix = [ + 'schema_version' => 'evolvephp.comparator.matrix.v1', + 'comparators' => [ + [ + 'id' => 'incomplete', + // Missing required fields: name, framework_version, fixture_path + ], + ], + ]; + + $this->expectException(ComparatorMatrixException::class); + ComparatorMatrix::fromArray($invalidMatrix); + } + + public function testUnknownScenarioReferenceThrowsException(): void + { + $invalidMatrix = $this->minimalMatrix(); + $invalidMatrix['comparators'][0]['scenarios'] = ['not_a_common_scenario']; + + $this->expectException(ComparatorMatrixException::class); + $this->expectExceptionMessage('Unknown scenario'); + ComparatorMatrix::fromArray($invalidMatrix); + } + + public function testMatrixIncludesAllRequiredFrameworks(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + $comparators = $matrix->comparators(); + + $requiredFrameworks = ['evolvephp', 'laravel', 'symfony', 'slim', 'phalcon']; + foreach ($requiredFrameworks as $framework) { + $this->assertArrayHasKey( + $framework, + $comparators, + "Required framework '{$framework}' must be in matrix" + ); + } + } + + public function testEachComparatorHasRequiredMetadata(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + + foreach ($matrix->comparators() as $id => $comparator) { + $this->assertArrayHasKey('name', $comparator); + $this->assertArrayHasKey('framework_version', $comparator); + $this->assertArrayHasKey('fixture_path', $comparator); + $this->assertArrayHasKey('lock_path', $comparator); + $this->assertArrayHasKey('scenarios', $comparator); + + $this->assertIsString($comparator['name']); + $this->assertIsString($comparator['framework_version']); + $this->assertIsString($comparator['fixture_path']); + $this->assertIsString($comparator['lock_path']); + $this->assertIsArray($comparator['scenarios']); + } + } + + public function testMatrixReferencesExecutableFixturesAndLockfiles(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + + foreach ($matrix->comparators() as $id => $comparator) { + $fixturePath = $this->fixtureDir . DIRECTORY_SEPARATOR . trim((string) $comparator['fixture_path'], '/\\'); + $bootstrapPath = $this->fixtureDir . DIRECTORY_SEPARATOR . trim((string) $comparator['fixture_bootstrap'], '/\\'); + $lockPath = $this->fixtureDir . DIRECTORY_SEPARATOR . trim((string) $comparator['lock_path'], '/\\'); + + $this->assertDirectoryExists($fixturePath, "Comparator '{$id}' fixture directory must exist"); + $this->assertFileExists($bootstrapPath, "Comparator '{$id}' must expose an executable bootstrap"); + $this->assertFileExists($lockPath, "Comparator '{$id}' must own a Composer lockfile"); + } + } + + public function testMatrixUsesExactSelectedVersions(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + + foreach ($matrix->comparators() as $id => $comparator) { + $version = (string) $comparator['framework_version']; + + $this->assertDoesNotMatchRegularExpression('/\blatest\b/i', $version, "Comparator '{$id}' must not use latest wording"); + $this->assertDoesNotMatchRegularExpression('/\.x\b/i', $version, "Comparator '{$id}' must record an exact version"); + $this->assertDoesNotMatchRegularExpression('/\^|~|\*/', $version, "Comparator '{$id}' must not use a Composer constraint as its selected version"); + } + } + + public function testExternalComparatorPrincipalVersionsAreApprovedExactPins(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + + $expected = [ + 'laravel' => ['package' => 'laravel/framework', 'version' => '13.29.0', 'constraint' => '13.29.0'], + 'symfony' => ['package' => 'symfony/http-kernel', 'version' => '8.1.5', 'constraint' => '8.1.5'], + 'slim' => ['package' => 'slim/slim', 'version' => '4.15.2', 'constraint' => '4.15.2'], + 'phalcon' => ['package' => 'ext-phalcon', 'version' => '5.20.3', 'constraint' => 'suggest ext-phalcon 5.20.3'], + ]; + + foreach ($expected as $id => $pin) { + $comparator = $matrix->comparator($id); + $this->assertIsArray($comparator); + $this->assertSame($pin['package'], $comparator['framework_package']); + $this->assertSame($pin['version'], $comparator['framework_version']); + $this->assertSame($pin['constraint'], $comparator['composer_constraint']); + } + } + + public function testMatrixManifestAndLockfilePrincipalVersionsCannotDrift(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + + foreach (['laravel', 'symfony', 'slim'] as $id) { + $comparator = $matrix->comparator($id); + $this->assertIsArray($comparator); + + $fixturePath = $this->fixtureDir . DIRECTORY_SEPARATOR . trim((string) $comparator['fixture_path'], '/\\'); + $manifest = $this->readJsonFile($fixturePath . DIRECTORY_SEPARATOR . 'composer.json'); + $lock = $this->readJsonFile($this->fixtureDir . DIRECTORY_SEPARATOR . trim((string) $comparator['lock_path'], '/\\')); + $package = (string) $comparator['framework_package']; + + $this->assertSame($comparator['composer_constraint'], $manifest['require'][$package] ?? null); + $this->assertSame($comparator['framework_version'], ltrim($this->lockedVersion($lock, $package), 'v')); + } + } + + public function testMatrixLockHashesMatchLockfiles(): void + { + $matrixPath = $this->fixtureDir . DIRECTORY_SEPARATOR . 'matrix.json'; + $matrix = ComparatorMatrix::fromJsonFile($matrixPath); + + foreach ($matrix->comparators() as $id => $comparator) { + $lockPath = $this->fixtureDir . DIRECTORY_SEPARATOR . trim((string) $comparator['lock_path'], '/\\'); + + $this->assertSame( + strtolower((string) $comparator['lock_sha256']), + hash_file('sha256', $lockPath), + "Comparator '{$id}' matrix lock hash must match its lockfile" + ); + } + } + + public function testMissingFixtureBootstrapThrowsException(): void + { + $invalidMatrix = $this->minimalMatrix(); + $invalidMatrix['comparators'][0]['fixture_bootstrap'] = 'fixture/missing.php'; + + $this->expectException(ComparatorMatrixException::class); + $this->expectExceptionMessage('Fixture bootstrap not found'); + ComparatorMatrix::fromArray($invalidMatrix, $this->fixtureDir); + } + + public function testMissingLockfileThrowsException(): void + { + $invalidMatrix = $this->minimalMatrix(); + $invalidMatrix['comparators'][0]['lock_path'] = 'fixture/missing.lock'; + + $this->expectException(ComparatorMatrixException::class); + $this->expectExceptionMessage('Lockfile not found'); + ComparatorMatrix::fromArray($invalidMatrix, $this->fixtureDir); + } + + public function testUnsupportedImplementationModelThrowsException(): void + { + $invalidMatrix = $this->minimalMatrix(); + $invalidMatrix['comparators'][0]['implementation_model'] = 'shortcut'; + + $this->expectException(ComparatorMatrixException::class); + $this->expectExceptionMessage('Unsupported implementation model'); + ComparatorMatrix::fromArray($invalidMatrix); + } + + /** + * @return array + */ + private function minimalMatrix(): array + { + return [ + 'schema_version' => 'evolvephp.comparator.matrix.v1', + 'common_scenarios' => [ + 'http_static' => ['id' => 'http_static'], + ], + 'comparators' => [ + [ + 'id' => 'fixture', + 'name' => 'Fixture', + 'framework_package' => 'fixture/framework', + 'framework_version' => '1.0.0', + 'composer_constraint' => '^1.0', + 'fixture_path' => 'evolvephp', + 'fixture_bootstrap' => 'evolvephp/bootstrap.php', + 'lock_path' => 'evolvephp/composer.lock', + 'implementation_model' => 'pure-php', + 'availability' => 'always', + 'scenarios' => ['http_static'], + ], + ], + ]; + } + + /** + * @return array + */ + private function readJsonFile(string $path): array + { + $this->assertFileExists($path); + + return json_decode(file_get_contents($path), associative: true, flags: JSON_THROW_ON_ERROR); + } + + /** + * @param array $lock + */ + private function lockedVersion(array $lock, string $package): string + { + foreach ($lock['packages'] ?? [] as $lockedPackage) { + if (($lockedPackage['name'] ?? null) === $package) { + return (string) $lockedPackage['version']; + } + } + + $this->fail("Package '{$package}' must exist in comparator lockfile"); + } +} diff --git a/benchmarks/tests/Comparator/ComparatorResultNormalizerTest.php b/benchmarks/tests/Comparator/ComparatorResultNormalizerTest.php new file mode 100644 index 0000000..a96de44 --- /dev/null +++ b/benchmarks/tests/Comparator/ComparatorResultNormalizerTest.php @@ -0,0 +1,68 @@ + 'evolvephp.benchmark.results.v1', + 'source_sha' => str_repeat('a', 40), + 'scenarios' => [ + [ + 'id' => 'http_static', + 'sample_count' => 2, + 'mean' => 10.5, + ], + ], + ], + [ + 'id' => 'laravel', + 'name' => 'Laravel', + 'framework_version' => '12.68.0', + 'fixture_identity' => ['hash' => str_repeat('b', 64)], + 'execution_environment_identity' => ['hash' => str_repeat('c', 64)], + 'source_evolvephp_sha' => str_repeat('d', 40), + 'scenario_id' => 'http_static', + 'availability' => 'available', + 'notes' => ['local correctness smoke only'], + ], + ); + + self::assertSame('evolvephp.comparator.result.v1', $result['comparator_schema_version']); + self::assertSame('laravel', $result['comparator_id']); + self::assertSame('Laravel', $result['framework_name']); + self::assertSame('12.68.0', $result['framework_version']); + self::assertSame(str_repeat('b', 64), $result['fixture_identity']['hash']); + self::assertSame(str_repeat('c', 64), $result['execution_environment_identity']['hash']); + self::assertSame(str_repeat('d', 40), $result['source_evolvephp_sha']); + self::assertSame('http_static', $result['scenario_id']); + self::assertSame('available', $result['availability']); + self::assertSame(['local correctness smoke only'], $result['notes']); + self::assertSame('evolvephp.benchmark.results.v1', $result['baseline_result']['schema_version']); + } + + public function testUnavailableComparatorResultDoesNotInventTimingFields(): void + { + $result = ComparatorResultNormalizer::unavailable([ + 'id' => 'phalcon', + 'name' => 'Phalcon', + 'framework_version' => '5.9.3', + 'availability_reason' => 'phalcon extension not loaded', + ]); + + self::assertSame('unavailable', $result['availability']); + self::assertSame('phalcon extension not loaded', $result['availability_reason']); + self::assertNull($result['baseline_result']); + self::assertArrayNotHasKey('p95', $result); + self::assertArrayNotHasKey('p99', $result); + self::assertArrayNotHasKey('timing', $result); + } +} diff --git a/benchmarks/tests/Comparator/ComparatorSmokeVerifierTest.php b/benchmarks/tests/Comparator/ComparatorSmokeVerifierTest.php new file mode 100644 index 0000000..d3bec70 --- /dev/null +++ b/benchmarks/tests/Comparator/ComparatorSmokeVerifierTest.php @@ -0,0 +1,53 @@ + 'evolvephp.benchmark.environment.v2', + 'runtime' => ['php_version' => '8.4.0'], + 'platform' => ['os' => 'Linux'], + 'opcache' => ['enabled' => true], + 'jit' => ['enabled' => false], + 'extensions' => ['json', 'pdo'], + ]; + + $environment2 = $environment1; + + $fp1 = ExecutionEnvironmentFingerprint::fromEnvironment($environment1); + $fp2 = ExecutionEnvironmentFingerprint::fromEnvironment($environment2); + + $this->assertSame($fp1['hash'], $fp2['hash'], 'Same execution environment must produce same hash'); + $this->assertArrayNotHasKey('lock_hash', $fp1['fields'], 'Lock hash must not be in execution environment fingerprint'); + } + + public function testDifferentPhpVersionsProduceDifferentExecutionEnvironmentHash(): void + { + $environment1 = [ + 'runtime' => ['php_version' => '8.4.0'], + 'platform' => ['os' => 'Linux'], + 'opcache' => ['enabled' => true], + 'jit' => ['enabled' => false], + ]; + + $environment2 = $environment1; + $environment2['runtime']['php_version'] = '8.5.0'; + + $fp1 = ExecutionEnvironmentFingerprint::fromEnvironment($environment1); + $fp2 = ExecutionEnvironmentFingerprint::fromEnvironment($environment2); + + $this->assertNotSame($fp1['hash'], $fp2['hash'], 'Different PHP versions must produce different hashes'); + } + + public function testDifferentOpcacheStateProduceDifferentExecutionEnvironmentHash(): void + { + $environment1 = [ + 'runtime' => ['php_version' => '8.4.0'], + 'platform' => ['os' => 'Linux'], + 'opcache' => ['enabled' => true], + 'jit' => ['enabled' => false], + ]; + + $environment2 = $environment1; + $environment2['opcache']['enabled'] = false; + + $fp1 = ExecutionEnvironmentFingerprint::fromEnvironment($environment1); + $fp2 = ExecutionEnvironmentFingerprint::fromEnvironment($environment2); + + $this->assertNotSame($fp1['hash'], $fp2['hash'], 'Different OPcache state must produce different hashes'); + } + + public function testDifferentJitStateProduceDifferentExecutionEnvironmentHash(): void + { + $environment1 = [ + 'runtime' => ['php_version' => '8.4.0'], + 'platform' => ['os' => 'Linux'], + 'opcache' => ['enabled' => true], + 'jit' => ['enabled' => false], + ]; + + $environment2 = $environment1; + $environment2['jit']['enabled'] = true; + + $fp1 = ExecutionEnvironmentFingerprint::fromEnvironment($environment1); + $fp2 = ExecutionEnvironmentFingerprint::fromEnvironment($environment2); + + $this->assertNotSame($fp1['hash'], $fp2['hash'], 'Different JIT state must produce different hashes'); + } + + public function testFixtureIdentityIncludesFrameworkAndLockHash(): void + { + $fixtureData = [ + 'comparator_id' => 'laravel', + 'framework_name' => 'Laravel', + 'framework_version' => '11.0.0', + 'fixture_version' => 1, + 'lock_hash' => 'abc123def456', + ]; + + $identity = FixtureIdentity::fromArray($fixtureData); + + $this->assertArrayHasKey('lock_hash', $identity['fields']); + $this->assertArrayHasKey('framework_version', $identity['fields']); + $this->assertArrayHasKey('comparator_id', $identity['fields']); + } + + public function testSameExecutionEnvironmentWithDifferentFixtureLockHashesAreComparable(): void + { + $environment = [ + 'runtime' => ['php_version' => '8.4.0'], + 'platform' => ['os' => 'Linux'], + 'opcache' => ['enabled' => true], + 'jit' => ['enabled' => false], + ]; + + $fixture1 = [ + 'comparator_id' => 'laravel', + 'framework_version' => '11.0.0', + 'lock_hash' => 'laravel-lock-abc123', + ]; + + $fixture2 = [ + 'comparator_id' => 'symfony', + 'framework_version' => '7.0.0', + 'lock_hash' => 'symfony-lock-def456', + ]; + + $envFp = ExecutionEnvironmentFingerprint::fromEnvironment($environment); + $fixFp1 = FixtureIdentity::fromArray($fixture1); + $fixFp2 = FixtureIdentity::fromArray($fixture2); + + // Same execution environment + $this->assertSame($envFp['hash'], $envFp['hash']); + // Different fixtures + $this->assertNotSame($fixFp1['hash'], $fixFp2['hash']); + // But comparison is valid because execution environments match + $this->assertTrue(true, 'Different fixtures with same execution environment are comparable'); + } +} diff --git a/benchmarks/tests/Comparator/PhalconAvailabilityTest.php b/benchmarks/tests/Comparator/PhalconAvailabilityTest.php new file mode 100644 index 0000000..844e3e7 --- /dev/null +++ b/benchmarks/tests/Comparator/PhalconAvailabilityTest.php @@ -0,0 +1,54 @@ +assertIsArray($availability); + $this->assertArrayHasKey('available', $availability); + $this->assertArrayHasKey('extension_present', $availability); + + if ($availability['extension_present']) { + $this->assertArrayHasKey('extension_version', $availability); + $this->assertIsString($availability['extension_version']); + } + } + + public function testPhalconUnavailableReturnsConsistentStructure(): void + { + // If extension is not available, the structure must still be deterministic + $availability = PhalconAvailability::detect(); + + if (!$availability['available']) { + $this->assertFalse($availability['extension_present']); + $this->assertSame('unavailable', $availability['status']); + } + } + + public function testPhalconAvailabilityCanBeMarkedUnavailable(): void + { + $unavailable = PhalconAvailability::unavailable('extension not loaded'); + + $this->assertFalse($unavailable['available']); + $this->assertSame('unavailable', $unavailable['status']); + $this->assertIsString($unavailable['reason']); + } + + public function testPhalconFixtureDoesNotCrashMatrixWhenUnavailable(): void + { + $availability = PhalconAvailability::detect(); + + // This test verifies that the matrix can be loaded regardless of Phalcon availability + // The actual Phalcon fixture will handle unavailability gracefully + $this->assertIsArray($availability, 'Availability detection must return array structure'); + } +} diff --git a/tests/Architecture/EvolvePhp2ComparatorMatrixTest.php b/tests/Architecture/EvolvePhp2ComparatorMatrixTest.php new file mode 100644 index 0000000..ed286cd --- /dev/null +++ b/tests/Architecture/EvolvePhp2ComparatorMatrixTest.php @@ -0,0 +1,235 @@ +root = dirname(__DIR__, 2); + } + + public function testComparatorMatrixFileExists(): void + { + $matrixPath = $this->projectPath('benchmarks/comparators/matrix.json'); + $this->assertFileExists($matrixPath, 'Comparator matrix must exist at benchmarks/comparators/matrix.json'); + } + + public function testComparatorMatrixIsValidJson(): void + { + $matrixPath = $this->projectPath('benchmarks/comparators/matrix.json'); + $content = file_get_contents($matrixPath); + $data = json_decode($content, associative: true); + + $this->assertIsArray($data, 'Matrix file must contain valid JSON'); + $this->assertArrayHasKey('schema_version', $data); + $this->assertArrayHasKey('comparators', $data); + $this->assertArrayHasKey('common_scenarios', $data); + } + + public function testComparatorMatrixHasFiveFrameworks(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $comparators = $matrix['comparators'] ?? []; + + $this->assertCount(5, $comparators, 'Matrix must include EvolvePHP baseline + 4 frameworks'); + $ids = array_column($comparators, 'id'); + $this->assertContains('evolvephp', $ids); + $this->assertContains('laravel', $ids); + $this->assertContains('symfony', $ids); + $this->assertContains('slim', $ids); + $this->assertContains('phalcon', $ids); + } + + public function testComparatorMatrixEnforcesStableOrdering(): void + { + $matrix1 = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $matrix2 = $this->readJsonFile('benchmarks/comparators/matrix.json'); + + $ids1 = array_column($matrix1['comparators'], 'id'); + $ids2 = array_column($matrix2['comparators'], 'id'); + + $this->assertSame($ids1, $ids2, 'Comparator ordering must be stable across loads'); + } + + public function testAllComparatorFixtureDirectoriesExist(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $comparators = $matrix['comparators'] ?? []; + $comparatorBasePath = $this->projectPath('benchmarks/comparators'); + + foreach ($comparators as $comparator) { + $id = $comparator['id'] ?? 'unknown'; + $fixturePath = $comparatorBasePath . DIRECTORY_SEPARATOR . trim($comparator['fixture_path'] ?? '', '/\\'); + $this->assertDirectoryExists( + $fixturePath, + "Fixture directory must exist for comparator '{$id}' at {$fixturePath}" + ); + + // Each fixture must have a composer.json + $composerPath = $fixturePath . DIRECTORY_SEPARATOR . 'composer.json'; + $this->assertFileExists( + $composerPath, + "Fixture must have composer.json at {$composerPath}" + ); + } + } + + public function testCommonScenariosAreDefinedAndStable(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $commonScenarios = $matrix['common_scenarios'] ?? []; + + $expectedScenarios = [ + 'application_boot', + 'http_static', + 'http_parameterized', + 'http_middleware', + 'http_not_found', + 'http_repeated_warm', + ]; + + foreach ($expectedScenarios as $scenario) { + $this->assertArrayHasKey($scenario, $commonScenarios, "Scenario '{$scenario}' must be defined"); + } + } + + public function testComparatorScenariosReferenceOnlyCommonScenarios(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $commonScenarios = array_keys($matrix['common_scenarios'] ?? []); + $comparators = $matrix['comparators'] ?? []; + + foreach ($comparators as $comparator) { + $id = $comparator['id'] ?? 'unknown'; + $scenarios = $comparator['scenarios'] ?? []; + foreach ($scenarios as $scenario) { + $this->assertContains( + $scenario, + $commonScenarios, + "Comparator '{$id}' references unknown scenario '{$scenario}'" + ); + } + } + } + + public function testPhalconComparatorIsMarkedConditionalAvailability(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $comparators = $matrix['comparators'] ?? []; + $phalcon = null; + + foreach ($comparators as $comp) { + if (($comp['id'] ?? null) === 'phalcon') { + $phalcon = $comp; + break; + } + } + + $this->assertNotNull($phalcon, 'Phalcon comparator must be defined'); + $this->assertSame('conditional', $phalcon['availability'] ?? 'always', 'Phalcon availability must be conditional'); + $this->assertSame('compiled-extension', $phalcon['implementation_model'] ?? 'pure-php', 'Phalcon implementation model must be marked as compiled-extension'); + $this->assertArrayHasKey('availability_condition', $phalcon, 'Phalcon must document its availability condition'); + } + + public function testComparatorMetadataIncludesVersionAndFrameworkInfo(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $comparators = $matrix['comparators'] ?? []; + + foreach ($comparators as $comparator) { + $id = $comparator['id'] ?? 'unknown'; + $this->assertIsString($comparator['framework_version'] ?? null, "Comparator '{$id}' must have framework_version"); + $this->assertNotEmpty($comparator['framework_version'] ?? '', "Comparator '{$id}' framework_version must not be empty"); + $this->assertIsString($comparator['implementation_model'] ?? null, "Comparator '{$id}' must have implementation_model"); + $this->assertIsString($comparator['availability'] ?? null, "Comparator '{$id}' must have availability"); + } + } + + public function testComparatorDependenciesStayOutsideProductionPackageManifests(): void + { + $benchmarkPackageNames = [ + 'evolvephp/benchmarks', + 'evolvephp/benchmark-evolvephp', + 'evolvephp/benchmark-laravel', + 'evolvephp/benchmark-symfony', + 'evolvephp/benchmark-slim', + 'evolvephp/benchmark-phalcon', + 'phpbench/phpbench', + ]; + + foreach (glob($this->projectPath('packages/*/composer.json')) ?: [] as $manifestPath) { + $manifest = $this->readJsonFile($this->relativeProjectPath($manifestPath)); + $requires = array_merge($manifest['require'] ?? [], $manifest['require-dev'] ?? []); + + foreach ($benchmarkPackageNames as $packageName) { + $this->assertArrayNotHasKey( + $packageName, + $requires, + "Production package manifest {$manifestPath} must not depend on benchmark tooling" + ); + } + } + } + + public function testApplicationSkeletonDoesNotRequireBenchmarkOrComparatorTooling(): void + { + $manifest = $this->readJsonFile('skeleton/composer.json'); + $requires = array_merge($manifest['require'] ?? [], $manifest['require-dev'] ?? []); + + foreach (array_keys($requires) as $packageName) { + $this->assertFalse(str_starts_with($packageName, 'evolvephp/benchmark-')); + $this->assertNotSame('evolvephp/benchmarks', $packageName); + $this->assertNotSame('phpbench/phpbench', $packageName); + } + } + + public function testComparatorRootsRemainInsideBenchmarkBoundary(): void + { + $matrix = $this->readJsonFile('benchmarks/comparators/matrix.json'); + $benchmarkBoundary = realpath($this->projectPath('benchmarks/comparators')); + $this->assertIsString($benchmarkBoundary); + + foreach ($matrix['comparators'] ?? [] as $comparator) { + $fixturePath = realpath($this->projectPath('benchmarks/comparators/' . trim((string) $comparator['fixture_path'], '/\\'))); + $lockPath = realpath($this->projectPath('benchmarks/comparators/' . trim((string) $comparator['lock_path'], '/\\'))); + + $this->assertIsString($fixturePath, "Comparator {$comparator['id']} fixture path must resolve"); + $this->assertIsString($lockPath, "Comparator {$comparator['id']} lockfile path must resolve"); + $this->assertStringStartsWith($benchmarkBoundary, $fixturePath); + $this->assertStringStartsWith($benchmarkBoundary, $lockPath); + } + } + + public function testRootProductionDependenciesWereNotExpandedForComparators(): void + { + $manifest = $this->readJsonFile('composer.json'); + $runtimeRequires = $manifest['require'] ?? []; + + foreach (array_keys($runtimeRequires) as $packageName) { + $this->assertFalse(str_starts_with($packageName, 'evolvephp/benchmark-')); + $this->assertNotSame('evolvephp/benchmarks', $packageName); + $this->assertNotSame('phpbench/phpbench', $packageName); + } + } + + private function projectPath(string $path): string + { + return $this->root . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $path); + } + + private function readJsonFile(string $path): mixed + { + $fullPath = $this->projectPath($path); + $this->assertFileExists($fullPath); + + return json_decode(file_get_contents($fullPath), associative: true, flags: JSON_THROW_ON_ERROR); + } + + private function relativeProjectPath(string $path): string + { + return str_replace('\\', '/', substr($path, strlen($this->root) + 1)); + } +} diff --git a/tests/Documentation/EvolvePhp2ReadmeAndMetadataConsistencyTest.php b/tests/Documentation/EvolvePhp2ReadmeAndMetadataConsistencyTest.php index 9036be7..3ed6179 100644 --- a/tests/Documentation/EvolvePhp2ReadmeAndMetadataConsistencyTest.php +++ b/tests/Documentation/EvolvePhp2ReadmeAndMetadataConsistencyTest.php @@ -256,9 +256,13 @@ private function trackedFiles() $this->assertSame(0, $exitCode, 'git ls-files should succeed.'); - return array_map(function ($file) { + $files = array_map(function ($file) { return str_replace('\\', '/', $file); }, $output); + + return array_values(array_filter($files, function ($file) { + return ! str_contains($file, '/vendor/') && ! str_starts_with($file, 'vendor/'); + })); } private function markdownHeadings($content) From cd2970650d33c3cbc27a411b765313c5b7dc1952 Mon Sep 17 00:00:00 2001 From: Josiah King Date: Mon, 31 Aug 2026 16:20:15 +0100 Subject: [PATCH 2/2] Refine comparator benchmark documentation --- PHASE_6_5C1_REVIEW_BUNDLE.md | 22 ---------------------- benchmarks/README.md | 8 ++++---- 2 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 PHASE_6_5C1_REVIEW_BUNDLE.md diff --git a/PHASE_6_5C1_REVIEW_BUNDLE.md b/PHASE_6_5C1_REVIEW_BUNDLE.md deleted file mode 100644 index 806d1ba..0000000 --- a/PHASE_6_5C1_REVIEW_BUNDLE.md +++ /dev/null @@ -1,22 +0,0 @@ -# Phase 6.5C1 Review Bundle - -This file supersedes the earlier draft review notes for Phase 6.5C1. - -Phase 6.5C1 establishes comparator fixture correctness, dependency isolation and reproducibility. It does not establish that EvolvePHP is faster than Laravel, Symfony, Slim or Phalcon. Authoritative cross-framework performance measurements belong to Phase 6.5C2 and require the controlled PHP 8.4 benchmark environment. - -Benchmark and comparator infrastructure is EvolvePHP framework-development tooling. It is not required by applications built with EvolvePHP and is intentionally excluded from normal production application dependency graphs. - -The authoritative implementation-review evidence for this repair was produced in the final agent response for the acceptance-repair turn. The local comparator smoke command is: - -```powershell -D:\php-84\php.exe .\benchmarks\bin\comparator-smoke.php -``` - -Selected comparator versions are recorded in `benchmarks/comparators/matrix.json` with each comparator lockfile SHA-256 hash. External comparator framework identities are exactly pinned: Laravel `13.29.0`, Symfony `8.1.5`, Slim `4.15.2`, and Phalcon expected `5.20.3`. - -The reserved maintainer validators remain pending maintainer validation and were not run by the coding agent: - -- `release:validate` -- `release:split:validate` -- `release:skeleton:validate` -- `release:consumer:validate` diff --git a/benchmarks/README.md b/benchmarks/README.md index 53149f3..6e36d77 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -102,9 +102,9 @@ Cross-framework comparison, optimization work, and regression budgets are intent Benchmark and comparator infrastructure is EvolvePHP framework-development tooling. It is not required by applications built with EvolvePHP and is intentionally excluded from normal production application dependency graphs. -Phase 6.5C1 establishes comparator fixture correctness, dependency isolation and reproducibility. It does not establish that EvolvePHP is faster than Laravel, Symfony, Slim or Phalcon. +The comparator foundation establishes fixture correctness, dependency isolation and reproducibility. It does not establish that EvolvePHP is faster than Laravel, Symfony, Slim or Phalcon. -Authoritative cross-framework performance measurements belong to Phase 6.5C2 and require the controlled PHP 8.4 benchmark environment. +Authoritative cross-framework performance measurements require the documented controlled PHP 8.4 benchmark environment. ### Comparator Installation @@ -138,7 +138,7 @@ The smoke command loads `benchmarks/comparators/matrix.json`, validates fixture ### Comparator Versions -The Phase 6.5C1 matrix records these selected versions and lockfile hashes: +The comparator matrix records these selected versions and lockfile hashes: | Comparator | Package | Version | Constraint | Lockfile SHA-256 | | --- | --- | --- | --- | --- | @@ -201,4 +201,4 @@ Local development machines without the extension are allowed to report Phalcon u ### Local Comparator Results -Any local comparator output is local and non-canonical. It may prove fixture correctness, availability handling, schema shape and dependency isolation, but it must not be used as an authoritative performance comparison. Phase 6.5C2 owns controlled PHP 8.4 cross-framework measurement, normalization, publication and remediation decisions. +Any local comparator output is local and non-canonical. It may prove fixture correctness, availability handling, schema shape and dependency isolation, but it must not be used as an authoritative performance comparison. Controlled cross-framework measurement, normalization, publication and remediation decisions must use the documented PHP 8.4 benchmark protocol.