From 42d61ee52a0d1b9b7abc26109f6e73043d5d5ecf Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Tue, 17 Feb 2026 15:25:25 +0100 Subject: [PATCH 1/2] Add hydrator generation extension --- .github/workflows/benchmark.yml | 2 +- .gitignore | 2 + Makefile | 17 +- composer.json | 5 + composer.lock | 6519 ----------------- docs/extensions.md | 4 +- docs/generated-code.md | 103 + docs/hydrator.md | 1 + docs/project.json | 3 +- phpcs.xml.dist | 2 + phpstan.neon.dist | 2 + .../Generated/ClassNotGeneratable.php | 18 + src/Extension/Generated/ClassPlan.php | 47 + src/Extension/Generated/ClassPlanner.php | 304 + src/Extension/Generated/CodeEmitter.php | 523 ++ src/Extension/Generated/GeneratedHydrator.php | 124 + .../Generated/GeneratedMiddleware.php | 36 + .../GeneratedMiddlewareExtension.php | 102 + .../GeneratedMiddlewareNotWritable.php | 18 + src/Extension/Generated/HydratorNotSet.php | 18 + .../Generated/MiddlewareGenerator.php | 51 + .../Generated/OutdatedGeneratedMiddleware.php | 18 + src/Extension/Generated/PropertyPlan.php | 46 + src/Extension/Generated/Templates.php | 479 ++ src/Extension/Generated/ValueKind.php | 25 + src/HydratorFactoryAlreadySet.php | 15 + src/Middleware/HydratorAwareMiddleware.php | 17 + src/Middleware/Stack.php | 8 +- src/StackHydrator.php | 42 +- src/StackHydratorBuilder.php | 45 +- tests/Architecture/FinalClassesTest.php | 2 + tests/Benchmark/EventSauceHydratorBench.php | 132 + tests/Benchmark/Fixture/ProfileCreated.php | 3 + tests/Benchmark/Fixture/ProfileIdCaster.php | 39 + .../GeneratedEventSauceHydratorBench.php | 145 + tests/Benchmark/GeneratedHydratorBench.php | 142 + ...GeneratedHydratorWithCryptographyBench.php | 156 + tests/Benchmark/JMSHydratorBench.php | 141 + tests/Benchmark/SerdeHydratorBench.php | 148 + tests/Benchmark/SymfonyHydratorBench.php | 141 + .../GeneratedMiddlewareExtensionTest.php | 85 + .../Generated/GeneratedStackHydratorTest.php | 995 +++ .../Generated/MiddlewareGeneratorTest.php | 153 + .../Generated/Snapshot/SnapshotMiddleware.php | 3997 ++++++++++ .../Unit/Fixture/AsymmetricVisibilityDto.php | 16 + .../Fixture/AsymmetricVisibilityParentDto.php | 13 + tests/Unit/Fixture/CountingMiddleware.php | 67 + tests/Unit/Fixture/NestedLifecycleDto.php | 15 + tests/Unit/Fixture/PrivateChildDto.php | 23 + tests/Unit/Fixture/PrivateParentDto.php | 25 + tests/Unit/Fixture/PrivatePropertiesDto.php | 31 + tests/Unit/StackHydratorBuilderTest.php | 33 + 52 files changed, 8552 insertions(+), 6546 deletions(-) delete mode 100644 composer.lock create mode 100644 docs/generated-code.md create mode 100644 src/Extension/Generated/ClassNotGeneratable.php create mode 100644 src/Extension/Generated/ClassPlan.php create mode 100644 src/Extension/Generated/ClassPlanner.php create mode 100644 src/Extension/Generated/CodeEmitter.php create mode 100644 src/Extension/Generated/GeneratedHydrator.php create mode 100644 src/Extension/Generated/GeneratedMiddleware.php create mode 100644 src/Extension/Generated/GeneratedMiddlewareExtension.php create mode 100644 src/Extension/Generated/GeneratedMiddlewareNotWritable.php create mode 100644 src/Extension/Generated/HydratorNotSet.php create mode 100644 src/Extension/Generated/MiddlewareGenerator.php create mode 100644 src/Extension/Generated/OutdatedGeneratedMiddleware.php create mode 100644 src/Extension/Generated/PropertyPlan.php create mode 100644 src/Extension/Generated/Templates.php create mode 100644 src/Extension/Generated/ValueKind.php create mode 100644 src/HydratorFactoryAlreadySet.php create mode 100644 src/Middleware/HydratorAwareMiddleware.php create mode 100644 tests/Benchmark/EventSauceHydratorBench.php create mode 100644 tests/Benchmark/Fixture/ProfileIdCaster.php create mode 100644 tests/Benchmark/GeneratedEventSauceHydratorBench.php create mode 100644 tests/Benchmark/GeneratedHydratorBench.php create mode 100644 tests/Benchmark/GeneratedHydratorWithCryptographyBench.php create mode 100644 tests/Benchmark/JMSHydratorBench.php create mode 100644 tests/Benchmark/SerdeHydratorBench.php create mode 100644 tests/Benchmark/SymfonyHydratorBench.php create mode 100644 tests/Unit/Extension/Generated/GeneratedMiddlewareExtensionTest.php create mode 100644 tests/Unit/Extension/Generated/GeneratedStackHydratorTest.php create mode 100644 tests/Unit/Extension/Generated/MiddlewareGeneratorTest.php create mode 100644 tests/Unit/Extension/Generated/Snapshot/SnapshotMiddleware.php create mode 100644 tests/Unit/Fixture/AsymmetricVisibilityDto.php create mode 100644 tests/Unit/Fixture/AsymmetricVisibilityParentDto.php create mode 100644 tests/Unit/Fixture/CountingMiddleware.php create mode 100644 tests/Unit/Fixture/NestedLifecycleDto.php create mode 100644 tests/Unit/Fixture/PrivateChildDto.php create mode 100644 tests/Unit/Fixture/PrivateParentDto.php create mode 100644 tests/Unit/Fixture/PrivatePropertiesDto.php diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 23ef92b0..42d768ee 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - ini-values: memory_limit=-1, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M + ini-values: memory_limit=-1, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M, xdebug.mode=off - name: "Checkout base" uses: actions/checkout@v7 diff --git a/.gitignore b/.gitignore index a8264bba..3e62e239 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ infection.log infection.html .phpbench/ docs_php/ +var +stub diff --git a/Makefile b/Makefile index faf64b56..49cc8e18 100644 --- a/Makefile +++ b/Makefile @@ -34,14 +34,25 @@ static: phpstan cs ## run stat test: phpunit ## run tests +.PHONY: snapshot +snapshot: vendor ## regenerate the snapshot of the generated middleware + UPDATE_SNAPSHOTS=1 vendor/bin/phpunit --no-coverage --filter testGeneratedCodeMatchesSnapshot + +# benchmarks need opcache and must not run under xdebug, otherwise the numbers are meaningless +PHPBENCH_OPTS = --php-config='{"opcache.enable_cli": 1, "xdebug.mode": "off", "memory_limit": "-1"}' + .PHONY: benchmark benchmark: vendor ## run benchmarks - vendor/bin/phpbench run tests/Benchmark --report=default + vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --report=default + +.PHONY: benchmark-fast +benchmark-fast: vendor ## run only the fast benchmarks (our hydrators and the generated eventsauce mapper) with 1M objects + vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --iterations=3 --revs=1 --report='{"generator":"expression","cols":["benchmark","subject","mode","rstdev"]}' --filter='Benchmark\\(StackHydratorBench|GeneratedHydratorBench|GeneratedHydratorWithCryptographyBench|HydratorWithCryptographyBench|GeneratedEventSauceHydratorBench)::bench(Hydrate|Extract)1000000Objects$$' .PHONY: benchmark-diff-test benchmark-diff-test: vendor ## run benchmarks - vendor/bin/phpbench run tests/Benchmark --revs=1 --report=default --progress=none --tag=base - vendor/bin/phpbench run tests/Benchmark --revs=1 --report=diff --progress=none --ref=base + vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --revs=1 --report=default --progress=none --tag=base + vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --revs=1 --report=diff --progress=none --ref=base .PHONY: docs diff --git a/composer.json b/composer.json index 0f796930..428f47f8 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,10 @@ "symfony/type-info": "^7.3.0 || ^8.0.0" }, "require-dev": { + "crell/serde": "^1.5", + "eventsauce/object-hydrator": "^1.8", "infection/infection": "^0.32.4", + "jms/serializer": "^3.32", "patchlevel/coding-standard": "^1.3.0", "phpat/phpat": "^0.12.2", "phpbench/phpbench": "^1.4.3", @@ -38,6 +41,8 @@ "phpstan/phpstan-phpunit": "^2.0.15", "phpunit/phpunit": "^11.5.53", "symfony/cache": "^6.4.0 || ^7.0.0 || ^8.0.0", + "symfony/serializer": "^8.0", + "symfony/property-access": "^8.0", "symfony/var-dumper": "^5.4.29 || ^6.4.0 || ^7.0.0 || ^8.0.0", "wnx/commonmark-markdown-renderer": "^1.6" }, diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 28ace722..00000000 --- a/composer.lock +++ /dev/null @@ -1,6519 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "8325c0e87a9d0da748dae8c076093197", - "packages": [ - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+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/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": "symfony/type-info", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/type-info.git", - "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", - "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "psr/container": "^1.1|^2.0" - }, - "conflict": { - "phpstan/phpdoc-parser": "<1.30" - }, - "require-dev": { - "phpstan/phpdoc-parser": "^1.30|^2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\TypeInfo\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mathias Arlaud", - "email": "mathias.arlaud@gmail.com" - }, - { - "name": "Baptiste LEDUC", - "email": "baptiste.leduc@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Extracts PHP types information.", - "homepage": "https://symfony.com", - "keywords": [ - "PHPStan", - "phpdoc", - "symfony", - "type" - ], - "support": { - "source": "https://github.com/symfony/type-info/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" - } - ], - "packages-dev": [ - { - "name": "colinodell/json5", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/colinodell/json5.git", - "reference": "5724d21bc5c910c2560af1b8915f0cc0163579c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/colinodell/json5/zipball/5724d21bc5c910c2560af1b8915f0cc0163579c8", - "reference": "5724d21bc5c910c2560af1b8915f0cc0163579c8", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^8.0" - }, - "require-dev": { - "mikehaertl/php-shellcommand": "^1.7.0", - "phpstan/phpstan": "^1.10.57", - "scrutinizer/ocular": "^1.9", - "squizlabs/php_codesniffer": "^3.8.1", - "symfony/finder": "^6.0|^7.0", - "symfony/phpunit-bridge": "^7.0.3" - }, - "bin": [ - "bin/json5" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "files": [ - "src/global.php" - ], - "psr-4": { - "ColinODell\\Json5\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", - "role": "Developer" - } - ], - "description": "UTF-8 compatible JSON5 parser for PHP", - "homepage": "https://github.com/colinodell/json5", - "keywords": [ - "JSON5", - "json", - "json5_decode", - "json_decode" - ], - "support": { - "issues": "https://github.com/colinodell/json5/issues", - "source": "https://github.com/colinodell/json5/tree/v3.0.0" - }, - "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://www.patreon.com/colinodell", - "type": "patreon" - } - ], - "time": "2024-02-09T13:06:12+00:00" - }, - { - "name": "composer/pcre", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", - "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<2.2.2" - }, - "require-dev": { - "phpstan/phpstan": "^2", - "phpstan/phpstan-deprecation-rules": "^2", - "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^9" - }, - "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.4.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2026-06-07T11:47:49+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", - "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^2.2", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "^2.2", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", - "yoast/phpunit-polyfills": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "opensource@frenck.dev", - "homepage": "https://frenck.dev", - "role": "Open source developer" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", - "source": "https://github.com/PHPCSStandards/composer-installer" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2026-05-06T08:26:05+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/annotations", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.10.28", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6.4 || ^7", - "vimeo/psalm": "^4.30 || ^5.14" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "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": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.2" - }, - "abandoned": true, - "time": "2024-09-05T10:17:24+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": "fidry/cpu-core-counter", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2025-08-14T07:29:31+00:00" - }, - { - "name": "infection/abstract-testframework-adapter", - "version": "0.5.1", - "source": { - "type": "git", - "url": "https://github.com/infection/abstract-testframework-adapter.git", - "reference": "b24bf3e850f70cd20a10621f08c3cef66f147ac8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/abstract-testframework-adapter/zipball/b24bf3e850f70cd20a10621f08c3cef66f147ac8", - "reference": "b24bf3e850f70cd20a10621f08c3cef66f147ac8", - "shasum": "" - }, - "require": { - "php": "^8.3" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.18", - "fidry/makefile": "^1.0", - "friendsofphp/php-cs-fixer": "^3.95.2", - "phpunit/phpunit": "^12.0 || ^13.0", - "rector/rector": "^2.4.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Infection\\AbstractTestFramework\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Abstract Test Framework Adapter for Infection", - "support": { - "issues": "https://github.com/infection/abstract-testframework-adapter/issues", - "source": "https://github.com/infection/abstract-testframework-adapter/tree/0.5.1" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2026-05-28T19:10:29+00:00" - }, - { - "name": "infection/extension-installer", - "version": "0.1.2", - "source": { - "type": "git", - "url": "https://github.com/infection/extension-installer.git", - "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/extension-installer/zipball/9b351d2910b9a23ab4815542e93d541e0ca0cdcf", - "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0" - }, - "require-dev": { - "composer/composer": "^1.9 || ^2.0", - "friendsofphp/php-cs-fixer": "^2.18, <2.19", - "infection/infection": "^0.15.2", - "php-coveralls/php-coveralls": "^2.4", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.10", - "phpstan/phpstan-phpunit": "^0.12.6", - "phpstan/phpstan-strict-rules": "^0.12.2", - "phpstan/phpstan-webmozart-assert": "^0.12.2", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.8" - }, - "type": "composer-plugin", - "extra": { - "class": "Infection\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "Infection\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Infection Extension Installer", - "support": { - "issues": "https://github.com/infection/extension-installer/issues", - "source": "https://github.com/infection/extension-installer/tree/0.1.2" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2021-10-20T22:08:34+00:00" - }, - { - "name": "infection/include-interceptor", - "version": "0.2.5", - "source": { - "type": "git", - "url": "https://github.com/infection/include-interceptor.git", - "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/include-interceptor/zipball/0cc76d95a79d9832d74e74492b0a30139904bdf7", - "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7", - "shasum": "" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "infection/infection": "^0.15.0", - "phan/phan": "^2.4 || ^3", - "php-coveralls/php-coveralls": "^2.2", - "phpstan/phpstan": "^0.12.8", - "phpunit/phpunit": "^8.5", - "vimeo/psalm": "^3.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Infection\\StreamWrapper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Stream Wrapper: Include Interceptor. Allows to replace included (autoloaded) file with another one.", - "support": { - "issues": "https://github.com/infection/include-interceptor/issues", - "source": "https://github.com/infection/include-interceptor/tree/0.2.5" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2021-08-09T10:03:57+00:00" - }, - { - "name": "infection/infection", - "version": "0.32.7", - "source": { - "type": "git", - "url": "https://github.com/infection/infection.git", - "reference": "066ee69f1e8e6dec8965a79d5ba020656c590b5e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/066ee69f1e8e6dec8965a79d5ba020656c590b5e", - "reference": "066ee69f1e8e6dec8965a79d5ba020656c590b5e", - "shasum": "" - }, - "require": { - "colinodell/json5": "^3.0", - "composer-runtime-api": "^2.0", - "composer/xdebug-handler": "^3.0", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "fidry/cpu-core-counter": "^1.0", - "infection/abstract-testframework-adapter": "^0.5.0", - "infection/extension-installer": "^0.1.0", - "infection/include-interceptor": "^0.2.5", - "infection/mutator": "^0.4", - "justinrainbow/json-schema": "^6.0", - "nikic/php-parser": "^5.6.2", - "ondram/ci-detector": "^4.1.0", - "php": "^8.3", - "psr/log": "^2.0 || ^3.0", - "sanmai/di-container": "^0.1.12", - "sanmai/duoclock": "^0.1.0", - "sanmai/later": "^0.1.7", - "sanmai/pipeline": "^7.2", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", - "symfony/console": "^6.4 || ^7.0 || ^8.0", - "symfony/filesystem": "^6.4 || ^7.0 || ^8.0", - "symfony/finder": "^6.4 || ^7.0 || ^8.0", - "symfony/polyfill-php85": "^1.33", - "symfony/process": "^6.4 || ^7.0 || ^8.0", - "thecodingmachine/safe": "^v3.0", - "webmozart/assert": "^1.11 || ^2.0" - }, - "conflict": { - "antecedent/patchwork": "<2.1.25", - "dg/bypass-finals": "<1.4.1" - }, - "require-dev": { - "carthage-software/mago": "^1.20", - "ext-simplexml": "*", - "fidry/makefile": "^1.0", - "fig/log-test": "^1.2", - "phpat/phpat": "^0.12.4", - "phpbench/phpbench": "^1.4", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpstan/phpstan-webmozart-assert": "^2.0", - "phpunit/phpunit": "^11.5.27", - "rector/rector": "^2.2.4", - "shipmonk/dead-code-detector": "^0.15", - "shipmonk/name-collision-detector": "^2.1", - "sidz/phpstan-rules": "^0.5.1", - "symfony/yaml": "^6.4 || ^7.0 || ^8.0", - "thecodingmachine/phpstan-safe-rule": "^1.4", - "webmozarts/strict-phpunit": "^7.15" - }, - "bin": [ - "bin/infection" - ], - "type": "library", - "autoload": { - "psr-4": { - "Infection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com", - "homepage": "https://twitter.com/maks_rafalko" - }, - { - "name": "Oleg Zhulnev", - "homepage": "https://github.com/sidz" - }, - { - "name": "Gert de Pagter", - "homepage": "https://github.com/BackEndTea" - }, - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com", - "homepage": "https://twitter.com/tfidry" - }, - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com", - "homepage": "https://www.alexeykopytko.com" - }, - { - "name": "Andreas Möller", - "email": "am@localheinz.com", - "homepage": "https://localheinz.com" - } - ], - "description": "Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.", - "keywords": [ - "coverage", - "mutant", - "mutation framework", - "mutation testing", - "testing", - "unit testing" - ], - "support": { - "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.32.7" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2026-04-23T21:11:41+00:00" - }, - { - "name": "infection/mutator", - "version": "0.4.1", - "source": { - "type": "git", - "url": "https://github.com/infection/mutator.git", - "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/mutator/zipball/3c976d721b02b32f851ee4e15d553ef1e9186d1d", - "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Infection\\Mutator\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Mutator interface to implement custom mutators (mutation operators) for Infection", - "support": { - "issues": "https://github.com/infection/mutator/issues", - "source": "https://github.com/infection/mutator/tree/0.4.1" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2025-04-29T08:19:52+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "6.10.0", - "source": { - "type": "git", - "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33", - "reference": "8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33", - "shasum": "" - }, - "require": { - "ext-json": "*", - "marc-mabe/php-enum": "^4.4", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "dev-main", - "marc-mabe/php-enum-phpstan": "^2.0", - "phpspec/prophecy": "^1.19", - "phpstan/phpstan": "^1.12", - "phpunit/phpunit": "^8.5" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/jsonrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.10.0" - }, - "time": "2026-06-16T20:50:26+00:00" - }, - { - "name": "league/commonmark", - "version": "2.10.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9d489ab67a02960fd8ffe624d93f751daf95439e", - "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e", - "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-09-07T13:44:26+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": "marc-mabe/php-enum", - "version": "v4.7.2", - "source": { - "type": "git", - "url": "https://github.com/marc-mabe/php-enum.git", - "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", - "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", - "shasum": "" - }, - "require": { - "ext-reflection": "*", - "php": "^7.1 | ^8.0" - }, - "require-dev": { - "phpbench/phpbench": "^0.16.10 || ^1.0.4", - "phpstan/phpstan": "^1.3.1", - "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", - "vimeo/psalm": "^4.17.0 | ^5.26.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-3.x": "3.2-dev", - "dev-master": "4.7-dev" - } - }, - "autoload": { - "psr-4": { - "MabeEnum\\": "src/" - }, - "classmap": [ - "stubs/Stringable.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Marc Bennewitz", - "email": "dev@mabe.berlin", - "homepage": "https://mabe.berlin/", - "role": "Lead" - } - ], - "description": "Simple and fast implementation of enumerations with native PHP", - "homepage": "https://github.com/marc-mabe/php-enum", - "keywords": [ - "enum", - "enum-map", - "enum-set", - "enumeration", - "enumerator", - "enummap", - "enumset", - "map", - "set", - "type", - "type-hint", - "typehint" - ], - "support": { - "issues": "https://github.com/marc-mabe/php-enum/issues", - "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" - }, - "time": "2025-09-14T11:18:39+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.13.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+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": "nikic/php-parser", - "version": "v5.8.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", - "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" - }, - "time": "2026-07-04T14:30:18+00:00" - }, - { - "name": "ondram/ci-detector", - "version": "4.2.0", - "source": { - "type": "git", - "url": "https://github.com/OndraM/ci-detector.git", - "reference": "8b0223b5ed235fd377c75fdd1bfcad05c0f168b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/OndraM/ci-detector/zipball/8b0223b5ed235fd377c75fdd1bfcad05c0f168b8", - "reference": "8b0223b5ed235fd377c75fdd1bfcad05c0f168b8", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.13.2", - "lmc/coding-standard": "^3.0.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.1.0", - "phpstan/phpstan": "^1.2.0", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpunit/phpunit": "^9.6.13" - }, - "type": "library", - "autoload": { - "psr-4": { - "OndraM\\CiDetector\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ondřej Machulda", - "email": "ondrej.machulda@gmail.com" - } - ], - "description": "Detect continuous integration environment and provide unified access to properties of current build", - "keywords": [ - "CircleCI", - "Codeship", - "Wercker", - "adapter", - "appveyor", - "aws", - "aws codebuild", - "azure", - "azure devops", - "azure pipelines", - "bamboo", - "bitbucket", - "buddy", - "ci-info", - "codebuild", - "continuous integration", - "continuousphp", - "devops", - "drone", - "github", - "gitlab", - "interface", - "jenkins", - "pipelines", - "sourcehut", - "teamcity", - "travis" - ], - "support": { - "issues": "https://github.com/OndraM/ci-detector/issues", - "source": "https://github.com/OndraM/ci-detector/tree/4.2.0" - }, - "time": "2024-03-12T13:22:30+00:00" - }, - { - "name": "patchlevel/coding-standard", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/patchlevel/coding-standard.git", - "reference": "a5b1d54f7bbe3d822603679d8c55cff2a7c7fc9b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/patchlevel/coding-standard/zipball/a5b1d54f7bbe3d822603679d8c55cff2a7c7fc9b", - "reference": "a5b1d54f7bbe3d822603679d8c55cff2a7c7fc9b", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2 || ^1.0.0", - "php": "^7.4 || ^8.0", - "slevomat/coding-standard": "^8.12.1", - "squizlabs/php_codesniffer": "^3.7.2" - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Badura", - "email": "daniel.badura@patchlevel.de" - }, - { - "name": "David Badura", - "email": "david.badura@patchlevel.de" - } - ], - "description": "The Patchlevel Coding Standard is a set of PHPCS rules applied to all Patchlevel projects.", - "homepage": "https://github.com/patchlevel/coding-standard", - "keywords": [ - "checks", - "code", - "coding", - "cs", - "patchlevel", - "rules", - "sniffer", - "sniffs", - "standard", - "style" - ], - "support": { - "issues": "https://github.com/patchlevel/coding-standard/issues", - "source": "https://github.com/patchlevel/coding-standard/tree/1.3.0" - }, - "time": "2023-06-02T10:05:08+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpat/phpat", - "version": "0.12.4", - "source": { - "type": "git", - "url": "https://github.com/carlosas/phpat.git", - "reference": "5319264270c335f548451209bb0f32b55aa59924" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/carlosas/phpat/zipball/5319264270c335f548451209bb0f32b55aa59924", - "reference": "5319264270c335f548451209bb0f32b55aa59924", - "shasum": "" - }, - "require": { - "php": "^8.1", - "phpstan/phpstan": "^2.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.87", - "kubawerlos/php-cs-fixer-custom-fixers": "3.32.*", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^6.13" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - } - }, - "autoload": { - "files": [ - "helpers.php" - ], - "psr-4": { - "PHPat\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Carlos Alandete Sastre", - "email": "carlos.alandete@gmail.com" - } - ], - "description": "PHP Architecture Tester", - "support": { - "issues": "https://github.com/carlosas/phpat/issues", - "source": "https://github.com/carlosas/phpat/tree/0.12.4" - }, - "time": "2026-03-17T16:47:43+00:00" - }, - { - "name": "phpbench/container", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/phpbench/container.git", - "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196", - "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196", - "shasum": "" - }, - "require": { - "psr/container": "^1.0|^2.0", - "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0" - }, - "require-dev": { - "php-cs-fixer/shim": "^3.89", - "phpstan/phpstan": "^0.12.52", - "phpunit/phpunit": "^8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpBench\\DependencyInjection\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Leech", - "email": "daniel@dantleech.com" - } - ], - "description": "Simple, configurable, service container.", - "support": { - "issues": "https://github.com/phpbench/container/issues", - "source": "https://github.com/phpbench/container/tree/2.2.3" - }, - "time": "2025-11-06T09:05:13+00:00" - }, - { - "name": "phpbench/phpbench", - "version": "1.7.0", - "source": { - "type": "git", - "url": "https://github.com/phpbench/phpbench.git", - "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", - "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^2.0", - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "ext-tokenizer": "*", - "php": "^8.2", - "phpbench/container": "^2.2", - "psr/log": "^1.1 || ^2.0 || ^3.0", - "seld/jsonlint": "^1.1", - "symfony/console": "^6.1 || ^7.0 || ^8.0", - "symfony/filesystem": "^6.1 || ^7.0 || ^8.0", - "symfony/finder": "^6.1 || ^7.0 || ^8.0", - "symfony/options-resolver": "^6.1 || ^7.0 || ^8.0", - "symfony/process": "^6.1 || ^7.0 || ^8.0", - "webmozart/glob": "^4.6" - }, - "require-dev": { - "dantleech/invoke": "^2.0", - "ergebnis/composer-normalize": "^2.39", - "jangregor/phpstan-prophecy": "^2.0", - "php-cs-fixer/shim": "^3.9", - "phpspec/prophecy": "^1.22", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^11.5", - "rector/rector": "^2.2", - "sebastian/exporter": "^6.3.2", - "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", - "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" - }, - "suggest": { - "ext-xdebug": "For Xdebug profiling extension." - }, - "bin": [ - "bin/phpbench" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "files": [ - "lib/Report/Func/functions.php" - ], - "psr-4": { - "PhpBench\\": "lib/", - "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Leech", - "email": "daniel@dantleech.com" - } - ], - "description": "PHP Benchmarking Framework", - "keywords": [ - "benchmarking", - "optimization", - "performance", - "profiling", - "testing" - ], - "support": { - "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.7.0" - }, - "funding": [ - { - "url": "https://github.com/dantleech", - "type": "github" - } - ], - "time": "2026-06-08T19:09:20+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "2.3.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", - "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" - }, - "time": "2026-07-08T07:01:06+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "2.2.7", - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", - "reference": "692db47b9dddb0487934e5236e77d48594aef921", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ondřej Mirtes" - }, - { - "name": "Markus Staab" - }, - { - "name": "Vincent Langlet" - } - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2026-07-29T17:39:32+00:00" - }, - { - "name": "phpstan/phpstan-phpunit", - "version": "2.0.18", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f5dc20ff8082d02339b60cab68ec3eb0d859fb30", - "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30", - "shasum": "" - }, - "require": { - "phar-io/version": "^3.2", - "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.2.3" - }, - "conflict": { - "phpunit/phpunit": "<7.0" - }, - "require-dev": { - "nikic/php-parser": "^5", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "shipmonk/name-collision-detector": "^2.1" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "extension.neon", - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPUnit extensions and rules for PHPStan", - "keywords": [ - "static analysis" - ], - "support": { - "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.18" - }, - "time": "2026-07-04T12:16:09+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "11.0.12", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", - "php": ">=8.2", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-text-template": "^4.0.1", - "sebastian/code-unit-reverse-lookup": "^4.0.1", - "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.1", - "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.3.1" - }, - "require-dev": { - "phpunit/phpunit": "^11.5.46" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "11.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", - "type": "tidelift" - } - ], - "time": "2025-12-24T07:01:01+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "5.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", - "type": "tidelift" - } - ], - "time": "2026-02-02T13:52:54+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "5.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^11.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:07:44+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:08:43+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "7.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:09:35+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "11.5.56", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", - "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-filter": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.1", - "phpunit/php-invoker": "^5.0.1", - "phpunit/php-text-template": "^4.0.1", - "phpunit/php-timer": "^7.0.1", - "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.3", - "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.2", - "sebastian/global-state": "^7.0.2", - "sebastian/object-enumerator": "^6.0.1", - "sebastian/recursion-context": "^6.0.3", - "sebastian/type": "^5.1.3", - "sebastian/version": "^5.0.2", - "staabm/side-effects-detector": "^1.0.5" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "11.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" - } - ], - "time": "2026-07-06T14:52:39+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/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/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": "sanmai/di-container", - "version": "0.1.17", - "source": { - "type": "git", - "url": "https://github.com/sanmai/di-container.git", - "reference": "a901c4a8778c9212ef4d66607525281af2f787bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sanmai/di-container/zipball/a901c4a8778c9212ef4d66607525281af2f787bd", - "reference": "a901c4a8778c9212ef4d66607525281af2f787bd", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/container": "^1.1.2 || ^2.0", - "sanmai/pipeline": "^6.17 || ^7.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.8", - "friendsofphp/php-cs-fixer": "^3.17", - "infection/infection": ">=0.31", - "php-coveralls/php-coveralls": "^2.4.1", - "phpbench/phpbench": "^1.4", - "phpstan/extension-installer": "^1.4", - "phpunit/phpunit": "^11.5.25", - "sanmai/phpstan-rules": "^0.3.10" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.1.x-dev" - }, - "preferred-install": "dist" - }, - "autoload": { - "psr-4": { - "DIContainer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com", - "homepage": "https://github.com/sanmai" - }, - { - "name": "Maks Rafalko", - "homepage": "https://twitter.com/maks_rafalko" - }, - { - "name": "Théo FIDRY", - "homepage": "https://twitter.com/tfidry" - } - ], - "description": "dependency injection container with automatic constructor dependency resolution", - "keywords": [ - "Autowiring", - "constructor di", - "di container", - "psr 11" - ], - "support": { - "issues": "https://github.com/sanmai/di-container/issues", - "source": "https://github.com/sanmai/di-container/tree/0.1.17" - }, - "funding": [ - { - "url": "https://github.com/sanmai", - "type": "github" - } - ], - "time": "2026-06-01T08:52:14+00:00" - }, - { - "name": "sanmai/duoclock", - "version": "0.1.3", - "source": { - "type": "git", - "url": "https://github.com/sanmai/DuoClock.git", - "reference": "47461e3ff65b7308635047831a55615652e7be1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sanmai/DuoClock/zipball/47461e3ff65b7308635047831a55615652e7be1a", - "reference": "47461e3ff65b7308635047831a55615652e7be1a", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/clock": "^1.0" - }, - "provide": { - "psr/clock-implementation": "1.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.8", - "friendsofphp/php-cs-fixer": "^3.17", - "infection/infection": ">=0.29", - "php-coveralls/php-coveralls": "^2.4.1", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^11.5.25", - "sanmai/phpstan-rules": "^0.3.1" - }, - "type": "library", - "extra": { - "preferred-install": "dist" - }, - "autoload": { - "psr-4": { - "DuoClock\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com" - } - ], - "description": "PHP time mocking for tests - PSR-20 clock with mockable sleep(), time(), and TimeSpy for PHPUnit testing", - "support": { - "issues": "https://github.com/sanmai/DuoClock/issues", - "source": "https://github.com/sanmai/DuoClock/tree/0.1.3" - }, - "funding": [ - { - "url": "https://github.com/sanmai", - "type": "github" - } - ], - "time": "2025-12-26T06:12:34+00:00" - }, - { - "name": "sanmai/later", - "version": "0.1.8", - "source": { - "type": "git", - "url": "https://github.com/sanmai/later.git", - "reference": "c56aeb8fa7fdf81eda2640a68b51884685963d13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sanmai/later/zipball/c56aeb8fa7fdf81eda2640a68b51884685963d13", - "reference": "c56aeb8fa7fdf81eda2640a68b51884685963d13", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.8", - "friendsofphp/php-cs-fixer": "^3.35.1", - "infection/infection": ">=0.27.6", - "phan/phan": ">=2", - "php-coveralls/php-coveralls": "^2.0", - "phpstan/phpstan": ">=1.4.5", - "phpunit/phpunit": ">=9.5 <10", - "vimeo/psalm": ">=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.1.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Later\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com" - } - ], - "description": "Later: deferred wrapper object", - "support": { - "issues": "https://github.com/sanmai/later/issues", - "source": "https://github.com/sanmai/later/tree/0.1.8" - }, - "funding": [ - { - "url": "https://github.com/sanmai", - "type": "github" - } - ], - "time": "2026-06-29T07:24:33+00:00" - }, - { - "name": "sanmai/pipeline", - "version": "7.10", - "source": { - "type": "git", - "url": "https://github.com/sanmai/pipeline.git", - "reference": "a8e4e57a6031efc7a92defd13fed9c1c4c73fc71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sanmai/pipeline/zipball/a8e4e57a6031efc7a92defd13fed9c1c4c73fc71", - "reference": "a8e4e57a6031efc7a92defd13fed9c1c4c73fc71", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.8", - "esi/phpunit-coverage-check": ">2", - "friendsofphp/php-cs-fixer": "^3.17", - "infection/infection": ">=0.32.3", - "league/pipeline": "^0.3 || ^1.0", - "php-coveralls/php-coveralls": "^2.4.1", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^11 || ^12", - "sanmai/phpstan-rules": "^0.3.11", - "sanmai/phpunit-double-colon-syntax": "^0.1.1", - "vimeo/psalm": ">=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Pipeline\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com" - } - ], - "description": "General-purpose collections pipeline", - "support": { - "issues": "https://github.com/sanmai/pipeline/issues", - "source": "https://github.com/sanmai/pipeline/tree/7.10" - }, - "funding": [ - { - "url": "https://github.com/sanmai", - "type": "github" - } - ], - "time": "2026-08-03T04:56:22+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:41:36+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", - "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2025-03-19T07:56:08+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:45:54+00:00" - }, - { - "name": "sebastian/comparator", - "version": "6.3.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", - "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/diff": "^6.0", - "sebastian/exporter": "^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^11.4" - }, - "suggest": { - "ext-bcmath": "For comparing BcMath\\Number objects" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.3-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-01-24T09:26:40+00:00" - }, - { - "name": "sebastian/complexity", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:49:50+00:00" - }, - { - "name": "sebastian/diff", - "version": "6.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:53:05+00:00" - }, - { - "name": "sebastian/environment", - "version": "7.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", - "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", - "type": "tidelift" - } - ], - "time": "2025-05-21T11:55:47+00:00" - }, - { - "name": "sebastian/exporter", - "version": "6.3.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/recursion-context": "^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^11.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.3-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2025-09-24T06:12:51+00:00" - }, - { - "name": "sebastian/global-state", - "version": "7.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:57:36+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:58:38+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "6.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:00:13+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:01:32+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "6.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", - "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2025-08-13T04:42:22+00:00" - }, - { - "name": "sebastian/type", - "version": "5.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", - "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/type", - "type": "tidelift" - } - ], - "time": "2025-08-09T06:55:48+00:00" - }, - { - "name": "sebastian/version", - "version": "5.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-10-09T05:16:32+00:00" - }, - { - "name": "seld/jsonlint", - "version": "1.12.1", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", - "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], - "time": "2026-06-12T11:32:29+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "8.22.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/1dd80bf3b93692bedb21a6623c496887fad05fec", - "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", - "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.3.0", - "squizlabs/php_codesniffer": "^3.13.4" - }, - "require-dev": { - "phing/phing": "3.0.1|3.1.0", - "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.24", - "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.7", - "phpstan/phpstan-strict-rules": "2.0.6", - "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.10" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "8.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "keywords": [ - "dev", - "phpcs" - ], - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.22.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2025-09-13T08:53:30+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.13.5", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" - }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-11-04T16:30:35+00:00" - }, - { - "name": "staabm/side-effects-detector", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/staabm/side-effects-detector.git", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.6", - "phpunit/phpunit": "^9.6.21", - "symfony/var-dumper": "^5.4.43", - "tomasvotruba/type-coverage": "1.0.0", - "tomasvotruba/unused-public": "1.0.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "lib/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A static analysis tool to detect side effects in PHP code", - "keywords": [ - "static analysis" - ], - "support": { - "issues": "https://github.com/staabm/side-effects-detector/issues", - "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" - }, - "funding": [ - { - "url": "https://github.com/staabm", - "type": "github" - } - ], - "time": "2024-10-20T05:08:20+00:00" - }, - { - "name": "symfony/cache", - "version": "v8.1.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "47616ab077b5aff4d0f718b96680a48599fc1a3f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/47616ab077b5aff4d0f718b96680a48599fc1a3f", - "reference": "47616ab077b5aff4d0f718b96680a48599fc1a3f", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^3.6", - "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^8.1" - }, - "conflict": { - "ext-redis": "<6.1", - "ext-relay": "<0.12.1" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "^1.0.3", - "doctrine/dbal": "^4.3", - "predis/predis": "^1.1|^2.0", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/clock": "^7.4|^8.0", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/filesystem": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "classmap": [ - "Traits/ValueWrapper.php" - ], - "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 extended PSR-6, PSR-16 (and tags) implementations", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v8.1.7" - }, - "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-09-08T13:39:13+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", - "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/cache": "^3.0" - }, - "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\\Cache\\": "" - } - }, - "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 caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-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/console", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "535e18a1b8925f6c01a55b171d157ab66c2ace15" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/535e18a1b8925f6c01a55b171d157ab66c2ace15", - "reference": "535e18a1b8925f6c01a55b171d157ab66c2ace15", - "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.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-27T13:58:19+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/filesystem", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "17856b7a222664a26a5ea1cb06ee0721c2438217" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/17856b7a222664a26a5ea1cb06ee0721c2438217", - "reference": "17856b7a222664a26a5ea1cb06ee0721c2438217", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "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 basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/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-22T15:42:13+00:00" - }, - { - "name": "symfony/finder", - "version": "v8.1.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e2989e762c70f9490fa3a00a0ac0fae5aa97a531", - "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531", - "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.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-27T09:05:56+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "88f9c561f678a02d54b897014049fa839e33ff82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", - "reference": "88f9c561f678a02d54b897014049fa839e33ff82", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "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 an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/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/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-deepclone", - "version": "v1.42.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-deepclone.git", - "reference": "70ba0627efc68e97ea392843458a2dd9d6dbd156" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/70ba0627efc68e97ea392843458a2dd9d6dbd156", - "reference": "70ba0627efc68e97ea392843458a2dd9d6dbd156", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "provide": { - "ext-deepclone": "*" - }, - "suggest": { - "ext-deepclone": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\DeepClone\\": "" - }, - "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 the deepclone extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "deepclone", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-deepclone/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-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-normalizer", - "version": "v1.38.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "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.38.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-25T13:48:31+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-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/process", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "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.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/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/var-dumper", - "version": "v8.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "865103cf742a039f34645b971fc3ace308d6c167" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/865103cf742a039f34645b971fc3ace308d6c167", - "reference": "865103cf742a039f34645b971fc3ace308d6c167", - "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.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-22T15:42:13+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v8.1.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "802362f41128c430fd6685491e1fb72127fae78a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/802362f41128c430fd6685491e1fb72127fae78a", - "reference": "802362f41128c430fd6685491e1fb72127fae78a", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-deepclone": "^1.40" - }, - "require-dev": { - "symfony/property-access": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "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 tools to export, instantiate, hydrate, clone and lazy-load PHP objects", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "deep-clone", - "export", - "hydrate", - "instantiate", - "lazy-loading", - "proxy", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/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-29T06:30:03+00:00" - }, - { - "name": "thecodingmachine/safe", - "version": "v3.4.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", - "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^10", - "squizlabs/php_codesniffer": "^3.2" - }, - "type": "library", - "autoload": { - "files": [ - "lib/special_cases.php", - "generated/apache.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/calendar.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gettext.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/mysql.php", - "generated/mysqli.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rnp.php", - "generated/rpminfo.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssdeep.php", - "generated/ssh2.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php" - ], - "classmap": [ - "lib/DateTime.php", - "lib/DateTimeImmutable.php", - "lib/Exceptions/", - "generated/Exceptions/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "support": { - "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" - }, - "funding": [ - { - "url": "https://github.com/OskarStark", - "type": "github" - }, - { - "url": "https://github.com/shish", - "type": "github" - }, - { - "url": "https://github.com/silasjoisten", - "type": "github" - }, - { - "url": "https://github.com/staabm", - "type": "github" - } - ], - "time": "2026-02-04T18:08:13+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-11-17T20:03:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", - "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-date": "*", - "ext-filter": "*", - "php": "^8.2" - }, - "suggest": { - "ext-intl": "", - "ext-simplexml": "", - "ext-spl": "" - }, - "type": "library", - "extra": { - "psalm": { - "pluginClass": "Webmozart\\Assert\\PsalmPlugin" - }, - "branch-alias": { - "dev-master": "2.0-dev", - "dev-feature/2-0": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - }, - { - "name": "Woody Gilk", - "email": "woody.gilk@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.4.1" - }, - "time": "2026-06-15T15:31:57+00:00" - }, - { - "name": "webmozart/glob", - "version": "4.7.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/glob.git", - "reference": "8a2842112d6916e61e0e15e316465b611f3abc17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/glob/zipball/8a2842112d6916e61e0e15e316465b611f3abc17", - "reference": "8a2842112d6916e61e0e15e316465b611f3abc17", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "symfony/filesystem": "^5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Glob\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "A PHP implementation of Ant's glob.", - "support": { - "issues": "https://github.com/webmozarts/glob/issues", - "source": "https://github.com/webmozarts/glob/tree/4.7.0" - }, - "time": "2024-03-07T20:33:40+00:00" - }, - { - "name": "wnx/commonmark-markdown-renderer", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/stefanzweifel/commonmark-markdown-renderer.git", - "reference": "3a283076abd1a1ed043940f9be43cd35470cd0d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/stefanzweifel/commonmark-markdown-renderer/zipball/3a283076abd1a1ed043940f9be43cd35470cd0d4", - "reference": "3a283076abd1a1ed043940f9be43cd35470cd0d4", - "shasum": "" - }, - "require": { - "league/commonmark": "^2.0", - "php": "^8.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.0", - "rector/rector": "^2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Wnx\\CommonmarkMarkdownRenderer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Stefan Zweifel", - "email": "stefan@stefanzweifel.dev", - "role": "Developer" - } - ], - "description": "Render Markdown AST back to Markdown.", - "homepage": "https://github.com/stefanzweifel/commonmark-markdown-renderer", - "keywords": [ - "commonmark-markdown-renderer", - "markdown", - "renderer", - "wnx" - ], - "support": { - "issues": "https://github.com/stefanzweifel/commonmark-markdown-renderer/issues", - "source": "https://github.com/stefanzweifel/commonmark-markdown-renderer/tree/v1.6.0" - }, - "funding": [ - { - "url": "https://github.com/stefanzweifel", - "type": "github" - } - ], - "time": "2025-11-23T20:14:14+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "ext-openssl": "*" - }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" -} diff --git a/docs/extensions.md b/docs/extensions.md index 9cd0f7f1..1690d21c 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -23,7 +23,7 @@ $hydrator = (new StackHydratorBuilder()) ``` ## Built-in extensions -The library ships with four extensions out of the box: +The library ships with five extensions out of the box: | Extension | Purpose | | --- | --- | @@ -31,6 +31,7 @@ The library ships with four extensions out of the box: | `LifecycleExtension` | [Lifecycle hooks](lifecycle-hooks.md), run code before and after the extract and hydrate process. | | `CryptographyExtension` | [Cryptography](cryptography.md), encrypt and decrypt sensitive data with crypto-shredding. | | `UpcastExtension` | [Upcasting](upcasting.md), reshape outdated stored data while it is hydrated. | +| `GeneratedMiddlewareExtension` | [Generated code](generated-code.md), generated mapping code for a fixed set of classes on top of the `CoreExtension`. | ## Middleware @@ -175,4 +176,5 @@ final class AuditExtension implements Extension * [How to run code before extract and after hydrate](lifecycle-hooks.md) * [How to encrypt sensitive data](cryptography.md) * [How to reshape outdated stored data](upcasting.md) +* [How to speed up hydration with generated code](generated-code.md) * [How to cache the metadata](caching.md) diff --git a/docs/generated-code.md b/docs/generated-code.md new file mode 100644 index 00000000..f8ce15da --- /dev/null +++ b/docs/generated-code.md @@ -0,0 +1,103 @@ +# Generated Code + +The `TransformMiddleware` maps arrays to objects with reflection: it iterates +over the metadata of a class, sets each property through a `ReflectionProperty` +and calls the [normalizers](normalizer.md) dynamically. This is fast enough for +most applications, but when you hydrate millions of objects the reflection +overhead adds up. The `GeneratedMiddlewareExtension` generates plain PHP code +for a fixed set of classes, which is then used instead of the reflection based +mapping for exactly these classes. + +## Enable the extension + +Register the `GeneratedMiddlewareExtension` in addition to the `CoreExtension`. +It needs a writable directory for the generated file and the list of classes +for which code should be generated. Classes referenced by these classes, such +as nested value objects, are discovered automatically. + +```php +use Patchlevel\Hydrator\CoreExtension; +use Patchlevel\Hydrator\Extension\Generated\GeneratedMiddlewareExtension; +use Patchlevel\Hydrator\StackHydratorBuilder; + +$hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new GeneratedMiddlewareExtension( + cachePath: __DIR__ . '/var/cache/hydrator', + classes: [ + ProfileCreated::class, + NameChanged::class, + ], + )) + ->build(); +``` +:::note +The builder returns a `GeneratedHydrator` instead of the plain `StackHydrator`. +It is a `StackHydrator` in every respect, but for the generated classes it +calls the generated code directly and skips the middleware stack entirely, as +long as no other middleware has to run for the class (see +[Nested objects](#nested-objects) for how that is decided). Everything else +takes the regular path. +::: + +:::success +The generated code behaves exactly like the `TransformMiddleware`: objects are +created without calling the constructor, missing fields keep their promoted +default value, private and readonly properties are supported and the same +exceptions are thrown. You can switch between both without changing your +classes. +::: + +The generated middleware runs right before the `TransformMiddleware` of the +`CoreExtension`, regardless of the registration order. Classes which are not +part of the list are passed on and handled by the `TransformMiddleware` as +usual. + +## Caching + +The generated middleware is written to the cache path once and reused on +subsequent requests, so the generation only happens on the first run. The file +name is derived from the configuration, changing the class list or the options +results in a new file. In development you can regenerate the code on every +build with the `debug` flag. + +```php +use Patchlevel\Hydrator\Extension\Generated\GeneratedMiddlewareExtension; + +$extension = new GeneratedMiddlewareExtension( + cachePath: __DIR__ . '/var/cache/hydrator', + classes: [ProfileCreated::class], + debug: true, +); +``` +:::warning +The generated code is tied to the [metadata](caching.md) of your classes. If a +field is renamed or a normalizer is added after the code was generated, the +middleware throws an `OutdatedGeneratedMiddleware` exception. Clear the cache +path when you deploy, like you do for other generated files. +::: + +## Nested objects + +The biggest speedup comes from inlining nested objects: instead of going through +the whole middleware stack again for every nested object, the generated code +hydrates and extracts them directly. Inlining a class would hide it from the +other middlewares, so it only happens when every other middleware in the stack +is a `SkippableMiddleware` which [skips](extensions.md) that class. The decision +is made per class and per direction: with the +[cryptography](cryptography.md) extension for example, value objects without +sensitive data are inlined while classes with sensitive data still go through +the stack. + +:::note +Nested classes with a class level normalizer or [lazy](lazy.md) classes are +never inlined, they always take the regular path through the hydrator. The +generated middleware is itself skippable: classes it has no code for go +straight to the `TransformMiddleware`. +::: + +## Learn more + +* [How to build the hydrator](hydrator.md) +* [How to cache the metadata](caching.md) +* [How to write your own extension](extensions.md) diff --git a/docs/hydrator.md b/docs/hydrator.md index 8cf62e48..0211ad18 100644 --- a/docs/hydrator.md +++ b/docs/hydrator.md @@ -183,3 +183,4 @@ value does not fit the property type. * [How normalizers convert complex types](normalizer.md) * [How to hydrate objects lazily](lazy.md) * [How to hook into the hydration process](extensions.md) +* [How to speed up hydration with generated code](generated-code.md) diff --git a/docs/project.json b/docs/project.json index 5fae563a..c7103e75 100644 --- a/docs/project.json +++ b/docs/project.json @@ -18,7 +18,8 @@ "subEntries": [ { "title": "Lifecycle Hooks", "file": "lifecycle-hooks.md" }, { "title": "Cryptography", "file": "cryptography.md" }, - { "title": "Upcasting", "file": "upcasting.md" } + { "title": "Upcasting", "file": "upcasting.md" }, + { "title": "Generated Code", "file": "generated-code.md" } ] }, { diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6998ee63..75142e1f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,6 +10,8 @@ src tests + + tests/Unit/Extension/Generated/Snapshot/* diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 81e9ded9..7bb272f1 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,6 +8,8 @@ parameters: paths: - src - tests + excludePaths: + - tests/Unit/Extension/Generated/Snapshot reportUnmatchedIgnoredErrors: false services: diff --git a/src/Extension/Generated/ClassNotGeneratable.php b/src/Extension/Generated/ClassNotGeneratable.php new file mode 100644 index 00000000..bff6fb12 --- /dev/null +++ b/src/Extension/Generated/ClassNotGeneratable.php @@ -0,0 +1,18 @@ + $properties + * @param bool $scopedHydrate hydrate code must run in the scope of the class + * @param bool $scopedExtract extract code must run in the scope of the class + */ + public function __construct( + public readonly int $index, + public readonly ClassMetadata $metadata, + public readonly array $properties, + public readonly bool $scopedHydrate, + public readonly bool $scopedExtract, + ) { + } + + /** @return class-string */ + public function className(): string + { + return $this->metadata->className; + } + + /** Classes without normalizers can never be part of a circular reference. */ + public function leaf(): bool + { + foreach ($this->properties as $property) { + if ($property->kind !== ValueKind::Raw) { + return false; + } + } + + return true; + } +} diff --git a/src/Extension/Generated/ClassPlanner.php b/src/Extension/Generated/ClassPlanner.php new file mode 100644 index 00000000..888e8d17 --- /dev/null +++ b/src/Extension/Generated/ClassPlanner.php @@ -0,0 +1,304 @@ + class => index, null for classes which can not be part of the middleware */ + private array $index = []; + + /** @var array */ + private array $classes = []; + + private int $nextIndex = 0; + private int $normalizerSlots = 0; + private int $flagSlots = 0; + private int $defaultSlots = 0; + + public function __construct( + private readonly MetadataFactory $metadataFactory, + ) { + } + + /** + * @param class-string $class + * + * @throws ClassNotGeneratable + */ + public function add(string $class): void + { + $class = ltrim($class, '\\'); + + try { + $metadata = $this->metadataFactory->metadata($class); + } catch (ClassNotFound) { + throw new ClassNotGeneratable($class, 'class not found'); + } + + if ($metadata->normalizer !== null) { + // handled by the hydrator itself, never reaches a middleware + return; + } + + if (!self::generatable($metadata)) { + throw new ClassNotGeneratable($class, 'the class is internal, abstract or an interface'); + } + + $this->register($metadata); + } + + /** @return list ordered by index */ + public function plans(): array + { + ksort($this->classes); + + return array_values($this->classes); + } + + /** @return int index of the registered class */ + private function register(ClassMetadata $metadata): int + { + $class = $metadata->className; + $index = $this->index[$class] ?? null; + + if ($index !== null) { + return $index; + } + + $index = $this->nextIndex++; + // reserve the index before planning the properties: nested classes may reference this class again + $this->index[$class] = $index; + + $properties = []; + + foreach ($metadata->properties as $property) { + $properties[] = $this->plan($metadata, $property); + } + + [$scopedHydrate, $scopedExtract] = self::scoped($metadata, $properties); + + $this->classes[$index] = new ClassPlan($index, $metadata, $properties, $scopedHydrate, $scopedExtract); + + return $index; + } + + private function plan(ClassMetadata $metadata, PropertyMetadata $property): PropertyPlan + { + $reflection = $property->reflection; + $declaringClass = $reflection->getDeclaringClass()->getName(); + $inherited = $declaringClass !== $metadata->className; + + $kind = ValueKind::Raw; + $slot = null; + $inner = null; + $flag = null; + $nested = null; + $default = null; + $defaultSlot = null; + + $normalizer = $property->normalizer; + + if ($normalizer !== null) { + $kind = ValueKind::Normalizer; + $slot = $this->normalizerSlots++; + $nested = $this->nestedClass($normalizer); + + if ($nested !== null) { + $kind = ValueKind::NestedObject; + } elseif ($normalizer instanceof ArrayNormalizer) { + $nested = $this->nestedClass($normalizer->innerNormalizer()); + + if ($nested !== null) { + $kind = ValueKind::NestedArray; + $inner = $this->normalizerSlots++; + } + } + + if ($nested !== null) { + $flag = $this->flagSlots++; + } + } + + if ($reflection->isPromoted()) { + $parameter = $metadata->promotedConstructorDefaults()[$property->propertyName] ?? null; + + if ($parameter !== null) { + // scalar defaults are inlined, everything else is resolved via reflection at runtime + $default = self::literal($parameter->getDefaultValue()); + + if ($default === null) { + $defaultSlot = $this->defaultSlots++; + } + } + } + + return new PropertyPlan( + name: $property->propertyName, + field: $property->fieldName, + kind: $kind, + slot: $slot, + inner: $inner, + flag: $flag, + nested: $nested, + default: $default, + defaultSlot: $defaultSlot, + hydrateScope: $inherited && ($reflection->isPrivate() || $reflection->isReadOnly() || self::privateSet($reflection)) ? $declaringClass : null, + extractScope: $inherited && $reflection->isPrivate() ? $declaringClass : null, + ); + } + + /** @return int|null the index of the nested class if the normalizer is an object normalizer for an inlinable class */ + private function nestedClass(Normalizer $normalizer): int|null + { + if (!$normalizer instanceof ObjectNormalizer) { + return null; + } + + try { + $class = $normalizer->className(); + } catch (InvalidType) { + return null; + } + + return $this->nestedIndex($class); + } + + /** + * Nested classes can only be inlined if they would end up in this middleware anyway. + * + * @param class-string $class + */ + private function nestedIndex(string $class): int|null + { + if (array_key_exists($class, $this->index)) { + return $this->index[$class]; + } + + try { + $metadata = $this->metadataFactory->metadata($class); + } catch (ClassNotFound) { + return $this->index[$class] = null; + } + + if ($metadata->normalizer !== null || $metadata->lazy === true || !self::generatable($metadata)) { + return $this->index[$class] = null; + } + + return $this->register($metadata); + } + + /** + * Code runs in the scope of the class (a bound closure) only if needed, otherwise plain methods are used. + * + * @param list $properties + * + * @return array{bool, bool} [hydrate needs the class scope, extract needs the class scope] + */ + private static function scoped(ClassMetadata $metadata, array $properties): array + { + $hydrate = false; + $extract = false; + + foreach ($properties as $property) { + if ($property->hydrateScope !== null || $property->extractScope !== null) { + // handled by helper closures bound to the declaring class + continue; + } + + $reflection = $metadata->properties[$property->name]->reflection; + + if (!$reflection->isPublic()) { + $hydrate = true; + $extract = true; + } elseif (self::restrictedSet($reflection)) { + $hydrate = true; + } + } + + return [$hydrate, $extract]; + } + + /** Constructor visibility does not matter, the generated code runs in the scope of the class. */ + private static function generatable(ClassMetadata $metadata): bool + { + $reflection = $metadata->reflection; + + return !$reflection->isInternal() && !$reflection->isAbstract() && !$reflection->isInterface() && !$reflection->isEnum(); + } + + /** Readonly and asymmetric visibility restrict writes to the class scope. */ + private static function restrictedSet(ReflectionProperty $reflection): bool + { + return $reflection->isReadOnly() || self::privateSet($reflection) || (PHP_VERSION_ID >= 80400 && $reflection->isProtectedSet()); + } + + private static function privateSet(ReflectionProperty $reflection): bool + { + return PHP_VERSION_ID >= 80400 && $reflection->isPrivateSet(); + } + + /** @return string|null php expression for the value, null if it can not be expressed as a literal */ + private static function literal(mixed $value): string|null + { + if ($value === null || is_bool($value) || is_int($value) || is_float($value) || is_string($value)) { + return var_export($value, true); + } + + if ($value instanceof UnitEnum) { + return sprintf('\\%s::%s', $value::class, $value->name); + } + + if (is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $literal = self::literal($item); + + if ($literal === null) { + return null; + } + + $items[] = sprintf('%s => %s', var_export($key, true), $literal); + } + + return '[' . implode(', ', $items) . ']'; + } + + return null; + } +} diff --git a/src/Extension/Generated/CodeEmitter.php b/src/Extension/Generated/CodeEmitter.php new file mode 100644 index 00000000..8e51b6ca --- /dev/null +++ b/src/Extension/Generated/CodeEmitter.php @@ -0,0 +1,523 @@ + property declarations of the generated class */ + private array $properties = []; + + /** @var list init methods, one per class */ + private array $inits = []; + + /** @var list hydrate/extract methods which do not need the scope of a class */ + private array $methods = []; + + private int $helperSlots = 0; + + /** @param list $classes indexed by {@see ClassPlan::$index} */ + public function __construct( + private readonly array $classes, + ) { + } + + public function emit(string $namespace, string $className): string + { + $constructor = []; + $compiledHydrateCases = []; + $compiledExtractCases = []; + $skipCases = []; + $hydrateCases = []; + $extractCases = []; + + foreach ($this->classes as $plan) { + $class = $plan->className(); + $index = $plan->index; + + // until the class is initialized, the closures point to a stub which initializes the class first + $constructor[] = sprintf( + '$this->h%1$d = function (array $data, array $context, object|null $object = null): object { $this->init%1$d(); return ($this->h%1$d)($data, $context, $object); };', + $index, + ); + $constructor[] = sprintf( + '$this->e%1$d = function (object $object, array $context): array { $this->init%1$d(); return ($this->e%1$d)($object, $context); };', + $index, + ); + + $hydrateCall = $plan->scopedHydrate ? sprintf('($this->h%d)', $index) : sprintf('$this->hydrate%d', $index); + $extractCall = $plan->scopedExtract ? sprintf('$this->e%d', $index) : sprintf('$this->extract%d(...)', $index); + $compiledHydrateCases[] = Templates::render(Templates::COMPILED_CASE, ['class' => $class, 'index' => (string)$index, 'closure' => sprintf('$this->fastHydrate%d(...)', $index)]); + $compiledExtractCases[] = Templates::render(Templates::COMPILED_CASE, ['class' => $class, 'index' => (string)$index, 'closure' => $extractCall]); + $this->methods[] = Templates::render(Templates::FAST_HYDRATE, ['index' => (string)$index, 'call' => $hydrateCall]); + + $skipCases[] = sprintf('\\%s::class,', $class); + $hydrateCases[] = $plan->scopedHydrate + ? sprintf('\\%s::class => ($this->h%d)($data, $populate, $object),', $class, $index) + : sprintf('\\%1$s::class => $this->ready%2$d ? $this->hydrate%2$d($data, $populate, $object) : ($this->h%2$d)($data, $populate, $object),', $class, $index); + $extractCases[] = $plan->scopedExtract + ? sprintf('\\%s::class => ($this->e%d)($object, $context),', $class, $index) + : sprintf('\\%1$s::class => $this->ready%2$d ? $this->extract%2$d($object, $context) : ($this->e%2$d)($object, $context),', $class, $index); + + $this->emitClass($plan); + } + + return Templates::render(Templates::MIDDLEWARE, [ + 'namespace' => $namespace, + 'className' => $className, + 'version' => (string)MiddlewareGenerator::VERSION, + 'properties' => implode("\n", $this->properties), + 'constructor' => implode("\n", $constructor), + 'skipCases' => implode("\n", $skipCases), + 'compiledHydrateCases' => implode("\n", $compiledHydrateCases), + 'compiledExtractCases' => implode("\n", $compiledExtractCases), + 'hydrateCases' => implode("\n", $hydrateCases), + 'extractCases' => implode("\n", $extractCases), + 'inits' => implode("\n\n", $this->inits), + 'methods' => implode("\n\n", $this->methods), + ]); + } + + /** Emits the properties, the init method and the hydrate/extract code of one class. */ + private function emitClass(ClassPlan $plan): void + { + $class = $plan->className(); + $index = $plan->index; + + $this->declareProperties($plan); + + $init = [sprintf('if ($this->ready%1$d) { return; }', $index), sprintf('$this->ready%d = true;', $index)]; + $expected = []; + $nestedClasses = []; + + foreach ($plan->properties as $property) { + $expected[] = sprintf( + '%s => [%s, %s]', + var_export($property->name, true), + var_export($property->field, true), + $property->kind === ValueKind::Raw ? 'false' : 'true', + ); + } + + $init[] = sprintf('$metadata = $this->metadata(\\%s::class, [%s]);', $class, implode(', ', $expected)); + $init[] = sprintf('$this->r%d = $metadata->reflection;', $index); + + foreach ($plan->properties as $property) { + if ($property->defaultSlot !== null) { + $init[] = sprintf('$this->d%d = $this->promotedDefault($metadata, %s);', $property->defaultSlot, var_export($property->name, true)); + } + + if ($property->slot === null) { + continue; + } + + $init[] = sprintf('$this->n%d = $this->normalizer($metadata, %s);', $property->slot, var_export($property->name, true)); + + if ($property->nested === null) { + continue; + } + + $nested = $this->nested($property)->className(); + $nestedClasses[$property->nested] = true; + + $check = $property->kind === ValueKind::NestedObject ? 'inlineObject' : 'inlineArray'; + $init[] = sprintf('$this->ih%d = $this->%s($this->n%d, \\%s::class, Skip::Hydrate);', $property->flag, $check, $property->slot, $nested); + $init[] = sprintf('$this->ie%d = $this->%s($this->n%d, \\%s::class, Skip::Extract);', $property->flag, $check, $property->slot, $nested); + + if ($property->kind !== ValueKind::NestedArray) { + continue; + } + + $init[] = sprintf('if ($this->ih%1$d || $this->ie%1$d) { $this->n%2$d = $this->n%3$d->innerNormalizer(); }', $property->flag, $property->inner, $property->slot); + } + + [$hydrate, $hydrateHelpers] = $this->hydrateBody($plan); + [$extract, $extractHelpers] = $this->extractBody($plan); + + foreach ($hydrateHelpers as $helper) { + $init[] = $helper; + } + + $init[] = $this->wrap($plan, 'h', 'hydrate', 'array $data, array $context, object|null $object = null', 'object', $hydrate, $plan->scopedHydrate); + + foreach ($extractHelpers as $helper) { + $init[] = $helper; + } + + $init[] = $this->wrap($plan, 'e', 'extract', 'object $object, array $context', 'array', $extract, $plan->scopedExtract); + + foreach (array_keys($nestedClasses) as $nested) { + $init[] = sprintf('$this->init%d();', $nested); + } + + if (!$plan->leaf()) { + $init[] = sprintf('$this->tracked%d = $this->recursive%d();', $index, $index); + $this->properties[] = sprintf('public bool $tracked%d = true;', $index); + $this->methods[] = $this->recursiveMethod($plan); + } + + $this->inits[] = Templates::render(Templates::INIT, [ + 'index' => (string)$index, + 'body' => implode("\n", $init), + ]); + } + + private function declareProperties(ClassPlan $plan): void + { + foreach ($plan->properties as $property) { + if ($property->slot !== null) { + $this->properties[] = sprintf('public Normalizer $n%d;', $property->slot); + } + + if ($property->inner !== null) { + $this->properties[] = sprintf('public ObjectNormalizer $n%d;', $property->inner); + } + + if ($property->flag !== null) { + $this->properties[] = sprintf('public bool $ih%d = false;', $property->flag); + $this->properties[] = sprintf('public bool $ie%d = false;', $property->flag); + } + + if ($property->defaultSlot === null) { + continue; + } + + $this->properties[] = sprintf('public ReflectionParameter $d%d;', $property->defaultSlot); + } + + $index = $plan->index; + + $this->properties[] = sprintf('public bool $ready%d = false;', $index); + $this->properties[] = sprintf('public bool|null $recursive%d = null;', $index); + $this->properties[] = sprintf('public Closure $h%d;', $index); + $this->properties[] = sprintf('public Closure $e%d;', $index); + $this->properties[] = sprintf('public ReflectionClass $r%d;', $index); + } + + /** + * Circular references are only possible through normalizers which call the hydrator again. A class whose + * normalizers can not reach the class itself again does not need the (expensive) call stack tracking. + * Unknown paths and cycles are conservatively treated as recursive. + */ + private function recursiveMethod(ClassPlan $plan): string + { + $checks = []; + + foreach ($plan->properties as $property) { + if ($property->kind === ValueKind::Raw) { + continue; + } + + if ($property->nested === null) { + $checks[] = sprintf('$this->n%d instanceof HydratorAwareNormalizer', $property->slot); + + continue; + } + + $nested = $this->nested($property); + $recursive = $nested->leaf() ? 'false' : sprintf('$this->recursive%d()', $nested->index); + $checks[] = sprintf('($this->ie%d ? %s : $this->n%d instanceof HydratorAwareNormalizer)', $property->flag, $recursive, $property->slot); + } + + return Templates::render(Templates::RECURSIVE, [ + 'index' => (string)$plan->index, + 'checks' => implode("\n || ", $checks), + ]); + } + + /** + * Emits the body either as closure bound to the class scope (assigned in init) or as a plain method. + * + * @return string init code which assigns the closure + */ + private function wrap(ClassPlan $plan, string $slot, string $name, string $signature, string $return, string $body, bool $scoped): string + { + $index = $plan->index; + + if ($scoped) { + return Templates::render(Templates::CLOSURE, [ + 'slot' => $slot . $index, + 'signature' => $signature, + 'return' => $return, + 'body' => $body, + 'class' => $plan->className(), + ]); + } + + $this->methods[] = Templates::render(Templates::METHOD, [ + 'name' => $name . $index, + 'signature' => $signature, + 'return' => $return, + 'body' => $body, + ]); + + return sprintf('$this->%s%d = $this->%s%d(...);', $slot, $index, $name, $index); + } + + /** + * Generates the hydrate code, properties which need another scope are assigned in a helper closure. + * + * @return array{string, list} body and init code of the helper closures + */ + private function hydrateBody(ClassPlan $plan): array + { + $main = []; + $scoped = []; + + foreach ($plan->properties as $property) { + if ($property->hydrateScope !== null) { + $scoped[$property->hydrateScope][] = $this->assignment($plan, $property); + + continue; + } + + $main[] = $this->assignment($plan, $property); + } + + $helpers = []; + + foreach ($scoped as $scope => $code) { + $slot = $this->helperSlots++; + $this->properties[] = sprintf('public Closure $hp%d;', $slot); + $helpers[] = Templates::render(Templates::HYDRATE_HELPER, [ + 'slot' => (string)$slot, + 'scope' => $scope, + 'body' => implode("\n", $code), + ]); + $main[] = sprintf('($this->hp%d)($object, $data, $context);', $slot); + } + + $body = [sprintf('$object ??= $this->r%d->newInstanceWithoutConstructor();', $plan->index)]; + $body[] = implode("\n\n", $main); + $body[] = 'return $object;'; + + return [ + implode("\n\n", array_filter($body, static fn (string $code): bool => $code !== '')), + $helpers, + ]; + } + + private function assignment(ClassPlan $plan, PropertyPlan $property): string + { + $template = $property->hasDefault() ? Templates::ASSIGN_DEFAULT : Templates::ASSIGN; + + return Templates::render($template, [ + 'field' => var_export($property->field, true), + 'name' => $property->name, + 'property' => var_export($property->name, true), + 'class' => $plan->className(), + 'value' => $this->denormalize($plan, $property, '$value'), + 'default' => $property->default ?? sprintf('$this->d%d->getDefaultValue()', $property->defaultSlot), + ]); + } + + /** Code which denormalizes the field into the given variable. */ + private function denormalize(ClassPlan $plan, PropertyPlan $property, string $variable): string + { + $field = var_export($property->field, true); + + if ($property->kind === ValueKind::Raw) { + return sprintf('%s = $data[%s];', $variable, $field); + } + + $inner = match ($property->kind) { + ValueKind::Normalizer => sprintf('%s = $this->n%d->denormalize($data[%s], $context);', $variable, $property->slot, $field), + ValueKind::NestedObject => Templates::render(Templates::DENORMALIZE_OBJECT, [ + 'variable' => $variable, + 'field' => $field, + 'flag' => (string)$property->flag, + 'slot' => (string)$property->slot, + 'nested' => $this->nestedHydrateCall($property), + ]), + ValueKind::NestedArray => Templates::render(Templates::DENORMALIZE_ARRAY, [ + 'variable' => $variable, + 'field' => $field, + 'flag' => (string)$property->flag, + 'slot' => (string)$property->slot, + 'inner' => (string)$property->inner, + 'nested' => $this->nestedHydrateCall($property), + ]), + }; + + return Templates::render(Templates::DENORMALIZE, [ + 'inner' => $inner, + 'class' => $plan->className(), + 'property' => var_export($property->name, true), + 'slot' => (string)$property->slot, + ]); + } + + /** + * Generates the extract code, properties which need another scope are read in a helper closure. + * + * @return array{string, list} body and init code of the helper closures + */ + private function extractBody(ClassPlan $plan): array + { + $pre = []; + $scoped = []; + $fields = []; + + foreach ($plan->properties as $property) { + $field = var_export($property->field, true); + [$code, $expression] = $this->normalize($plan, $property); + + if ($property->extractScope !== null) { + $scoped[$property->extractScope][] = [$property->name, $code, $expression]; + $fields[] = sprintf('%s => $v_%s,', $field, $property->name); + + continue; + } + + if ($code !== '') { + $pre[] = $code; + } + + $fields[] = sprintf('%s => %s,', $field, $expression); + } + + $helpers = []; + + foreach ($scoped as $scope => $items) { + $slot = $this->helperSlots++; + $this->properties[] = sprintf('public Closure $ep%d;', $slot); + + $body = []; + $values = []; + $variables = []; + + foreach ($items as [$name, $code, $expression]) { + if ($code !== '') { + $body[] = $code; + } + + $values[] = $expression; + $variables[] = sprintf('$v_%s', $name); + } + + $body[] = sprintf('return [%s];', implode(', ', $values)); + + $helpers[] = Templates::render(Templates::EXTRACT_HELPER, [ + 'slot' => (string)$slot, + 'scope' => $scope, + 'body' => implode("\n", $body), + ]); + + array_unshift($pre, sprintf('[%s] = ($this->ep%d)($object, $context);', implode(', ', $variables), $slot)); + } + + $variables = [ + 'index' => (string)$plan->index, + 'class' => $plan->className(), + 'pre' => implode("\n\n", $pre), + 'fields' => implode("\n", $fields), + ]; + + if ($plan->leaf()) { + return [Templates::render(Templates::EXTRACT_PLAIN, $variables), $helpers]; + } + + return [ + Templates::render(Templates::EXTRACT_TRACKED, $variables) . "\n\n" . Templates::render(Templates::EXTRACT_PLAIN, $variables), + $helpers, + ]; + } + + /** @return array{string, string} preparing code and the expression for the normalized value */ + private function normalize(ClassPlan $plan, PropertyPlan $property): array + { + $name = $property->name; + + if ($property->kind === ValueKind::Raw) { + return ['', sprintf('$object->%s', $name)]; + } + + $variable = sprintf('$v_%s', $name); + + $inner = match ($property->kind) { + ValueKind::Normalizer => sprintf('%s = $this->n%d->normalize($object->%s, $context);', $variable, $property->slot, $name), + ValueKind::NestedObject => Templates::render(Templates::NORMALIZE_OBJECT, [ + 'variable' => $variable, + 'name' => $name, + 'flag' => (string)$property->flag, + 'slot' => (string)$property->slot, + 'nested' => $this->nestedExtractCall($property), + 'check' => $this->instanceCheck('$value', $property), + ]), + ValueKind::NestedArray => Templates::render(Templates::NORMALIZE_ARRAY, [ + 'variable' => $variable, + 'name' => $name, + 'flag' => (string)$property->flag, + 'slot' => (string)$property->slot, + 'inner' => (string)$property->inner, + 'nested' => $this->nestedExtractCall($property), + 'check' => $this->instanceCheck('$item', $property), + ]), + }; + + $code = Templates::render(Templates::NORMALIZE, [ + 'inner' => $inner, + 'class' => $plan->className(), + 'property' => var_export($name, true), + 'slot' => (string)$property->slot, + ]); + + return [$code, $variable]; + } + + /** Only exact instances are inlined, subclasses take the generic path through the hydrator. */ + private function instanceCheck(string $variable, PropertyPlan $property): string + { + $nested = $this->nested($property); + $class = $nested->className(); + + if ($nested->metadata->reflection->isFinal()) { + return sprintf('%s instanceof \\%s', $variable, $class); + } + + return sprintf('%1$s instanceof \\%2$s && %1$s::class === \\%2$s::class', $variable, $class); + } + + private function nestedHydrateCall(PropertyPlan $property): string + { + $nested = $this->nested($property); + + return $nested->scopedHydrate ? sprintf('($this->h%d)', $nested->index) : sprintf('$this->hydrate%d', $nested->index); + } + + private function nestedExtractCall(PropertyPlan $property): string + { + $nested = $this->nested($property); + + return $nested->scopedExtract ? sprintf('($this->e%d)', $nested->index) : sprintf('$this->extract%d', $nested->index); + } + + private function nested(PropertyPlan $property): ClassPlan + { + assert(is_int($property->nested)); + + return $this->classes[$property->nested]; + } +} diff --git a/src/Extension/Generated/GeneratedHydrator.php b/src/Extension/Generated/GeneratedHydrator.php new file mode 100644 index 00000000..81054838 --- /dev/null +++ b/src/Extension/Generated/GeneratedHydrator.php @@ -0,0 +1,124 @@ + false if the class has no compiled hydrator */ + private array $hydrators = []; + + /** @var array false if the class has no compiled extractor */ + private array $extractors = []; + + /** @var list|null */ + private array|null $generated = null; + + /** + * @param class-string $class + * @param array $context + * + * @return T + * + * @template T of object + */ + public function hydrate(string $class, mixed $data, array $context = []): object + { + $hydrator = $this->hydrators[$class] ?? $this->compileHydrator($class); + + if ($hydrator !== false && is_array($data)) { + $object = $hydrator($data, $context); + assert($object instanceof $class); + + return $object; + } + + return parent::hydrate($class, $data, $context); + } + + /** @param array $context */ + public function extract(object $object, array $context = []): mixed + { + $extractor = $this->extractors[$object::class] ?? $this->compileExtractor($object::class); + + if ($extractor !== false) { + return $extractor($object, $context); + } + + return parent::extract($object, $context); + } + + /** + * Decided once per class: the class must not use a class normalizer or a lazy proxy, and a generated + * middleware must handle it exclusively. Everything else is left to the stack. + * + * @param class-string $class + */ + private function compileHydrator(string $class): Closure|false + { + try { + $metadata = $this->metadata($class); + } catch (ClassNotFound) { + return $this->hydrators[$class] = false; + } + + if ($metadata->normalizer !== null || (PHP_VERSION_ID >= 80400 && ($metadata->lazy ?? $this->defaultLazy()))) { + return $this->hydrators[$class] = false; + } + + foreach ($this->generated() as $middleware) { + $hydrator = $middleware->compiledHydrator($metadata); + + if ($hydrator !== null) { + return $this->hydrators[$class] = $hydrator; + } + } + + return $this->hydrators[$class] = false; + } + + /** @param class-string $class */ + private function compileExtractor(string $class): Closure|false + { + $metadata = $this->metadata($class); + + if ($metadata->normalizer !== null) { + return $this->extractors[$class] = false; + } + + foreach ($this->generated() as $middleware) { + $extractor = $middleware->compiledExtractor($metadata); + + if ($extractor !== null) { + return $this->extractors[$class] = $extractor; + } + } + + return $this->extractors[$class] = false; + } + + /** @return list */ + private function generated(): array + { + return $this->generated ??= array_values(array_filter( + $this->middlewares(), + static fn ($middleware): bool => $middleware instanceof GeneratedMiddleware, + )); + } +} diff --git a/src/Extension/Generated/GeneratedMiddleware.php b/src/Extension/Generated/GeneratedMiddleware.php new file mode 100644 index 00000000..700845bb --- /dev/null +++ b/src/Extension/Generated/GeneratedMiddleware.php @@ -0,0 +1,36 @@ + $metadata + * + * @return (Closure(array, array): object)|null null if the class is not handled exclusively by this middleware + * + * @template T of object + */ + public function compiledHydrator(ClassMetadata $metadata): Closure|null; + + /** + * @param ClassMetadata $metadata + * + * @return (Closure(object, array): array)|null null if the class is not handled exclusively by this middleware + * + * @template T of object + */ + public function compiledExtractor(ClassMetadata $metadata): Closure|null; +} diff --git a/src/Extension/Generated/GeneratedMiddlewareExtension.php b/src/Extension/Generated/GeneratedMiddlewareExtension.php new file mode 100644 index 00000000..5ad9941f --- /dev/null +++ b/src/Extension/Generated/GeneratedMiddlewareExtension.php @@ -0,0 +1,102 @@ + $classes Classes for which code is generated, nested classes are discovered automatically. + * @param bool $debug Regenerate the middleware on every build. + * @param string|null $className Name of the generated class, derived from the configuration by default. + */ + public function __construct( + private readonly string $cachePath, + private readonly array $classes, + private readonly bool $debug = false, + private readonly string|null $className = null, + ) { + } + + public function configure(StackHydratorBuilder $builder): void + { + $className = $this->className ?? $this->defaultClassName(); + $fqcn = self::NAMESPACE . '\\' . $className; + + if (!class_exists($fqcn, false)) { + $this->load($fqcn, $className, $builder->getMetadataFactory()); + } + + $middleware = new $fqcn(); + assert($middleware instanceof Middleware); + + $builder->addMiddleware($middleware, Extension::PRIORITY_TRANSFORM + 1); + $builder->setHydratorFactory( + static fn (MetadataFactory $metadataFactory, array $middlewares, bool $defaultLazy): StackHydrator => new GeneratedHydrator( + $metadataFactory, + $middlewares, + $defaultLazy, + ), + ); + } + + public function defaultClassName(): string + { + return 'TransformMiddleware_' . substr( + hash('sha256', serialize([MiddlewareGenerator::VERSION, $this->classes])), + 0, + 16, + ); + } + + private function load(string $fqcn, string $className, MetadataFactory $metadataFactory): void + { + $file = sprintf('%s/%s.php', $this->cachePath, $className); + + if ($this->debug || !file_exists($file)) { + $generator = new MiddlewareGenerator($metadataFactory); + $this->write($file, $generator->dump($this->classes, $fqcn)); + } + + require $file; + } + + private function write(string $file, string $code): void + { + if (!is_dir($this->cachePath) && !@mkdir($this->cachePath, 0777, true) && !is_dir($this->cachePath)) { + throw new GeneratedMiddlewareNotWritable($file); + } + + $tmp = $file . '.' . uniqid('', true) . '.tmp'; + + if (@file_put_contents($tmp, $code) === false || !@rename($tmp, $file)) { + @unlink($tmp); + + throw new GeneratedMiddlewareNotWritable($file); + } + } +} diff --git a/src/Extension/Generated/GeneratedMiddlewareNotWritable.php b/src/Extension/Generated/GeneratedMiddlewareNotWritable.php new file mode 100644 index 00000000..8cd52fc7 --- /dev/null +++ b/src/Extension/Generated/GeneratedMiddlewareNotWritable.php @@ -0,0 +1,18 @@ + $classes + * + * @throws ClassNotGeneratable + */ + public function dump(array $classes, string $middlewareFqcn): string + { + $planner = new ClassPlanner($this->metadataFactory); + + foreach ($classes as $class) { + $planner->add($class); + } + + $parts = explode('\\', ltrim($middlewareFqcn, '\\')); + $className = array_pop($parts); + $namespace = implode('\\', $parts); + + return (new CodeEmitter($planner->plans()))->emit($namespace, $className); + } +} diff --git a/src/Extension/Generated/OutdatedGeneratedMiddleware.php b/src/Extension/Generated/OutdatedGeneratedMiddleware.php new file mode 100644 index 00000000..cb524b0d --- /dev/null +++ b/src/Extension/Generated/OutdatedGeneratedMiddleware.php @@ -0,0 +1,18 @@ +default !== null || $this->defaultSlot !== null; + } +} diff --git a/src/Extension/Generated/Templates.php b/src/Extension/Generated/Templates.php new file mode 100644 index 00000000..fd745a21 --- /dev/null +++ b/src/Extension/Generated/Templates.php @@ -0,0 +1,479 @@ + $variables */ + public static function render(string $template, array $variables): string + { + $template = trim($template, "\n"); + + foreach ($variables as $name => $value) { + $template = self::replaceIndented($template, $name, $value); + } + + return $template; + } + + private static function replaceIndented(string $template, string $name, string $value): string + { + $placeholder = '{{' . $name . '}}'; + + $result = preg_replace_callback( + '/^([ \t]*)' . preg_quote($placeholder, '/') . '$/m', + static function (array $matches) use ($value): string { + $lines = explode("\n", $value); + + return implode("\n", array_map( + static fn (string $line): string => $line === '' ? '' : $matches[1] . $line, + $lines, + )); + }, + $template, + ); + + return str_replace($placeholder, $value, $result ?? $template); + } + + public const MIDDLEWARE = <<<'PHP' + all middlewares except this one and the TransformMiddleware behind it */ + public array $others = []; + + /** @var array */ + public array $callStack = []; + + {{properties}} + + public function __construct() + { + {{constructor}} + } + + public function setHydrator(StackHydrator $hydrator): void + { + $this->hydrator = $hydrator; + $this->others = []; + + foreach ($hydrator->middlewares() as $middleware) { + if ($middleware === $this || $middleware instanceof TransformMiddleware) { + continue; + } + + $this->others[] = $middleware; + } + } + + /** Lets the GeneratedHydrator call the generated code directly, skipping the middleware stack. */ + public function compiledHydrator(ClassMetadata $metadata): Closure|null + { + if (!$this->exclusive($metadata, Skip::Hydrate)) { + return null; + } + + switch ($metadata->className) { + {{compiledHydrateCases}} + default: + return null; + } + } + + public function compiledExtractor(ClassMetadata $metadata): Closure|null + { + if (!$this->exclusive($metadata, Skip::Extract)) { + return null; + } + + switch ($metadata->className) { + {{compiledExtractCases}} + default: + return null; + } + } + + /** Classes without generated code are left to the TransformMiddleware directly. */ + public function skip(ClassMetadata $metadata): Skip + { + return match ($metadata->className) { + {{skipCases}} + => Skip::None, + default => Skip::Both, + }; + } + + public function hydrate(ClassMetadata $metadata, array $data, array $context, Stack $stack): object + { + $object = null; + $populate = $context; + + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($populate[Hydrator::OBJECT_TO_POPULATE]); + } + + return match ($metadata->className) { + {{hydrateCases}} + default => $stack->next()->hydrate($metadata, $data, $context, $stack), + }; + } + + public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array + { + return match ($metadata->className) { + {{extractCases}} + default => $stack->next()->extract($metadata, $object, $context, $stack), + }; + } + + {{inits}} + + {{methods}} + + private function hydrator(): StackHydrator + { + return $this->hydrator ?? throw new HydratorNotSet(self::class); + } + + /** @param array $expected property name => [field name, has normalizer] */ + private function metadata(string $class, array $expected): ClassMetadata + { + $metadata = $this->hydrator()->metadata($class); + $actual = []; + + foreach ($metadata->properties as $property) { + $actual[$property->propertyName] = [$property->fieldName, $property->normalizer !== null]; + } + + if ($actual !== $expected) { + throw new OutdatedGeneratedMiddleware($class); + } + + return $metadata; + } + + private function normalizer(ClassMetadata $metadata, string $property): Normalizer + { + return $metadata->properties[$property]->normalizer ?? throw new OutdatedGeneratedMiddleware($metadata->className); + } + + private function promotedDefault(ClassMetadata $metadata, string $property): ReflectionParameter + { + return $metadata->promotedConstructorDefaults()[$property] ?? throw new OutdatedGeneratedMiddleware($metadata->className); + } + + private function inlineObject(Normalizer $normalizer, string $class, Skip $direction): bool + { + if (!$normalizer instanceof ObjectNormalizer) { + return false; + } + + try { + if ($normalizer->className() !== $class) { + return false; + } + } catch (Throwable) { + return false; + } + + return $this->inlinable($class, $direction); + } + + private function inlineArray(Normalizer $normalizer, string $class, Skip $direction): bool + { + return $normalizer instanceof ArrayNormalizer && $this->inlineObject($normalizer->innerNormalizer(), $class, $direction); + } + + /** + * Nested objects are only inlined when this does not change the behaviour: the nested class must be handled + * by this middleware, must not be lazy and every other middleware must skip the class in this direction. + */ + private function inlinable(string $class, Skip $direction): bool + { + try { + $metadata = $this->hydrator()->metadata($class); + } catch (Throwable) { + return false; + } + + if ($metadata->normalizer !== null) { + return false; + } + + if (\PHP_VERSION_ID >= 80400 && ($metadata->lazy ?? $this->hydrator()->defaultLazy())) { + return false; + } + + return $this->exclusive($metadata, $direction); + } + + /** Whether every other middleware skips the class in this direction, so this middleware is the only one to run. */ + private function exclusive(ClassMetadata $metadata, Skip $direction): bool + { + foreach ($this->others as $middleware) { + if (!$middleware instanceof SkippableMiddleware) { + return false; + } + + $skip = $middleware->skip($metadata); + + if ($skip !== Skip::Both && $skip !== $direction) { + return false; + } + } + + return true; + } +} + +PHP; + + public const COMPILED_CASE = <<<'PHP' +case \{{class}}::class: + $this->init{{index}}(); + + return {{closure}}; +PHP; + + public const FAST_HYDRATE = <<<'PHP' +public function fastHydrate{{index}}(array $data, array $context): object +{ + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return {{call}}($data, $context, $object); + } + + return {{call}}($data, $context); +} +PHP; + + public const INIT = <<<'PHP' +private function init{{index}}(): void +{ + {{body}} +} +PHP; + + public const CLOSURE = <<<'PHP' +$this->{{slot}} = Closure::bind(function ({{signature}}): {{return}} { + {{body}} +}, $this, \{{class}}::class); +PHP; + + public const METHOD = <<<'PHP' +public function {{name}}({{signature}}): {{return}} +{ + {{body}} +} +PHP; + + public const HYDRATE_HELPER = <<<'PHP' +$this->hp{{slot}} = Closure::bind(function (object $object, array $data, array $context): void { + {{body}} +}, $this, \{{scope}}::class); +PHP; + + public const EXTRACT_TRACKED = <<<'PHP' +if ($this->tracked{{index}}) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \{{class}}::class; + + try { + {{pre}} + + return [ + {{fields}} + ]; + } finally { + unset($this->callStack[$id]); + } +} +PHP; + + public const RECURSIVE = <<<'PHP' +private function recursive{{index}}(): bool +{ + if ($this->recursive{{index}} !== null) { + return $this->recursive{{index}}; + } + + $this->recursive{{index}} = true; + + return $this->recursive{{index}} = {{checks}}; +} +PHP; + + public const EXTRACT_PLAIN = <<<'PHP' +{{pre}} + +return [ + {{fields}} +]; +PHP; + + public const EXTRACT_HELPER = <<<'PHP' +$this->ep{{slot}} = Closure::bind(function (object $object, array $context): array { + {{body}} +}, $this, \{{scope}}::class); +PHP; + + public const ASSIGN = <<<'PHP' +if (\array_key_exists({{field}}, $data)) { + {{value}} + + try { + $object->{{name}} = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\{{class}}::class, {{property}}, $e); + } +} +PHP; + + public const ASSIGN_DEFAULT = <<<'PHP' +if (\array_key_exists({{field}}, $data)) { + {{value}} + + try { + $object->{{name}} = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\{{class}}::class, {{property}}, $e); + } +} else { + $object->{{name}} = {{default}}; +} +PHP; + + public const DENORMALIZE = <<<'PHP' +try { + {{inner}} +} catch (Throwable $e) { + throw new DenormalizationFailure(\{{class}}::class, {{property}}, $this->n{{slot}}::class, $e); +} +PHP; + + public const DENORMALIZE_OBJECT = <<<'PHP' +{{variable}} = $data[{{field}}]; + +if ($this->ih{{flag}} && \is_array({{variable}})) { + {{variable}} = {{nested}}({{variable}}, $context); +} else { + {{variable}} = $this->n{{slot}}->denormalize({{variable}}, $context); +} +PHP; + + public const DENORMALIZE_ARRAY = <<<'PHP' +{{variable}} = $data[{{field}}]; + +if ($this->ih{{flag}} && \is_array({{variable}})) { + $items = []; + + foreach ({{variable}} as $key => $item) { + $items[$key] = \is_array($item) ? {{nested}}($item, $context) : $this->n{{inner}}->denormalize($item, $context); + } + + {{variable}} = $items; +} else { + {{variable}} = $this->n{{slot}}->denormalize({{variable}}, $context); +} +PHP; + + public const NORMALIZE = <<<'PHP' +try { + {{inner}} +} catch (CircularReference $e) { + throw $e; +} catch (Throwable $e) { + throw new NormalizationFailure(\{{class}}::class, {{property}}, $this->n{{slot}}::class, $e); +} +PHP; + + public const NORMALIZE_OBJECT = <<<'PHP' +$value = $object->{{name}}; +{{variable}} = $this->ie{{flag}} && {{check}} ? {{nested}}($value, $context) : $this->n{{slot}}->normalize($value, $context); +PHP; + + public const NORMALIZE_ARRAY = <<<'PHP' +$value = $object->{{name}}; + +if ($this->ie{{flag}} && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = {{check}} ? {{nested}}($item, $context) : $this->n{{inner}}->normalize($item, $context); + } + + {{variable}} = $items; +} else { + {{variable}} = $this->n{{slot}}->normalize($value, $context); +} +PHP; +} diff --git a/src/Extension/Generated/ValueKind.php b/src/Extension/Generated/ValueKind.php new file mode 100644 index 00000000..e7a8072b --- /dev/null +++ b/src/Extension/Generated/ValueKind.php @@ -0,0 +1,25 @@ + $middlewares */ + /** + * @param non-empty-list $middlewares + * @param int $index Position of the middleware returned by the next call to next(). + */ public function __construct( private readonly array $middlewares, + private int $index = 0, ) { } diff --git a/src/StackHydrator.php b/src/StackHydrator.php index 568b0cc5..36d28a38 100644 --- a/src/StackHydrator.php +++ b/src/StackHydrator.php @@ -9,6 +9,7 @@ use Patchlevel\Hydrator\Metadata\ClassNotFound; use Patchlevel\Hydrator\Metadata\MetadataFactory; use Patchlevel\Hydrator\Middleware\AllMiddlewaresSkipped; +use Patchlevel\Hydrator\Middleware\HydratorAwareMiddleware; use Patchlevel\Hydrator\Middleware\Middleware; use Patchlevel\Hydrator\Middleware\Skip; use Patchlevel\Hydrator\Middleware\SkippableMiddleware; @@ -22,7 +23,8 @@ use const PHP_VERSION_ID; -final class StackHydrator implements Hydrator +/** @final this is only not final anymore because of bc reasons for the generated hydrator. DONT extend this class! */ +class StackHydrator implements Hydrator { /** @var array */ private array $classMetadata = []; @@ -50,14 +52,29 @@ public function __construct( foreach ($middlewares as $middleware) { if ($middleware instanceof SkippableMiddleware) { $hasSkippableMiddlewares = true; + } - break; + if (!$middleware instanceof HydratorAwareMiddleware) { + continue; } + + $middleware->setHydrator($this); } $this->hasSkippableMiddlewares = $hasSkippableMiddlewares; } + /** @return list */ + public function middlewares(): array + { + return $this->middlewares; + } + + public function defaultLazy(): bool + { + return $this->defaultLazy; + } + /** * @param class-string $class * @param array $context @@ -69,7 +86,8 @@ public function __construct( public function hydrate(string $class, mixed $data, array $context = []): object { try { - $metadata = $this->metadata($class); + /** @var ClassMetadata $metadata */ + $metadata = $this->classMetadata[$class] ?? $this->metadata($class); } catch (ClassNotFound $e) { throw new ClassNotSupported($class, $e); } @@ -89,24 +107,24 @@ public function hydrate(string $class, mixed $data, array $context = []): object } if (PHP_VERSION_ID < 80400) { - $stack = new Stack($this->middlewaresFor($metadata, Skip::Hydrate)); + $middlewares = $this->middlewaresFor($metadata, Skip::Hydrate); - return $stack->next()->hydrate($metadata, $data, $context, $stack); + return $middlewares[0]->hydrate($metadata, $data, $context, new Stack($middlewares, 1)); } $lazy = $metadata->lazy ?? $this->defaultLazy; if (!$lazy) { - $stack = new Stack($this->middlewaresFor($metadata, Skip::Hydrate)); + $middlewares = $this->middlewaresFor($metadata, Skip::Hydrate); - return $stack->next()->hydrate($metadata, $data, $context, $stack); + return $middlewares[0]->hydrate($metadata, $data, $context, new Stack($middlewares, 1)); } return (new ReflectionClass($class))->newLazyProxy( function () use ($metadata, $data, $context): object { - $stack = new Stack($this->middlewaresFor($metadata, Skip::Hydrate)); + $middlewares = $this->middlewaresFor($metadata, Skip::Hydrate); - return $stack->next()->hydrate($metadata, $data, $context, $stack); + return $middlewares[0]->hydrate($metadata, $data, $context, new Stack($middlewares, 1)); }, ); } @@ -118,15 +136,15 @@ function () use ($metadata, $data, $context): object { */ public function extract(object $object, array $context = []): mixed { - $metadata = $this->metadata($object::class); + $metadata = $this->classMetadata[$object::class] ?? $this->metadata($object::class); if ($metadata->normalizer) { return $metadata->normalizer->normalize($object, $context); } - $stack = new Stack($this->middlewaresFor($metadata, Skip::Extract)); + $middlewares = $this->middlewaresFor($metadata, Skip::Extract); - return $stack->next()->extract($metadata, $object, $context, $stack); + return $middlewares[0]->extract($metadata, $object, $context, new Stack($middlewares, 1)); } /** diff --git a/src/StackHydratorBuilder.php b/src/StackHydratorBuilder.php index 92c251cd..7f50f290 100644 --- a/src/StackHydratorBuilder.php +++ b/src/StackHydratorBuilder.php @@ -4,11 +4,13 @@ namespace Patchlevel\Hydrator; +use Closure; use Patchlevel\Hydrator\Guesser\ChainGuesser; use Patchlevel\Hydrator\Guesser\Guesser; use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory; use Patchlevel\Hydrator\Metadata\EnrichingMetadataFactory; use Patchlevel\Hydrator\Metadata\MetadataEnricher; +use Patchlevel\Hydrator\Metadata\MetadataFactory; use Patchlevel\Hydrator\Metadata\Psr16MetadataFactory; use Patchlevel\Hydrator\Metadata\Psr6MetadataFactory; use Patchlevel\Hydrator\Middleware\Middleware; @@ -33,6 +35,9 @@ final class StackHydratorBuilder private CacheItemPoolInterface|CacheInterface|null $cache = null; + /** @var (Closure(MetadataFactory, list, bool): StackHydrator)|null */ + private Closure|null $hydratorFactory = null; + /** @return $this */ public function addMiddleware(Middleware $middleware, int $priority = Extension::PRIORITY_BEFORE_TRANSFORM): static { @@ -78,14 +83,28 @@ public function setCache(CacheItemPoolInterface|CacheInterface|null $cache): sta return $this; } + /** + * Replaces how the hydrator is created. This exists for the GeneratedHydrator of the generated middleware + * extension, the StackHydrator itself is not meant to be extended (see its @final annotation). + * + * @param Closure(MetadataFactory, list, bool): StackHydrator $factory receives the metadata factory, the middlewares and the default lazy flag + * + * @return $this + */ + public function setHydratorFactory(Closure $factory): static + { + if ($this->hydratorFactory !== null) { + throw new HydratorFactoryAlreadySet(); + } + + $this->hydratorFactory = $factory; + + return $this; + } + public function build(): StackHydrator { - $metadataFactory = new EnrichingMetadataFactory( - new AttributeMetadataFactory( - guesser: new ChainGuesser($this->guessers()), - ), - $this->metadataEnrichers(), - ); + $metadataFactory = $this->getMetadataFactory(); if ($this->cache instanceof CacheItemPoolInterface) { $metadataFactory = new Psr6MetadataFactory($metadataFactory, $this->cache); @@ -95,6 +114,10 @@ public function build(): StackHydrator $metadataFactory = new Psr16MetadataFactory($metadataFactory, $this->cache); } + if ($this->hydratorFactory !== null) { + return ($this->hydratorFactory)($metadataFactory, $this->middlewares(), $this->defaultLazy); + } + return new StackHydrator( $metadataFactory, $this->middlewares(), @@ -130,4 +153,14 @@ public function metadataEnrichers(): array return array_merge(...$this->metadataEnrichers); } + + public function getMetadataFactory(): MetadataFactory + { + return new EnrichingMetadataFactory( + new AttributeMetadataFactory( + guesser: new ChainGuesser($this->guessers()), + ), + $this->metadataEnrichers(), + ); + } } diff --git a/tests/Architecture/FinalClassesTest.php b/tests/Architecture/FinalClassesTest.php index 1da5cd61..412125b8 100644 --- a/tests/Architecture/FinalClassesTest.php +++ b/tests/Architecture/FinalClassesTest.php @@ -4,6 +4,7 @@ namespace Patchlevel\Hydrator\Tests\Architecture; +use Patchlevel\Hydrator\StackHydrator; use PHPat\Selector\Selector; use PHPat\Test\Builder\Rule; use PHPat\Test\PHPat; @@ -18,6 +19,7 @@ public function testFinalClasses(): Rule Selector::inNamespace('Patchlevel\Hydrator'), Selector::NOT(Selector::isAbstract()), Selector::NOT(Selector::isInterface()), + Selector::NOT(Selector::classname(StackHydrator::class)), ), ) ->shouldBeFinal(); diff --git a/tests/Benchmark/EventSauceHydratorBench.php b/tests/Benchmark/EventSauceHydratorBench.php new file mode 100644 index 00000000..45b4b2e5 --- /dev/null +++ b/tests/Benchmark/EventSauceHydratorBench.php @@ -0,0 +1,132 @@ +hydrator = new ObjectMapperUsingReflection(); + } + + public function setUp(): void + { + $object = $this->hydrator->hydrateObject( + ProfileCreated::class, + [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + + $this->hydrator->serializeObject($object); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->hydrateObject(ProfileCreated::class, [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->serializeObject($object); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->hydrateObject(ProfileCreated::class, [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->serializeObject($object); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->hydrateObject(ProfileCreated::class, [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->serializeObject($object); + } + } +} diff --git a/tests/Benchmark/Fixture/ProfileCreated.php b/tests/Benchmark/Fixture/ProfileCreated.php index 5f28157c..a4421646 100644 --- a/tests/Benchmark/Fixture/ProfileCreated.php +++ b/tests/Benchmark/Fixture/ProfileCreated.php @@ -4,6 +4,7 @@ namespace Patchlevel\Hydrator\Tests\Benchmark\Fixture; +use JMS\Serializer\Annotation\Type; use Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId; use Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData; @@ -12,10 +13,12 @@ final class ProfileCreated /** @param list $skills */ public function __construct( #[ProfileIdNormalizer] + #[ProfileIdCaster] #[DataSubjectId] public ProfileId $profileId, #[SensitiveData(fallback: 'unknown')] public string $name, + #[Type('list')] public array $skills = [], ) { } diff --git a/tests/Benchmark/Fixture/ProfileIdCaster.php b/tests/Benchmark/Fixture/ProfileIdCaster.php new file mode 100644 index 00000000..6672eb86 --- /dev/null +++ b/tests/Benchmark/Fixture/ProfileIdCaster.php @@ -0,0 +1,39 @@ +toString(); + } +} diff --git a/tests/Benchmark/GeneratedEventSauceHydratorBench.php b/tests/Benchmark/GeneratedEventSauceHydratorBench.php new file mode 100644 index 00000000..f80d48fe --- /dev/null +++ b/tests/Benchmark/GeneratedEventSauceHydratorBench.php @@ -0,0 +1,145 @@ +dump($classesToDump, $dumpedClassNamed); + file_put_contents(__DIR__ . '/../../var/cache/GeneratedEventSauceMapper.php', $code); + + require_once __DIR__ . '/../../var/cache/GeneratedEventSauceMapper.php'; + + // @phpstan-ignore class.notFound, assign.propertyType + $this->hydrator = new GeneratedEventSauceMapper(); + } + + public function setUp(): void + { + $object = $this->hydrator->hydrateObject( + ProfileCreated::class, + [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + + $this->hydrator->serializeObject($object); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->hydrateObject(ProfileCreated::class, [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->serializeObject($object); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->hydrateObject(ProfileCreated::class, [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->serializeObject($object); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->hydrateObject(ProfileCreated::class, [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->serializeObject($object); + } + } +} diff --git a/tests/Benchmark/GeneratedHydratorBench.php b/tests/Benchmark/GeneratedHydratorBench.php new file mode 100644 index 00000000..a391fa2b --- /dev/null +++ b/tests/Benchmark/GeneratedHydratorBench.php @@ -0,0 +1,142 @@ +hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new GeneratedMiddlewareExtension( + __DIR__ . '/../../var/cache', + [ + ProfileCreated::class, + Skill::class, + ], + debug: true, + )) + ->build(); + } + + public function setUp(): void + { + $this->hydrator->hydrate( + ProfileCreated::class, + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->extract($object); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->extract($object); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ]); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->extract($object); + } + } +} diff --git a/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php b/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php new file mode 100644 index 00000000..518483e2 --- /dev/null +++ b/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php @@ -0,0 +1,156 @@ +store = new InMemoryCipherKeyStore(); + + $this->hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new GeneratedMiddlewareExtension(__DIR__ . '/../../var/cache', [ProfileCreated::class, Skill::class], debug: true)) + ->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($this->store))) + ->build(); + } + + public function setUp(): void + { + $this->store->clear(); + + $object = $this->hydrator->hydrate( + ProfileCreated::class, + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + + $this->hydrator->extract($object); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->hydrate( + ProfileCreated::class, + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->extract($object); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->hydrate( + ProfileCreated::class, + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->extract($object); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->hydrate( + ProfileCreated::class, + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->extract($object); + } + } +} diff --git a/tests/Benchmark/JMSHydratorBench.php b/tests/Benchmark/JMSHydratorBench.php new file mode 100644 index 00000000..6954ac1f --- /dev/null +++ b/tests/Benchmark/JMSHydratorBench.php @@ -0,0 +1,141 @@ +hydrator = SerializerBuilder::create()->build(); + } + + public function setUp(): void + { + $object = $this->hydrator->fromArray( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + + $this->hydrator->toArray($object); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->fromArray( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->toArray($object); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->fromArray( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->toArray($object); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->fromArray( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->toArray($object); + } + } +} diff --git a/tests/Benchmark/SerdeHydratorBench.php b/tests/Benchmark/SerdeHydratorBench.php new file mode 100644 index 00000000..0ae425fc --- /dev/null +++ b/tests/Benchmark/SerdeHydratorBench.php @@ -0,0 +1,148 @@ +hydrator = new SerdeCommon($analyzer); + } + + public function setUp(): void + { + $object = $this->hydrator->deserialize( + [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + 'array', + ProfileCreated::class, + ); + + $this->hydrator->serialize($object, 'array'); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->deserialize( + [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + 'array', + ProfileCreated::class, + ); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->serialize($object, 'array'); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->deserialize( + [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + 'array', + ProfileCreated::class, + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->serialize($object, 'array'); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->deserialize( + [ + 'profile_id' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + 'array', + ProfileCreated::class, + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->serialize($object, 'array'); + } + } +} diff --git a/tests/Benchmark/SymfonyHydratorBench.php b/tests/Benchmark/SymfonyHydratorBench.php new file mode 100644 index 00000000..c8f30631 --- /dev/null +++ b/tests/Benchmark/SymfonyHydratorBench.php @@ -0,0 +1,141 @@ +hydrator = new Serializer([new ObjectNormalizer()], []); + } + + public function setUp(): void + { + $object = $this->hydrator->denormalize( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + + $this->hydrator->normalize($object); + } + + #[Bench\Revs(5)] + public function benchHydrate1Object(): void + { + $this->hydrator->denormalize( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + } + + #[Bench\Revs(5)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + $this->hydrator->normalize($object); + } + + #[Bench\Revs(3)] + public function benchHydrate1000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->denormalize( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->normalize($object); + } + } + + #[Bench\Revs(3)] + public function benchHydrate1000000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->denormalize( + [ + 'profileId' => '1', + 'name' => 'foo', + 'skills' => [ + ['name' => 'php'], + ['name' => 'symfony'], + ], + ], + ProfileCreated::class, + ); + } + } + + #[Bench\Revs(3)] + public function benchExtract1000000Objects(): void + { + $object = new ProfileCreated( + ProfileId::fromString('1'), + 'foo', + [ + new Skill('php'), + new Skill('symfony'), + ], + ); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->normalize($object); + } + } +} diff --git a/tests/Unit/Extension/Generated/GeneratedMiddlewareExtensionTest.php b/tests/Unit/Extension/Generated/GeneratedMiddlewareExtensionTest.php new file mode 100644 index 00000000..a991c0c0 --- /dev/null +++ b/tests/Unit/Extension/Generated/GeneratedMiddlewareExtensionTest.php @@ -0,0 +1,85 @@ +defaultClassName(), (new GeneratedMiddlewareExtension($path, [ProfileCreated::class]))->defaultClassName()); + self::assertNotSame($a->defaultClassName(), $b->defaultClassName()); + } + + public function testGeneratedFileIsCachedAndReused(): void + { + $path = GeneratedStackHydratorTest::cachePath() . '/' . uniqid('cache', true); + $className = 'Cached_' . uniqid(); + $file = sprintf('%s/%s.php', $path, $className); + + $extension = new GeneratedMiddlewareExtension($path, [ProfileCreated::class], className: $className); + $hydrator = (new StackHydratorBuilder())->useExtension(new CoreExtension())->useExtension($extension)->build(); + + self::assertFileExists($file); + $mtime = filemtime($file); + touch($file, $mtime - 100); + + // the class is already loaded, the file is neither regenerated nor required again + (new StackHydratorBuilder())->useExtension(new CoreExtension())->useExtension($extension)->build(); + self::assertSame($mtime - 100, filemtime($file)); + + $event = $hydrator->hydrate(ProfileCreated::class, ['profileId' => '1', 'email' => 'info@patchlevel.de']); + self::assertEquals(new ProfileCreated(ProfileId::fromString('1'), Email::fromString('info@patchlevel.de')), $event); + } + + public function testMiddlewareStack(): void + { + $hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new GeneratedMiddlewareExtension(GeneratedStackHydratorTest::cachePath(), [ProfileCreated::class])) + ->build(); + + $middlewares = $hydrator->middlewares(); + + self::assertCount(2, $middlewares); + self::assertStringStartsWith(GeneratedMiddlewareExtension::NAMESPACE . '\\', $middlewares[0]::class); + self::assertInstanceOf(TransformMiddleware::class, $middlewares[1]); + } + + public function testNotWritable(): void + { + $this->expectException(GeneratedMiddlewareNotWritable::class); + + $file = sys_get_temp_dir() . '/' . uniqid('not-a-directory'); + touch($file); + + (new StackHydratorBuilder()) + ->useExtension(new GeneratedMiddlewareExtension($file, [ProfileCreated::class], className: 'NotWritable_' . uniqid())) + ->build(); + } +} diff --git a/tests/Unit/Extension/Generated/GeneratedStackHydratorTest.php b/tests/Unit/Extension/Generated/GeneratedStackHydratorTest.php new file mode 100644 index 00000000..74a3b0f5 --- /dev/null +++ b/tests/Unit/Extension/Generated/GeneratedStackHydratorTest.php @@ -0,0 +1,995 @@ +hydrator = $this->builder()->build(); + } + + /** @param list $classes */ + private function builder(array $classes = self::CLASSES): StackHydratorBuilder + { + return (new StackHydratorBuilder())->useExtension(new CoreExtension())->useExtension($this->extension($classes)); + } + + /** @param list $classes */ + private function extension(array $classes = self::CLASSES): GeneratedMiddlewareExtension + { + return new GeneratedMiddlewareExtension( + self::cachePath(), + $classes, + debug: true, + ); + } + + public static function cachePath(): string + { + return sys_get_temp_dir() . '/patchlevel-hydrator-tests'; + } + + public function testExtract(): void + { + $event = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + self::assertEquals( + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + $this->hydrator->extract($event), + ); + } + + public function testExtractWithInheritance(): void + { + $event = new ParentDto( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + self::assertEquals( + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + $this->hydrator->extract($event), + ); + } + + public function testExtractWithHydratorAwareNormalizer(): void + { + $event = new ProfileCreatedWrapper( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + ); + + self::assertEquals( + ['event' => ['profileId' => '1', 'email' => 'info@patchlevel.de']], + $this->hydrator->extract($event), + ); + } + + public function testExtractPassesContextToNormalizer(): void + { + $dto = new ContextAwareDto('value'); + + $data = $this->hydrator->extract($dto, ['prefix' => 'ctx-']); + + self::assertSame(['value' => 'ctx-value'], $data); + } + + public function testExtractCircularReference(): void + { + $this->expectException(CircularReference::class); + $this->expectExceptionMessage('Circular reference detected: Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto -> Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto -> Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto -> Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto'); + + $dto1 = new Circle1Dto(); + $dto2 = new Circle2Dto(); + $dto3 = new Circle3Dto(); + + $dto1->to = $dto2; + $dto2->to = $dto3; + $dto3->to = $dto1; + + $this->hydrator->extract($dto1); + } + + public function testExtractWithInferNormalizer(): void + { + $result = $this->hydrator->extract( + new InferNormalizerWithNullableDto( + null, + null, + profileId: ProfileId::fromString('1'), + ), + ); + + self::assertEquals( + [ + 'status' => null, + 'dateTimeImmutable' => null, + 'dateTime' => null, + 'dateTimeZone' => null, + 'profileId' => '1', + ], + $result, + ); + } + + public function testExtractWithContext(): void + { + $object = new InferNormalizerDto( + Status::Draft, + new DateTimeImmutable('2015-02-13 22:34:32+01:00'), + new DateTime('2015-02-13 22:34:32+01:00'), + new DateTimeZone('EDT'), + ['foo'], + ); + + $expect = [ + 'status' => 'draft', + 'dateTimeImmutable' => '2015-02-13T22:34:32+01:00', + 'dateTime' => '2015-02-13T22:34:32+01:00', + 'dateTimeZone' => 'EDT', + 'array' => ['foo'], + ]; + + $middleware = $this->createMock(Middleware::class); + $middleware + ->expects($this->once()) + ->method('extract') + ->with( + $this->isInstanceOf(ClassMetadata::class), + $object, + ['context' => '123'], + $this->isInstanceOf(Stack::class), + )->willReturn($expect); + + $hydrator = $this->builder() + ->addMiddleware($middleware) + ->build(); + + $data = $hydrator->extract($object, ['context' => '123']); + + self::assertEquals($expect, $data); + } + + #[RequiresPhp('>=8.5')] + public function testExtractWithInlineNormalizer(): void + { + $event = new ProfileCreatedWithInlineNormalizer( + ProfileId::fromString('1'), + ValueObject::fromString('foo'), + ); + + self::assertEquals( + ['profileId' => '1', 'valueObject' => 'foo'], + $this->hydrator->extract($event), + ); + } + + public function testExtractWithClassNormalizer(): void + { + $data = $this->hydrator->extract( + ProfileId::fromString('id'), + ); + + self::assertEquals('id', $data); + } + + public function testExtractPrivateProperties(): void + { + $dto = new PrivatePropertiesDto(ProfileId::fromString('1'), 'foo', 12); + + self::assertSame( + ['profileId' => '1', 'name' => 'foo', 'age' => 12], + $this->hydrator->extract($dto), + ); + } + + public function testExtractPrivatePropertiesOfParent(): void + { + $dto = new PrivateChildDto(ProfileId::fromString('1'), Email::fromString('info@patchlevel.de'), 'note', 'foo'); + + self::assertSame( + ['profileId' => '1', 'name' => 'foo', 'email' => 'info@patchlevel.de', 'note' => 'note'], + $this->hydrator->extract($dto), + ); + self::assertSame((new StackHydrator())->extract($dto), $this->hydrator->extract($dto)); + } + + public function testHydrate(): void + { + $expected = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $event = $this->hydrator->hydrate( + ProfileCreated::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + + self::assertEquals($expected, $event); + } + + public function testHydratePassesContextToNormalizer(): void + { + $event = $this->hydrator->hydrate( + ContextAwareDto::class, + ['value' => 'value'], + ['suffix' => '-ctx'], + ); + + self::assertSame('value-ctx', $event->value); + } + + public function testHydrateUnknownClass(): void + { + $this->expectException(ClassNotSupported::class); + $this->expectExceptionCode(0); + + $this->hydrator->hydrate( + // @phpstan-ignore argument.type + 'Unknown', + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + } + + public function testHydrateWithArrayDataRequired(): void + { + $this->expectException(ArrayDataRequired::class); + + $this->hydrator->hydrate( + ProfileCreated::class, + 'foo', + ); + } + + public function testHydrateWithDefaults(): void + { + $object = $this->hydrator->hydrate( + DefaultDto::class, + ['name' => 'test'], + ); + + self::assertEquals('test', $object->name); + self::assertEquals(new Email('info@patchlevel.de'), $object->email); + self::assertEquals(true, $object->admin); + } + + public function testHydrateWithInheritance(): void + { + $expected = new ParentDto( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $event = $this->hydrator->hydrate( + ParentDto::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + + self::assertEquals($expected, $event); + } + + public function testHydrateWithHydratorAwareNormalizer(): void + { + $expected = new ProfileCreatedWrapper( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + ); + + $event = $this->hydrator->hydrate( + ProfileCreatedWrapper::class, + [ + 'event' => ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ], + ); + + self::assertEquals($expected, $event); + } + + public function testHydrateWithObjectToPopulate(): void + { + $id = ProfileId::fromString('1'); + $dto = new ProfileCreated($id, Email::fromString('foo@patchlevel.de')); + + $processedDto = $this->hydrator->hydrate( + $dto::class, + ['email' => 'other@patchlevel.de'], + [StackHydrator::OBJECT_TO_POPULATE => $dto], + ); + + self::assertSame($dto, $processedDto); + self::assertSame($id, $processedDto->profileId); + self::assertEquals(Email::fromString('other@patchlevel.de'), $processedDto->email); + } + + public function testHydrateWithTypeMismatch(): void + { + $this->expectException(TypeMismatch::class); + $this->expectExceptionMessage('The value could not be set because the expected type of the property "profileId" in class "Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated" does not match.'); + + $this->hydrator->hydrate( + ProfileCreated::class, + ['profileId' => null, 'email' => null], + ); + } + + public function testHydrateWithContext(): void + { + $expect = new InferNormalizerDto( + Status::Draft, + new DateTimeImmutable('2015-02-13 22:34:32+01:00'), + new DateTime('2015-02-13 22:34:32+01:00'), + new DateTimeZone('EDT'), + ['foo'], + ); + + $data = [ + 'status' => 'draft', + 'dateTimeImmutable' => '2015-02-13T22:34:32+01:00', + 'dateTime' => '2015-02-13T22:34:32+01:00', + 'dateTimeZone' => 'EDT', + 'array' => ['foo'], + ]; + + $middleware = $this->createMock(Middleware::class); + $middleware + ->expects($this->once()) + ->method('hydrate') + ->with( + $this->isInstanceOf(ClassMetadata::class), + $data, + ['context' => '123'], + $this->isInstanceOf(Stack::class), + )->willReturn($expect); + + $hydrator = $this->builder() + ->addMiddleware($middleware) + ->build(); + + $object = $hydrator->hydrate(InferNormalizerDto::class, $data, ['context' => '123']); + + self::assertEquals($expect, $object); + } + + #[RequiresPhp('>=8.5')] + public function testHydrateWithInlineNormalizer(): void + { + $expected = new ProfileCreatedWithInlineNormalizer( + ProfileId::fromString('1'), + ValueObject::fromString('foo'), + ); + + $event = $this->hydrator->hydrate( + ProfileCreatedWithInlineNormalizer::class, + ['profileId' => '1', 'valueObject' => 'foo'], + ); + + self::assertEquals($expected, $event); + } + + public function testDenormalizationFailure(): void + { + $this->expectException(DenormalizationFailure::class); + + $this->hydrator->hydrate( + ProfileCreated::class, + ['profileId' => 123, 'email' => 123], + ); + } + + public function testNormalizationFailure(): void + { + $this->expectException(NormalizationFailure::class); + + $this->hydrator->extract( + new WrongNormalizer(true), + ); + } + + public function testHydrateWithNormalizerInBaseClass(): void + { + $expected = new NormalizerInBaseClassDefinedDto( + StatusWithNormalizer::Draft, + new ProfileCreatedWithNormalizer( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + [StatusWithNormalizer::Draft], + [StatusWithNormalizer::Draft], + [StatusWithNormalizer::Draft], + [ + 'foo' => new Skill('php'), + 'bar' => new Skill('symfony'), + ], + [ + 'foo' => 'php', + 'bar' => 15, + 'baz' => ['test'], + ], + ); + + $event = $this->hydrator->hydrate( + NormalizerInBaseClassDefinedDto::class, + [ + 'status' => 'draft', + 'profileCreated' => ['profileId' => '1', 'email' => 'info@patchlevel.de'], + 'defaultArray' => ['draft'], + 'listArray' => ['draft'], + 'iterableArray' => ['draft'], + 'skillsHashMap' => ['foo' => ['name' => 'php'], 'bar' => ['name' => 'symfony']], + 'jsonArray' => ['foo' => 'php', 'bar' => 15, 'baz' => ['test']], + ], + ); + + self::assertEquals($expected, $event); + } + + public function testHydrateWithInferNormalizer(): void + { + $expected = new InferNormalizerDto( + Status::Draft, + new DateTimeImmutable('2015-02-13 22:34:32+01:00'), + new DateTime('2015-02-13 22:34:32+01:00'), + new DateTimeZone('EDT'), + ['foo'], + ); + + $event = $this->hydrator->hydrate( + InferNormalizerDto::class, + [ + 'status' => 'draft', + 'dateTimeImmutable' => '2015-02-13T22:34:32+01:00', + 'dateTime' => '2015-02-13T22:34:32+01:00', + 'dateTimeZone' => 'EDT', + 'array' => ['foo'], + ], + ); + + self::assertEquals($expected, $event); + } + + public function testHydrateWithInferNormalizerAndNullableProperties(): void + { + $expected = new InferNormalizerWithNullableDto( + null, + null, + null, + null, + ); + + $event = $this->hydrator->hydrate( + InferNormalizerWithNullableDto::class, + [ + 'status' => null, + 'dateTimeImmutable' => null, + 'dateTime' => null, + 'dateTimeZone' => null, + ], + ); + + self::assertEquals($expected, $event); + } + + public function testHydrateWithInferNormalizerWitIterables(): void + { + $expected = new InferNormalizerWithIterablesDto( + [Status::Draft], + [Status::Draft], + [Status::Draft], + [ + 'foo' => Status::Draft, + 'bar' => Status::Draft, + ], + [ + 'foo' => [Status::Draft], + 'bar' => [Status::Draft], + ], + [ + 'foo' => 'php', + 'bar' => 15, + 'baz' => ['test'], + ], + [ + 'status' => Status::Draft, + 'other' => [Status::Draft], + ], + ); + + $event = $this->hydrator->hydrate( + InferNormalizerWithIterablesDto::class, + [ + 'defaultArray' => ['draft'], + 'listArray' => ['draft'], + 'iterableArray' => ['draft'], + 'hashMap' => ['foo' => 'draft', 'bar' => 'draft'], + 'nested' => ['foo' => ['draft'], 'bar' => ['draft']], + 'jsonArray' => ['foo' => 'php', 'bar' => 15, 'baz' => ['test']], + 'shapeArray' => ['status' => 'draft', 'other' => ['draft']], + ], + ); + + self::assertEquals($expected, $event); + } + + public function testHydrateWithClassNormalizer(): void + { + $object = $this->hydrator->hydrate( + ProfileId::class, + 'id', + ); + + self::assertEquals(ProfileId::fromString('id'), $object); + } + + public function testHydratePrivateProperties(): void + { + $dto = $this->hydrator->hydrate( + PrivatePropertiesDto::class, + ['profileId' => '1', 'name' => 'foo', 'age' => 12], + ); + + self::assertEquals(ProfileId::fromString('1'), $dto->profileId()); + self::assertSame('foo', $dto->name()); + self::assertSame(12, $dto->age()); + } + + public function testHydratePrivatePropertiesOfParent(): void + { + $dto = $this->hydrator->hydrate( + PrivateChildDto::class, + ['email' => 'info@patchlevel.de', 'note' => 'note', 'profileId' => '1', 'name' => 'foo'], + ); + + self::assertEquals(ProfileId::fromString('1'), $dto->profileId); + self::assertEquals(Email::fromString('info@patchlevel.de'), $dto->email()); + self::assertSame('note', $dto->note()); + self::assertSame('foo', $dto->name()); + } + + #[RequiresPhp('>=8.4')] + public function testHydrateAsymmetricVisibility(): void + { + $data = ['name' => 'foo', 'age' => 12, 'note' => 'note']; + $hydrator = $this->builder([AsymmetricVisibilityDto::class])->build(); + + $dto = $hydrator->hydrate(AsymmetricVisibilityDto::class, $data); + + self::assertEquals(new AsymmetricVisibilityDto('foo', 12, 'note'), $dto); + self::assertSame($data, $hydrator->extract($dto)); + } + + public function testHydrateCoercesScalarsLikeReflection(): void + { + $dto = $this->hydrator->hydrate( + PrivatePropertiesDto::class, + ['profileId' => '1', 'name' => 'foo', 'age' => '12'], + ); + + self::assertSame(12, $dto->age()); + } + + public function testHydrateUnknownClassFallsBackToTransformMiddleware(): void + { + $hydrator = $this->builder([ProfileCreated::class])->build(); + + $event = $hydrator->hydrate( + ParentDto::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + + self::assertEquals(new ParentDto(ProfileId::fromString('1'), Email::fromString('info@patchlevel.de')), $event); + self::assertEquals(['profileId' => '1', 'email' => 'info@patchlevel.de'], $hydrator->extract($event)); + } + + #[RequiresPhp('>=8.4')] + public function testLazyHydrate(): void + { + $event = $this->hydrator->hydrate( + LazyProfileCreated::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + + $expected = new LazyProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $reflection = new ReflectionClass(LazyProfileCreated::class); + self::assertTrue($reflection->isUninitializedLazyObject($event)); + + $reflection->initializeLazyObject($event); + self::assertEquals($expected, $event); + } + + #[RequiresPhp('<8.4')] + public function testLazyNotSupported(): void + { + $event = $this->hydrator->hydrate( + LazyProfileCreated::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + + $expected = new LazyProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + self::assertEquals($expected, $event); + } + + #[RequiresPhp('>=8.4')] + public function testLazyExtract(): void + { + $event = $this->hydrator->hydrate( + LazyProfileCreated::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + ); + + $data = $this->hydrator->extract($event); + + self::assertEquals(['profileId' => '1', 'email' => 'info@patchlevel.de'], $data); + } + + public function testDecrypt(): void + { + $object = new SensitiveDataProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $encryptedPayload = ['id' => '1', 'email' => 'encrypted']; + + $cryptographer = $this->createMock(Cryptographer::class); + $cryptographer + ->expects($this->once()) + ->method('supports') + ->with('encrypted') + ->willReturn(true); + + $cryptographer + ->expects($this->once()) + ->method('decrypt') + ->with('1', 'encrypted') + ->willReturn('info@patchlevel.de'); + + $hydrator = $this->builder() + ->useExtension(new CryptographyExtension($cryptographer)) + ->build(); + + $return = $hydrator->hydrate(SensitiveDataProfileCreated::class, $encryptedPayload); + + self::assertEquals($object, $return); + } + + public function testEncrypt(): void + { + $object = new SensitiveDataProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $encryptedPayload = [ + 'id' => '1', + 'email' => [ + '__enc' => 'v1', + 'data' => 'encrypted', + 'method' => 'foo', + 'iv' => 'bar', + ], + ]; + + $cryptographer = $this->createMock(Cryptographer::class); + + $cryptographer + ->expects($this->never()) + ->method('supports'); + + $cryptographer + ->expects($this->once()) + ->method('encrypt') + ->with('1', 'info@patchlevel.de') + ->willReturn([ + '__enc' => 'v1', + 'data' => 'encrypted', + 'method' => 'foo', + 'iv' => 'bar', + ]); + + $hydrator = $this->builder() + ->useExtension(new CryptographyExtension($cryptographer)) + ->build(); + + $return = $hydrator->extract($object); + + self::assertSame($encryptedPayload, $return); + } + + /** + * Reads the inlining decisions of the generated middleware. + * + * @return array flag name => inlined + */ + private static function inlined(StackHydrator $hydrator): array + { + foreach ($hydrator->middlewares() as $middleware) { + if (str_starts_with($middleware::class, GeneratedMiddlewareExtension::NAMESPACE . '\\')) { + /** @var array $flags */ + $flags = array_filter( + get_object_vars($middleware), + static fn (string $name): bool => preg_match('/^i[he]\d+$/', $name) === 1, + ARRAY_FILTER_USE_KEY, + ); + + return $flags; + } + } + + self::fail('generated middleware not found'); + } + + public function testNestedObjectsAreInlinedWithoutOtherMiddlewares(): void + { + $hydrator = $this->builder([NestedLifecycleDto::class])->build(); + $data = ['child' => ['name' => 'a'], 'items' => [['name' => 'b']]]; + + self::assertSame($data, $hydrator->extract($hydrator->hydrate(NestedLifecycleDto::class, $data))); + self::assertSame(['ih0' => true, 'ie0' => true, 'ih1' => true, 'ie1' => true], self::inlined($hydrator)); + } + + public function testNestedObjectsAreInlinedWhenOtherMiddlewaresSkipThem(): void + { + $counter = new CountingMiddleware([LifecycleFixture::class => Skip::Both]); + + $hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension($this->extension([NestedLifecycleDto::class])) + ->addMiddleware($counter) + ->build(); + + $data = ['child' => ['name' => 'a'], 'items' => [['name' => 'b']]]; + self::assertSame($data, $hydrator->extract($hydrator->hydrate(NestedLifecycleDto::class, $data))); + + self::assertSame(['ih0' => true, 'ie0' => true, 'ih1' => true, 'ie1' => true], self::inlined($hydrator)); + self::assertSame([NestedLifecycleDto::class => 1], $counter->hydrated); + } + + public function testInliningIsDecidedPerDirection(): void + { + // an upcaster which can not be introspected keeps the upcast middleware in the hydrate stack + $upcaster = new class implements Upcaster { + /** + * @param array $data + * @param array $context + * + * @return array + */ + public function upcast(ClassMetadata $metadata, array $data, array $context): array + { + return $data; + } + }; + + $hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension($this->extension([NestedLifecycleDto::class])) + ->useExtension(new UpcastExtension([$upcaster])) + ->build(); + + $data = ['child' => ['name' => 'a'], 'items' => [['name' => 'b']]]; + self::assertSame($data, $hydrator->extract($hydrator->hydrate(NestedLifecycleDto::class, $data))); + self::assertSame(['ih0' => false, 'ie0' => true, 'ih1' => false, 'ie1' => true], self::inlined($hydrator)); + + // callback upcasters for other classes do not prevent inlining + $hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension($this->extension([NestedLifecycleDto::class])) + ->useExtension(new UpcastExtension([CallbackUpcaster::forClass(ProfileCreated::class, static fn (array $data): array => $data)])) + ->build(); + + self::assertSame($data, $hydrator->extract($hydrator->hydrate(NestedLifecycleDto::class, $data))); + self::assertSame(['ih0' => true, 'ie0' => true, 'ih1' => true, 'ie1' => true], self::inlined($hydrator)); + } + + public function testCompiledClosures(): void + { + $hydrator = $this->builder([ProfileCreated::class])->build(); + $middleware = $hydrator->middlewares()[0]; + + self::assertInstanceOf(GeneratedHydrator::class, $hydrator); + self::assertInstanceOf(GeneratedMiddleware::class, $middleware); + self::assertNotNull($middleware->compiledHydrator($hydrator->metadata(ProfileCreated::class))); + self::assertNotNull($middleware->compiledExtractor($hydrator->metadata(ProfileCreated::class))); + self::assertNull($middleware->compiledHydrator($hydrator->metadata(ParentDto::class))); + self::assertNull($middleware->compiledExtractor($hydrator->metadata(ParentDto::class))); + + // with a middleware which does not skip the class, the stack has to run + $hydrator = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension($this->extension([ProfileCreated::class])) + ->addMiddleware(new CountingMiddleware()) + ->build(); + $middleware = $hydrator->middlewares()[1]; + + self::assertInstanceOf(GeneratedMiddleware::class, $middleware); + self::assertNull($middleware->compiledHydrator($hydrator->metadata(ProfileCreated::class))); + self::assertNull($middleware->compiledExtractor($hydrator->metadata(ProfileCreated::class))); + } + + public function testUnknownClassesAreSkipped(): void + { + $hydrator = $this->builder([ProfileCreated::class])->build(); + $middleware = $hydrator->middlewares()[0]; + + self::assertInstanceOf(SkippableMiddleware::class, $middleware); + self::assertSame(Skip::None, $middleware->skip($hydrator->metadata(ProfileCreated::class))); + self::assertSame(Skip::Both, $middleware->skip($hydrator->metadata(ParentDto::class))); + } + + public function testNestedObjectsAreNotInlinedWithNonSkippableMiddleware(): void + { + $counter = new CountingMiddleware(); + + $hydrator = $this->builder()->build(); + $direct = (new StackHydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension($this->extension()) + ->addMiddleware($counter) + ->build(); + + $data = ['child' => ['name' => 'a'], 'items' => [['name' => 'b'], ['name' => 'c']]]; + + $object = $hydrator->hydrate(NestedLifecycleDto::class, $data); + self::assertSame($data, $hydrator->extract($object)); + + // with another middleware in the stack, nested objects go through the whole stack + $object = $direct->hydrate(NestedLifecycleDto::class, $data); + self::assertSame($data, $direct->extract($object)); + + self::assertSame([NestedLifecycleDto::class => 1, LifecycleFixture::class => 3], $counter->hydrated); + self::assertSame([NestedLifecycleDto::class => 1, LifecycleFixture::class => 3], $counter->extracted); + self::assertNotContains(true, self::inlined($direct)); + } + + public function testNestedObjectsRespectOtherMiddlewares(): void + { + $hydrator = $this->builder() + ->useExtension(new LifecycleExtension()) + ->build(); + + $object = $hydrator->hydrate( + NestedLifecycleDto::class, + ['child' => ['name' => 'a'], 'items' => [['name' => 'b']]], + ); + + self::assertSame('a [preHydrate] [postHydrate]', $object->child->name); + self::assertSame('b [preHydrate] [postHydrate]', $object->items[0]->name); + + self::assertSame( + [ + 'child' => ['name' => 'a [preHydrate] [postHydrate] [preExtract] [postExtract]'], + 'items' => [['name' => 'b [preHydrate] [postHydrate] [preExtract] [postExtract]']], + ], + $hydrator->extract($object), + ); + } +} diff --git a/tests/Unit/Extension/Generated/MiddlewareGeneratorTest.php b/tests/Unit/Extension/Generated/MiddlewareGeneratorTest.php new file mode 100644 index 00000000..47f8922a --- /dev/null +++ b/tests/Unit/Extension/Generated/MiddlewareGeneratorTest.php @@ -0,0 +1,153 @@ +dump([ProfileCreated::class, Skill::class], 'Foo\\Bar'); + $second = $generator->dump([ProfileCreated::class, Skill::class], 'Foo\\Bar'); + + self::assertSame($first, $second); + self::assertStringContainsString('namespace Foo;', $first); + self::assertStringContainsString('final class Bar implements SkippableMiddleware, HydratorAwareMiddleware', $first); + self::assertStringNotContainsString('strict_types=1', $first); + } + + /** + * The snapshot makes every change of the generated code visible in the diff of a pull request. + * Run "make snapshot" after an intended change of the generator. + */ + public function testGeneratedCodeMatchesSnapshot(): void + { + $file = __DIR__ . '/Snapshot/SnapshotMiddleware.php'; + $code = (new MiddlewareGenerator(new AttributeMetadataFactory()))->dump( + GeneratedStackHydratorTest::CLASSES, + 'Patchlevel\\Hydrator\\Tests\\Snapshot\\SnapshotMiddleware', + ); + + if (getenv('UPDATE_SNAPSHOTS') === '1') { + file_put_contents($file, $code); + } + + self::assertStringEqualsFile($file, $code, 'The generated code changed, run "make snapshot" if the change is intended.'); + } + + public function testClassesWithClassNormalizerAreSkipped(): void + { + $generator = new MiddlewareGenerator(new AttributeMetadataFactory()); + + $code = $generator->dump([ProfileId::class], 'Foo\\Bar'); + + self::assertStringNotContainsString(sprintf('\\%s::class', ProfileId::class), $code); + } + + public function testAbstractClassIsNotGeneratable(): void + { + $this->expectException(ClassNotGeneratable::class); + + (new MiddlewareGenerator(new AttributeMetadataFactory()))->dump([ChildDto::class], 'Foo\\Bar'); + } + + public function testUnknownClassIsNotGeneratable(): void + { + $this->expectException(ClassNotGeneratable::class); + + /** @phpstan-ignore argument.type */ + (new MiddlewareGenerator(new AttributeMetadataFactory()))->dump(['Unknown'], 'Foo\\Bar'); + } + + public function testMiddlewareRequiresHydrator(): void + { + $middleware = $this->load([ProfileCreated::class]); + $metadata = (new AttributeMetadataFactory())->metadata(ProfileCreated::class); + + $this->expectException(HydratorNotSet::class); + + $middleware->hydrate($metadata, ['profileId' => '1', 'email' => 'info@patchlevel.de'], [], new Stack([$middleware])); + } + + public function testOutdatedMiddlewareIsDetected(): void + { + $middleware = $this->load([ProfileCreated::class]); + + $hydrator = (new StackHydratorBuilder()) + ->addMiddleware($middleware) + ->addMetadataEnricher(new class implements MetadataEnricher { + public function enrich(ClassMetadata $classMetadata): void + { + foreach ($classMetadata->properties as $property) { + $property->fieldName = 'renamed_' . $property->fieldName; + } + } + }) + ->build(); + + $this->expectException(OutdatedGeneratedMiddleware::class); + + $hydrator->hydrate(ProfileCreated::class, ['renamed_profileId' => '1', 'renamed_email' => 'info@patchlevel.de']); + } + + /** @param list $classes */ + private function load(array $classes): Middleware + { + $fqcn = 'Patchlevel\\Hydrator\\Tests\\Generated\\Middleware' . self::$counter++; + $code = (new MiddlewareGenerator(new AttributeMetadataFactory()))->dump($classes, $fqcn); + + $file = tempnam(GeneratedStackHydratorTest::cachePath(), 'middleware'); + assert($file !== false); + file_put_contents($file, $code); + + require $file; + unlink($file); + + assert(is_a($fqcn, Middleware::class, true)); + + return new $fqcn(); + } +} diff --git a/tests/Unit/Extension/Generated/Snapshot/SnapshotMiddleware.php b/tests/Unit/Extension/Generated/Snapshot/SnapshotMiddleware.php new file mode 100644 index 00000000..033e6158 --- /dev/null +++ b/tests/Unit/Extension/Generated/Snapshot/SnapshotMiddleware.php @@ -0,0 +1,3997 @@ + all middlewares except this one and the TransformMiddleware behind it */ + public array $others = []; + + /** @var array */ + public array $callStack = []; + + public Normalizer $n0; + public Normalizer $n1; + public bool $ready0 = false; + public bool|null $recursive0 = null; + public Closure $h0; + public Closure $e0; + public ReflectionClass $r0; + public bool $tracked0 = true; + public Normalizer $n2; + public Normalizer $n3; + public bool $ready1 = false; + public bool|null $recursive1 = null; + public Closure $h1; + public Closure $e1; + public ReflectionClass $r1; + public bool $tracked1 = true; + public Normalizer $n4; + public bool $ih0 = false; + public bool $ie0 = false; + public bool $ready2 = false; + public bool|null $recursive2 = null; + public Closure $h2; + public Closure $e2; + public ReflectionClass $r2; + public bool $tracked2 = true; + public Normalizer $n5; + public bool $ih3 = false; + public bool $ie3 = false; + public bool $ready3 = false; + public bool|null $recursive3 = null; + public Closure $h3; + public Closure $e3; + public ReflectionClass $r3; + public bool $tracked3 = true; + public Normalizer $n6; + public bool $ih2 = false; + public bool $ie2 = false; + public bool $ready4 = false; + public bool|null $recursive4 = null; + public Closure $h4; + public Closure $e4; + public ReflectionClass $r4; + public bool $tracked4 = true; + public Normalizer $n7; + public bool $ih1 = false; + public bool $ie1 = false; + public bool $ready5 = false; + public bool|null $recursive5 = null; + public Closure $h5; + public Closure $e5; + public ReflectionClass $r5; + public bool $tracked5 = true; + public Normalizer $n8; + public bool $ready6 = false; + public bool|null $recursive6 = null; + public Closure $h6; + public Closure $e6; + public ReflectionClass $r6; + public bool $tracked6 = true; + public Normalizer $n9; + public bool $ih4 = false; + public bool $ie4 = false; + public ReflectionParameter $d0; + public bool $ready7 = false; + public bool|null $recursive7 = null; + public Closure $h7; + public Closure $e7; + public ReflectionClass $r7; + public bool $tracked7 = true; + public bool $ready8 = false; + public bool|null $recursive8 = null; + public Closure $h8; + public Closure $e8; + public ReflectionClass $r8; + public Normalizer $n10; + public Normalizer $n11; + public Normalizer $n12; + public Normalizer $n13; + public bool $ready9 = false; + public bool|null $recursive9 = null; + public Closure $h9; + public Closure $e9; + public ReflectionClass $r9; + public bool $tracked9 = true; + public Normalizer $n14; + public Normalizer $n15; + public Normalizer $n16; + public Normalizer $n17; + public Normalizer $n18; + public bool $ready10 = false; + public bool|null $recursive10 = null; + public Closure $h10; + public Closure $e10; + public ReflectionClass $r10; + public bool $tracked10 = true; + public Normalizer $n19; + public Normalizer $n20; + public Normalizer $n21; + public Normalizer $n22; + public Normalizer $n23; + public Normalizer $n24; + public bool $ready11 = false; + public bool|null $recursive11 = null; + public Closure $h11; + public Closure $e11; + public ReflectionClass $r11; + public bool $tracked11 = true; + public Normalizer $n25; + public Normalizer $n26; + public bool $ih5 = false; + public bool $ie5 = false; + public Normalizer $n29; + public Normalizer $n30; + public Normalizer $n31; + public Normalizer $n32; + public ObjectNormalizer $n33; + public bool $ih6 = false; + public bool $ie6 = false; + public bool $ready12 = false; + public bool|null $recursive12 = null; + public Closure $h12; + public Closure $e12; + public ReflectionClass $r12; + public bool $tracked12 = true; + public Normalizer $n27; + public Normalizer $n28; + public bool $ready13 = false; + public bool|null $recursive13 = null; + public Closure $h13; + public Closure $e13; + public ReflectionClass $r13; + public bool $tracked13 = true; + public bool $ready14 = false; + public bool|null $recursive14 = null; + public Closure $h14; + public Closure $e14; + public ReflectionClass $r14; + public Normalizer $n34; + public Normalizer $n35; + public bool $ready15 = false; + public bool|null $recursive15 = null; + public Closure $h15; + public Closure $e15; + public ReflectionClass $r15; + public bool $tracked15 = true; + public Normalizer $n36; + public bool $ready16 = false; + public bool|null $recursive16 = null; + public Closure $h16; + public Closure $e16; + public ReflectionClass $r16; + public bool $tracked16 = true; + public Normalizer $n37; + public Normalizer $n38; + public bool $ready17 = false; + public bool|null $recursive17 = null; + public Closure $h17; + public Closure $e17; + public ReflectionClass $r17; + public bool $tracked17 = true; + public Normalizer $n39; + public bool $ready18 = false; + public bool|null $recursive18 = null; + public Closure $h18; + public Closure $e18; + public ReflectionClass $r18; + public bool $tracked18 = true; + public Normalizer $n40; + public Normalizer $n41; + public bool $ready19 = false; + public bool|null $recursive19 = null; + public Closure $h19; + public Closure $e19; + public ReflectionClass $r19; + public Closure $hp0; + public Closure $ep1; + public bool $tracked19 = true; + public Normalizer $n42; + public Normalizer $n43; + public bool $ready20 = false; + public bool|null $recursive20 = null; + public Closure $h20; + public Closure $e20; + public ReflectionClass $r20; + public bool $tracked20 = true; + public Normalizer $n44; + public bool $ih7 = false; + public bool $ie7 = false; + public Normalizer $n45; + public ObjectNormalizer $n46; + public bool $ih8 = false; + public bool $ie8 = false; + public bool $ready21 = false; + public bool|null $recursive21 = null; + public Closure $h21; + public Closure $e21; + public ReflectionClass $r21; + public bool $tracked21 = true; + public bool $ready22 = false; + public bool|null $recursive22 = null; + public Closure $h22; + public Closure $e22; + public ReflectionClass $r22; + + public function __construct() + { + $this->h0 = function (array $data, array $context, object|null $object = null): object { $this->init0(); return ($this->h0)($data, $context, $object); }; + $this->e0 = function (object $object, array $context): array { $this->init0(); return ($this->e0)($object, $context); }; + $this->h1 = function (array $data, array $context, object|null $object = null): object { $this->init1(); return ($this->h1)($data, $context, $object); }; + $this->e1 = function (object $object, array $context): array { $this->init1(); return ($this->e1)($object, $context); }; + $this->h2 = function (array $data, array $context, object|null $object = null): object { $this->init2(); return ($this->h2)($data, $context, $object); }; + $this->e2 = function (object $object, array $context): array { $this->init2(); return ($this->e2)($object, $context); }; + $this->h3 = function (array $data, array $context, object|null $object = null): object { $this->init3(); return ($this->h3)($data, $context, $object); }; + $this->e3 = function (object $object, array $context): array { $this->init3(); return ($this->e3)($object, $context); }; + $this->h4 = function (array $data, array $context, object|null $object = null): object { $this->init4(); return ($this->h4)($data, $context, $object); }; + $this->e4 = function (object $object, array $context): array { $this->init4(); return ($this->e4)($object, $context); }; + $this->h5 = function (array $data, array $context, object|null $object = null): object { $this->init5(); return ($this->h5)($data, $context, $object); }; + $this->e5 = function (object $object, array $context): array { $this->init5(); return ($this->e5)($object, $context); }; + $this->h6 = function (array $data, array $context, object|null $object = null): object { $this->init6(); return ($this->h6)($data, $context, $object); }; + $this->e6 = function (object $object, array $context): array { $this->init6(); return ($this->e6)($object, $context); }; + $this->h7 = function (array $data, array $context, object|null $object = null): object { $this->init7(); return ($this->h7)($data, $context, $object); }; + $this->e7 = function (object $object, array $context): array { $this->init7(); return ($this->e7)($object, $context); }; + $this->h8 = function (array $data, array $context, object|null $object = null): object { $this->init8(); return ($this->h8)($data, $context, $object); }; + $this->e8 = function (object $object, array $context): array { $this->init8(); return ($this->e8)($object, $context); }; + $this->h9 = function (array $data, array $context, object|null $object = null): object { $this->init9(); return ($this->h9)($data, $context, $object); }; + $this->e9 = function (object $object, array $context): array { $this->init9(); return ($this->e9)($object, $context); }; + $this->h10 = function (array $data, array $context, object|null $object = null): object { $this->init10(); return ($this->h10)($data, $context, $object); }; + $this->e10 = function (object $object, array $context): array { $this->init10(); return ($this->e10)($object, $context); }; + $this->h11 = function (array $data, array $context, object|null $object = null): object { $this->init11(); return ($this->h11)($data, $context, $object); }; + $this->e11 = function (object $object, array $context): array { $this->init11(); return ($this->e11)($object, $context); }; + $this->h12 = function (array $data, array $context, object|null $object = null): object { $this->init12(); return ($this->h12)($data, $context, $object); }; + $this->e12 = function (object $object, array $context): array { $this->init12(); return ($this->e12)($object, $context); }; + $this->h13 = function (array $data, array $context, object|null $object = null): object { $this->init13(); return ($this->h13)($data, $context, $object); }; + $this->e13 = function (object $object, array $context): array { $this->init13(); return ($this->e13)($object, $context); }; + $this->h14 = function (array $data, array $context, object|null $object = null): object { $this->init14(); return ($this->h14)($data, $context, $object); }; + $this->e14 = function (object $object, array $context): array { $this->init14(); return ($this->e14)($object, $context); }; + $this->h15 = function (array $data, array $context, object|null $object = null): object { $this->init15(); return ($this->h15)($data, $context, $object); }; + $this->e15 = function (object $object, array $context): array { $this->init15(); return ($this->e15)($object, $context); }; + $this->h16 = function (array $data, array $context, object|null $object = null): object { $this->init16(); return ($this->h16)($data, $context, $object); }; + $this->e16 = function (object $object, array $context): array { $this->init16(); return ($this->e16)($object, $context); }; + $this->h17 = function (array $data, array $context, object|null $object = null): object { $this->init17(); return ($this->h17)($data, $context, $object); }; + $this->e17 = function (object $object, array $context): array { $this->init17(); return ($this->e17)($object, $context); }; + $this->h18 = function (array $data, array $context, object|null $object = null): object { $this->init18(); return ($this->h18)($data, $context, $object); }; + $this->e18 = function (object $object, array $context): array { $this->init18(); return ($this->e18)($object, $context); }; + $this->h19 = function (array $data, array $context, object|null $object = null): object { $this->init19(); return ($this->h19)($data, $context, $object); }; + $this->e19 = function (object $object, array $context): array { $this->init19(); return ($this->e19)($object, $context); }; + $this->h20 = function (array $data, array $context, object|null $object = null): object { $this->init20(); return ($this->h20)($data, $context, $object); }; + $this->e20 = function (object $object, array $context): array { $this->init20(); return ($this->e20)($object, $context); }; + $this->h21 = function (array $data, array $context, object|null $object = null): object { $this->init21(); return ($this->h21)($data, $context, $object); }; + $this->e21 = function (object $object, array $context): array { $this->init21(); return ($this->e21)($object, $context); }; + $this->h22 = function (array $data, array $context, object|null $object = null): object { $this->init22(); return ($this->h22)($data, $context, $object); }; + $this->e22 = function (object $object, array $context): array { $this->init22(); return ($this->e22)($object, $context); }; + } + + public function setHydrator(StackHydrator $hydrator): void + { + $this->hydrator = $hydrator; + $this->others = []; + + foreach ($hydrator->middlewares() as $middleware) { + if ($middleware === $this || $middleware instanceof TransformMiddleware) { + continue; + } + + $this->others[] = $middleware; + } + } + + /** Lets the GeneratedHydrator call the generated code directly, skipping the middleware stack. */ + public function compiledHydrator(ClassMetadata $metadata): Closure|null + { + if (!$this->exclusive($metadata, Skip::Hydrate)) { + return null; + } + + switch ($metadata->className) { + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class: + $this->init0(); + + return $this->fastHydrate0(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class: + $this->init1(); + + return $this->fastHydrate1(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class: + $this->init2(); + + return $this->fastHydrate2(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class: + $this->init3(); + + return $this->fastHydrate3(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class: + $this->init4(); + + return $this->fastHydrate4(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class: + $this->init5(); + + return $this->fastHydrate5(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class: + $this->init6(); + + return $this->fastHydrate6(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class: + $this->init7(); + + return $this->fastHydrate7(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class: + $this->init8(); + + return $this->fastHydrate8(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class: + $this->init9(); + + return $this->fastHydrate9(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class: + $this->init10(); + + return $this->fastHydrate10(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class: + $this->init11(); + + return $this->fastHydrate11(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class: + $this->init12(); + + return $this->fastHydrate12(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class: + $this->init13(); + + return $this->fastHydrate13(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class: + $this->init14(); + + return $this->fastHydrate14(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class: + $this->init15(); + + return $this->fastHydrate15(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class: + $this->init16(); + + return $this->fastHydrate16(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class: + $this->init17(); + + return $this->fastHydrate17(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class: + $this->init18(); + + return $this->fastHydrate18(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class: + $this->init19(); + + return $this->fastHydrate19(...); + case \Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class: + $this->init20(); + + return $this->fastHydrate20(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class: + $this->init21(); + + return $this->fastHydrate21(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class: + $this->init22(); + + return $this->fastHydrate22(...); + default: + return null; + } + } + + public function compiledExtractor(ClassMetadata $metadata): Closure|null + { + if (!$this->exclusive($metadata, Skip::Extract)) { + return null; + } + + switch ($metadata->className) { + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class: + $this->init0(); + + return $this->extract0(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class: + $this->init1(); + + return $this->extract1(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class: + $this->init2(); + + return $this->extract2(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class: + $this->init3(); + + return $this->extract3(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class: + $this->init4(); + + return $this->extract4(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class: + $this->init5(); + + return $this->extract5(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class: + $this->init6(); + + return $this->extract6(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class: + $this->init7(); + + return $this->extract7(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class: + $this->init8(); + + return $this->e8; + case \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class: + $this->init9(); + + return $this->extract9(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class: + $this->init10(); + + return $this->extract10(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class: + $this->init11(); + + return $this->extract11(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class: + $this->init12(); + + return $this->extract12(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class: + $this->init13(); + + return $this->extract13(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class: + $this->init14(); + + return $this->extract14(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class: + $this->init15(); + + return $this->extract15(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class: + $this->init16(); + + return $this->extract16(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class: + $this->init17(); + + return $this->extract17(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class: + $this->init18(); + + return $this->e18; + case \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class: + $this->init19(); + + return $this->e19; + case \Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class: + $this->init20(); + + return $this->extract20(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class: + $this->init21(); + + return $this->extract21(...); + case \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class: + $this->init22(); + + return $this->extract22(...); + default: + return null; + } + } + + /** Classes without generated code are left to the TransformMiddleware directly. */ + public function skip(ClassMetadata $metadata): Skip + { + return match ($metadata->className) { + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, + \Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, + \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, + => Skip::None, + default => Skip::Both, + }; + } + + public function hydrate(ClassMetadata $metadata, array $data, array $context, Stack $stack): object + { + $object = null; + $populate = $context; + + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($populate[Hydrator::OBJECT_TO_POPULATE]); + } + + return match ($metadata->className) { + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class => $this->ready0 ? $this->hydrate0($data, $populate, $object) : ($this->h0)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class => $this->ready1 ? $this->hydrate1($data, $populate, $object) : ($this->h1)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class => $this->ready2 ? $this->hydrate2($data, $populate, $object) : ($this->h2)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class => $this->ready3 ? $this->hydrate3($data, $populate, $object) : ($this->h3)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class => $this->ready4 ? $this->hydrate4($data, $populate, $object) : ($this->h4)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class => $this->ready5 ? $this->hydrate5($data, $populate, $object) : ($this->h5)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class => $this->ready6 ? $this->hydrate6($data, $populate, $object) : ($this->h6)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class => $this->ready7 ? $this->hydrate7($data, $populate, $object) : ($this->h7)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class => ($this->h8)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class => $this->ready9 ? $this->hydrate9($data, $populate, $object) : ($this->h9)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class => $this->ready10 ? $this->hydrate10($data, $populate, $object) : ($this->h10)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class => $this->ready11 ? $this->hydrate11($data, $populate, $object) : ($this->h11)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class => $this->ready12 ? $this->hydrate12($data, $populate, $object) : ($this->h12)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class => $this->ready13 ? $this->hydrate13($data, $populate, $object) : ($this->h13)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class => $this->ready14 ? $this->hydrate14($data, $populate, $object) : ($this->h14)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class => $this->ready15 ? $this->hydrate15($data, $populate, $object) : ($this->h15)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class => $this->ready16 ? $this->hydrate16($data, $populate, $object) : ($this->h16)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class => $this->ready17 ? $this->hydrate17($data, $populate, $object) : ($this->h17)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class => ($this->h18)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class => ($this->h19)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class => $this->ready20 ? $this->hydrate20($data, $populate, $object) : ($this->h20)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class => $this->ready21 ? $this->hydrate21($data, $populate, $object) : ($this->h21)($data, $populate, $object), + \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class => $this->ready22 ? $this->hydrate22($data, $populate, $object) : ($this->h22)($data, $populate, $object), + default => $stack->next()->hydrate($metadata, $data, $context, $stack), + }; + } + + public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array + { + return match ($metadata->className) { + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class => $this->ready0 ? $this->extract0($object, $context) : ($this->e0)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class => $this->ready1 ? $this->extract1($object, $context) : ($this->e1)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class => $this->ready2 ? $this->extract2($object, $context) : ($this->e2)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class => $this->ready3 ? $this->extract3($object, $context) : ($this->e3)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class => $this->ready4 ? $this->extract4($object, $context) : ($this->e4)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class => $this->ready5 ? $this->extract5($object, $context) : ($this->e5)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class => $this->ready6 ? $this->extract6($object, $context) : ($this->e6)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class => $this->ready7 ? $this->extract7($object, $context) : ($this->e7)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class => ($this->e8)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class => $this->ready9 ? $this->extract9($object, $context) : ($this->e9)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class => $this->ready10 ? $this->extract10($object, $context) : ($this->e10)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class => $this->ready11 ? $this->extract11($object, $context) : ($this->e11)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class => $this->ready12 ? $this->extract12($object, $context) : ($this->e12)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class => $this->ready13 ? $this->extract13($object, $context) : ($this->e13)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class => $this->ready14 ? $this->extract14($object, $context) : ($this->e14)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class => $this->ready15 ? $this->extract15($object, $context) : ($this->e15)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class => $this->ready16 ? $this->extract16($object, $context) : ($this->e16)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class => $this->ready17 ? $this->extract17($object, $context) : ($this->e17)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class => ($this->e18)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class => ($this->e19)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class => $this->ready20 ? $this->extract20($object, $context) : ($this->e20)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class => $this->ready21 ? $this->extract21($object, $context) : ($this->e21)($object, $context), + \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class => $this->ready22 ? $this->extract22($object, $context) : ($this->e22)($object, $context), + default => $stack->next()->extract($metadata, $object, $context, $stack), + }; + } + + private function init0(): void + { + if ($this->ready0) { return; } + $this->ready0 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, ['profileId' => ['profileId', true], 'email' => ['email', true]]); + $this->r0 = $metadata->reflection; + $this->n0 = $this->normalizer($metadata, 'profileId'); + $this->n1 = $this->normalizer($metadata, 'email'); + $this->h0 = $this->hydrate0(...); + $this->e0 = $this->extract0(...); + $this->tracked0 = $this->recursive0(); + } + + private function init1(): void + { + if ($this->ready1) { return; } + $this->ready1 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, ['profileId' => ['profileId', true], 'email' => ['email', true]]); + $this->r1 = $metadata->reflection; + $this->n2 = $this->normalizer($metadata, 'profileId'); + $this->n3 = $this->normalizer($metadata, 'email'); + $this->h1 = $this->hydrate1(...); + $this->e1 = $this->extract1(...); + $this->tracked1 = $this->recursive1(); + } + + private function init2(): void + { + if ($this->ready2) { return; } + $this->ready2 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class, ['profileCreated' => ['event', true]]); + $this->r2 = $metadata->reflection; + $this->n4 = $this->normalizer($metadata, 'profileCreated'); + $this->ih0 = $this->inlineObject($this->n4, \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, Skip::Hydrate); + $this->ie0 = $this->inlineObject($this->n4, \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, Skip::Extract); + $this->h2 = $this->hydrate2(...); + $this->e2 = $this->extract2(...); + $this->init0(); + $this->tracked2 = $this->recursive2(); + } + + private function init3(): void + { + if ($this->ready3) { return; } + $this->ready3 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, ['to' => ['to', true]]); + $this->r3 = $metadata->reflection; + $this->n5 = $this->normalizer($metadata, 'to'); + $this->ih3 = $this->inlineObject($this->n5, \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, Skip::Hydrate); + $this->ie3 = $this->inlineObject($this->n5, \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, Skip::Extract); + $this->h3 = $this->hydrate3(...); + $this->e3 = $this->extract3(...); + $this->init4(); + $this->tracked3 = $this->recursive3(); + } + + private function init4(): void + { + if ($this->ready4) { return; } + $this->ready4 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, ['to' => ['to', true]]); + $this->r4 = $metadata->reflection; + $this->n6 = $this->normalizer($metadata, 'to'); + $this->ih2 = $this->inlineObject($this->n6, \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, Skip::Hydrate); + $this->ie2 = $this->inlineObject($this->n6, \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, Skip::Extract); + $this->h4 = $this->hydrate4(...); + $this->e4 = $this->extract4(...); + $this->init5(); + $this->tracked4 = $this->recursive4(); + } + + private function init5(): void + { + if ($this->ready5) { return; } + $this->ready5 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, ['to' => ['to', true]]); + $this->r5 = $metadata->reflection; + $this->n7 = $this->normalizer($metadata, 'to'); + $this->ih1 = $this->inlineObject($this->n7, \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, Skip::Hydrate); + $this->ie1 = $this->inlineObject($this->n7, \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, Skip::Extract); + $this->h5 = $this->hydrate5(...); + $this->e5 = $this->extract5(...); + $this->init3(); + $this->tracked5 = $this->recursive5(); + } + + private function init6(): void + { + if ($this->ready6) { return; } + $this->ready6 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class, ['value' => ['value', true]]); + $this->r6 = $metadata->reflection; + $this->n8 = $this->normalizer($metadata, 'value'); + $this->h6 = $this->hydrate6(...); + $this->e6 = $this->extract6(...); + $this->tracked6 = $this->recursive6(); + } + + private function init7(): void + { + if ($this->ready7) { return; } + $this->ready7 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, ['admin' => ['admin', false], 'name' => ['name', false], 'email' => ['email', true]]); + $this->r7 = $metadata->reflection; + $this->d0 = $this->promotedDefault($metadata, 'email'); + $this->n9 = $this->normalizer($metadata, 'email'); + $this->ih4 = $this->inlineObject($this->n9, \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class, Skip::Hydrate); + $this->ie4 = $this->inlineObject($this->n9, \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class, Skip::Extract); + $this->h7 = $this->hydrate7(...); + $this->e7 = $this->extract7(...); + $this->init8(); + $this->tracked7 = $this->recursive7(); + } + + private function init8(): void + { + if ($this->ready8) { return; } + $this->ready8 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class, ['value' => ['value', false]]); + $this->r8 = $metadata->reflection; + $this->h8 = Closure::bind(function (array $data, array $context, object|null $object = null): object { + $object ??= $this->r8->newInstanceWithoutConstructor(); + + if (\array_key_exists('value', $data)) { + $value = $data['value']; + + try { + $object->value = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class, 'value', $e); + } + } + + return $object; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class); + $this->e8 = Closure::bind(function (object $object, array $context): array { + + + return [ + 'value' => $object->value, + ]; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\Email::class); + } + + private function init9(): void + { + if ($this->ready9) { return; } + $this->ready9 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, ['status' => ['status', true], 'dateTimeImmutable' => ['dateTimeImmutable', true], 'dateTime' => ['dateTime', true], 'dateTimeZone' => ['dateTimeZone', true], 'array' => ['array', false]]); + $this->r9 = $metadata->reflection; + $this->n10 = $this->normalizer($metadata, 'status'); + $this->n11 = $this->normalizer($metadata, 'dateTimeImmutable'); + $this->n12 = $this->normalizer($metadata, 'dateTime'); + $this->n13 = $this->normalizer($metadata, 'dateTimeZone'); + $this->h9 = $this->hydrate9(...); + $this->e9 = $this->extract9(...); + $this->tracked9 = $this->recursive9(); + } + + private function init10(): void + { + if ($this->ready10) { return; } + $this->ready10 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, ['status' => ['status', true], 'dateTimeImmutable' => ['dateTimeImmutable', true], 'dateTime' => ['dateTime', true], 'dateTimeZone' => ['dateTimeZone', true], 'profileId' => ['profileId', true]]); + $this->r10 = $metadata->reflection; + $this->n14 = $this->normalizer($metadata, 'status'); + $this->n15 = $this->normalizer($metadata, 'dateTimeImmutable'); + $this->n16 = $this->normalizer($metadata, 'dateTime'); + $this->n17 = $this->normalizer($metadata, 'dateTimeZone'); + $this->n18 = $this->normalizer($metadata, 'profileId'); + $this->h10 = $this->hydrate10(...); + $this->e10 = $this->extract10(...); + $this->tracked10 = $this->recursive10(); + } + + private function init11(): void + { + if ($this->ready11) { return; } + $this->ready11 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, ['defaultArray' => ['defaultArray', true], 'listArray' => ['listArray', true], 'iterableArray' => ['iterableArray', true], 'hashMap' => ['hashMap', true], 'nested' => ['nested', true], 'jsonArray' => ['jsonArray', false], 'shapeArray' => ['shapeArray', true]]); + $this->r11 = $metadata->reflection; + $this->n19 = $this->normalizer($metadata, 'defaultArray'); + $this->n20 = $this->normalizer($metadata, 'listArray'); + $this->n21 = $this->normalizer($metadata, 'iterableArray'); + $this->n22 = $this->normalizer($metadata, 'hashMap'); + $this->n23 = $this->normalizer($metadata, 'nested'); + $this->n24 = $this->normalizer($metadata, 'shapeArray'); + $this->h11 = $this->hydrate11(...); + $this->e11 = $this->extract11(...); + $this->tracked11 = $this->recursive11(); + } + + private function init12(): void + { + if ($this->ready12) { return; } + $this->ready12 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, ['status' => ['status', true], 'profileCreated' => ['profileCreated', true], 'defaultArray' => ['defaultArray', true], 'listArray' => ['listArray', true], 'iterableArray' => ['iterableArray', true], 'skillsHashMap' => ['skillsHashMap', true], 'jsonArray' => ['jsonArray', false]]); + $this->r12 = $metadata->reflection; + $this->n25 = $this->normalizer($metadata, 'status'); + $this->n26 = $this->normalizer($metadata, 'profileCreated'); + $this->ih5 = $this->inlineObject($this->n26, \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, Skip::Hydrate); + $this->ie5 = $this->inlineObject($this->n26, \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, Skip::Extract); + $this->n29 = $this->normalizer($metadata, 'defaultArray'); + $this->n30 = $this->normalizer($metadata, 'listArray'); + $this->n31 = $this->normalizer($metadata, 'iterableArray'); + $this->n32 = $this->normalizer($metadata, 'skillsHashMap'); + $this->ih6 = $this->inlineArray($this->n32, \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class, Skip::Hydrate); + $this->ie6 = $this->inlineArray($this->n32, \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class, Skip::Extract); + if ($this->ih6 || $this->ie6) { $this->n33 = $this->n32->innerNormalizer(); } + $this->h12 = $this->hydrate12(...); + $this->e12 = $this->extract12(...); + $this->init13(); + $this->init14(); + $this->tracked12 = $this->recursive12(); + } + + private function init13(): void + { + if ($this->ready13) { return; } + $this->ready13 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, ['profileId' => ['profileId', true], 'email' => ['email', true]]); + $this->r13 = $metadata->reflection; + $this->n27 = $this->normalizer($metadata, 'profileId'); + $this->n28 = $this->normalizer($metadata, 'email'); + $this->h13 = $this->hydrate13(...); + $this->e13 = $this->extract13(...); + $this->tracked13 = $this->recursive13(); + } + + private function init14(): void + { + if ($this->ready14) { return; } + $this->ready14 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class, ['name' => ['name', false]]); + $this->r14 = $metadata->reflection; + $this->h14 = $this->hydrate14(...); + $this->e14 = $this->extract14(...); + } + + private function init15(): void + { + if ($this->ready15) { return; } + $this->ready15 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, ['profileId' => ['profileId', true], 'email' => ['email', true]]); + $this->r15 = $metadata->reflection; + $this->n34 = $this->normalizer($metadata, 'profileId'); + $this->n35 = $this->normalizer($metadata, 'email'); + $this->h15 = $this->hydrate15(...); + $this->e15 = $this->extract15(...); + $this->tracked15 = $this->recursive15(); + } + + private function init16(): void + { + if ($this->ready16) { return; } + $this->ready16 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class, ['email' => ['email', true]]); + $this->r16 = $metadata->reflection; + $this->n36 = $this->normalizer($metadata, 'email'); + $this->h16 = $this->hydrate16(...); + $this->e16 = $this->extract16(...); + $this->tracked16 = $this->recursive16(); + } + + private function init17(): void + { + if ($this->ready17) { return; } + $this->ready17 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, ['profileId' => ['profileId', true], 'valueObject' => ['valueObject', true]]); + $this->r17 = $metadata->reflection; + $this->n37 = $this->normalizer($metadata, 'profileId'); + $this->n38 = $this->normalizer($metadata, 'valueObject'); + $this->h17 = $this->hydrate17(...); + $this->e17 = $this->extract17(...); + $this->tracked17 = $this->recursive17(); + } + + private function init18(): void + { + if ($this->ready18) { return; } + $this->ready18 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, ['profileId' => ['profileId', true], 'name' => ['name', false], 'age' => ['age', false]]); + $this->r18 = $metadata->reflection; + $this->n39 = $this->normalizer($metadata, 'profileId'); + $this->h18 = Closure::bind(function (array $data, array $context, object|null $object = null): object { + $object ??= $this->r18->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n39->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, 'profileId', $this->n39::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, 'profileId', $e); + } + } + + if (\array_key_exists('name', $data)) { + $value = $data['name']; + + try { + $object->name = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, 'name', $e); + } + } + + if (\array_key_exists('age', $data)) { + $value = $data['age']; + + try { + $object->age = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, 'age', $e); + } + } else { + $object->age = 42; + } + + return $object; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class); + $this->e18 = Closure::bind(function (object $object, array $context): array { + if ($this->tracked18) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class; + + try { + try { + $v_profileId = $this->n39->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, 'profileId', $this->n39::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'name' => $object->name, + 'age' => $object->age, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n39->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class, 'profileId', $this->n39::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'name' => $object->name, + 'age' => $object->age, + ]; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivatePropertiesDto::class); + $this->tracked18 = $this->recursive18(); + } + + private function init19(): void + { + if ($this->ready19) { return; } + $this->ready19 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, ['profileId' => ['profileId', true], 'name' => ['name', false], 'email' => ['email', true], 'note' => ['note', false]]); + $this->r19 = $metadata->reflection; + $this->n40 = $this->normalizer($metadata, 'profileId'); + $this->n41 = $this->normalizer($metadata, 'email'); + $this->hp0 = Closure::bind(function (object $object, array $data, array $context): void { + if (\array_key_exists('email', $data)) { + try { + $value = $this->n41->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'email', $this->n41::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'email', $e); + } + } + if (\array_key_exists('note', $data)) { + $value = $data['note']; + + try { + $object->note = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'note', $e); + } + } + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateParentDto::class); + $this->h19 = Closure::bind(function (array $data, array $context, object|null $object = null): object { + $object ??= $this->r19->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n40->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'profileId', $this->n40::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'profileId', $e); + } + } + + if (\array_key_exists('name', $data)) { + $value = $data['name']; + + try { + $object->name = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'name', $e); + } + } + + ($this->hp0)($object, $data, $context); + + return $object; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class); + $this->ep1 = Closure::bind(function (object $object, array $context): array { + try { + $v_email = $this->n41->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'email', $this->n41::class, $e); + } + return [$v_email, $object->note]; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateParentDto::class); + $this->e19 = Closure::bind(function (object $object, array $context): array { + if ($this->tracked19) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class; + + try { + [$v_email, $v_note] = ($this->ep1)($object, $context); + + try { + $v_profileId = $this->n40->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'profileId', $this->n40::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'name' => $object->name, + 'email' => $v_email, + 'note' => $v_note, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + [$v_email, $v_note] = ($this->ep1)($object, $context); + + try { + $v_profileId = $this->n40->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class, 'profileId', $this->n40::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'name' => $object->name, + 'email' => $v_email, + 'note' => $v_note, + ]; + }, $this, \Patchlevel\Hydrator\Tests\Unit\Fixture\PrivateChildDto::class); + $this->tracked19 = $this->recursive19(); + } + + private function init20(): void + { + if ($this->ready20) { return; } + $this->ready20 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, ['profileId' => ['id', true], 'email' => ['email', true]]); + $this->r20 = $metadata->reflection; + $this->n42 = $this->normalizer($metadata, 'profileId'); + $this->n43 = $this->normalizer($metadata, 'email'); + $this->h20 = $this->hydrate20(...); + $this->e20 = $this->extract20(...); + $this->tracked20 = $this->recursive20(); + } + + private function init21(): void + { + if ($this->ready21) { return; } + $this->ready21 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, ['child' => ['child', true], 'items' => ['items', true]]); + $this->r21 = $metadata->reflection; + $this->n44 = $this->normalizer($metadata, 'child'); + $this->ih7 = $this->inlineObject($this->n44, \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, Skip::Hydrate); + $this->ie7 = $this->inlineObject($this->n44, \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, Skip::Extract); + $this->n45 = $this->normalizer($metadata, 'items'); + $this->ih8 = $this->inlineArray($this->n45, \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, Skip::Hydrate); + $this->ie8 = $this->inlineArray($this->n45, \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, Skip::Extract); + if ($this->ih8 || $this->ie8) { $this->n46 = $this->n45->innerNormalizer(); } + $this->h21 = $this->hydrate21(...); + $this->e21 = $this->extract21(...); + $this->init22(); + $this->tracked21 = $this->recursive21(); + } + + private function init22(): void + { + if ($this->ready22) { return; } + $this->ready22 = true; + $metadata = $this->metadata(\Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, ['name' => ['name', false]]); + $this->r22 = $metadata->reflection; + $this->h22 = $this->hydrate22(...); + $this->e22 = $this->extract22(...); + } + + public function fastHydrate0(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate0($data, $context, $object); + } + + return $this->hydrate0($data, $context); + } + + public function hydrate0(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r0->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n0->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'profileId', $this->n0::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'profileId', $e); + } + } + + if (\array_key_exists('email', $data)) { + try { + $value = $this->n1->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'email', $this->n1::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'email', $e); + } + } + + return $object; + } + + public function extract0(object $object, array $context): array + { + if ($this->tracked0) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class; + + try { + try { + $v_profileId = $this->n0->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'profileId', $this->n0::class, $e); + } + + try { + $v_email = $this->n1->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'email', $this->n1::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n0->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'profileId', $this->n0::class, $e); + } + + try { + $v_email = $this->n1->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated::class, 'email', $this->n1::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } + + private function recursive0(): bool + { + if ($this->recursive0 !== null) { + return $this->recursive0; + } + + $this->recursive0 = true; + + return $this->recursive0 = $this->n0 instanceof HydratorAwareNormalizer + || $this->n1 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate1(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate1($data, $context, $object); + } + + return $this->hydrate1($data, $context); + } + + public function hydrate1(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r1->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n2->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'profileId', $this->n2::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'profileId', $e); + } + } + + if (\array_key_exists('email', $data)) { + try { + $value = $this->n3->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'email', $this->n3::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'email', $e); + } + } + + return $object; + } + + public function extract1(object $object, array $context): array + { + if ($this->tracked1) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class; + + try { + try { + $v_profileId = $this->n2->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'profileId', $this->n2::class, $e); + } + + try { + $v_email = $this->n3->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'email', $this->n3::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n2->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'profileId', $this->n2::class, $e); + } + + try { + $v_email = $this->n3->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto::class, 'email', $this->n3::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } + + private function recursive1(): bool + { + if ($this->recursive1 !== null) { + return $this->recursive1; + } + + $this->recursive1 = true; + + return $this->recursive1 = $this->n2 instanceof HydratorAwareNormalizer + || $this->n3 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate2(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate2($data, $context, $object); + } + + return $this->hydrate2($data, $context); + } + + public function hydrate2(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r2->newInstanceWithoutConstructor(); + + if (\array_key_exists('event', $data)) { + try { + $value = $data['event']; + + if ($this->ih0 && \is_array($value)) { + $value = $this->hydrate0($value, $context); + } else { + $value = $this->n4->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class, 'profileCreated', $this->n4::class, $e); + } + + try { + $object->profileCreated = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class, 'profileCreated', $e); + } + } + + return $object; + } + + public function extract2(object $object, array $context): array + { + if ($this->tracked2) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class; + + try { + try { + $value = $object->profileCreated; + $v_profileCreated = $this->ie0 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated ? $this->extract0($value, $context) : $this->n4->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class, 'profileCreated', $this->n4::class, $e); + } + + return [ + 'event' => $v_profileCreated, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $value = $object->profileCreated; + $v_profileCreated = $this->ie0 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreated ? $this->extract0($value, $context) : $this->n4->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWrapper::class, 'profileCreated', $this->n4::class, $e); + } + + return [ + 'event' => $v_profileCreated, + ]; + } + + private function recursive2(): bool + { + if ($this->recursive2 !== null) { + return $this->recursive2; + } + + $this->recursive2 = true; + + return $this->recursive2 = ($this->ie0 ? $this->recursive0() : $this->n4 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate3(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate3($data, $context, $object); + } + + return $this->hydrate3($data, $context); + } + + public function hydrate3(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r3->newInstanceWithoutConstructor(); + + if (\array_key_exists('to', $data)) { + try { + $value = $data['to']; + + if ($this->ih3 && \is_array($value)) { + $value = $this->hydrate4($value, $context); + } else { + $value = $this->n5->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, 'to', $this->n5::class, $e); + } + + try { + $object->to = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, 'to', $e); + } + } + + return $object; + } + + public function extract3(object $object, array $context): array + { + if ($this->tracked3) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class; + + try { + try { + $value = $object->to; + $v_to = $this->ie3 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto ? $this->extract4($value, $context) : $this->n5->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, 'to', $this->n5::class, $e); + } + + return [ + 'to' => $v_to, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $value = $object->to; + $v_to = $this->ie3 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto ? $this->extract4($value, $context) : $this->n5->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto::class, 'to', $this->n5::class, $e); + } + + return [ + 'to' => $v_to, + ]; + } + + private function recursive3(): bool + { + if ($this->recursive3 !== null) { + return $this->recursive3; + } + + $this->recursive3 = true; + + return $this->recursive3 = ($this->ie3 ? $this->recursive4() : $this->n5 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate4(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate4($data, $context, $object); + } + + return $this->hydrate4($data, $context); + } + + public function hydrate4(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r4->newInstanceWithoutConstructor(); + + if (\array_key_exists('to', $data)) { + try { + $value = $data['to']; + + if ($this->ih2 && \is_array($value)) { + $value = $this->hydrate5($value, $context); + } else { + $value = $this->n6->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, 'to', $this->n6::class, $e); + } + + try { + $object->to = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, 'to', $e); + } + } + + return $object; + } + + public function extract4(object $object, array $context): array + { + if ($this->tracked4) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class; + + try { + try { + $value = $object->to; + $v_to = $this->ie2 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto ? $this->extract5($value, $context) : $this->n6->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, 'to', $this->n6::class, $e); + } + + return [ + 'to' => $v_to, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $value = $object->to; + $v_to = $this->ie2 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto ? $this->extract5($value, $context) : $this->n6->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto::class, 'to', $this->n6::class, $e); + } + + return [ + 'to' => $v_to, + ]; + } + + private function recursive4(): bool + { + if ($this->recursive4 !== null) { + return $this->recursive4; + } + + $this->recursive4 = true; + + return $this->recursive4 = ($this->ie2 ? $this->recursive5() : $this->n6 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate5(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate5($data, $context, $object); + } + + return $this->hydrate5($data, $context); + } + + public function hydrate5(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r5->newInstanceWithoutConstructor(); + + if (\array_key_exists('to', $data)) { + try { + $value = $data['to']; + + if ($this->ih1 && \is_array($value)) { + $value = $this->hydrate3($value, $context); + } else { + $value = $this->n7->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, 'to', $this->n7::class, $e); + } + + try { + $object->to = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, 'to', $e); + } + } + + return $object; + } + + public function extract5(object $object, array $context): array + { + if ($this->tracked5) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class; + + try { + try { + $value = $object->to; + $v_to = $this->ie1 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto ? $this->extract3($value, $context) : $this->n7->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, 'to', $this->n7::class, $e); + } + + return [ + 'to' => $v_to, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $value = $object->to; + $v_to = $this->ie1 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto ? $this->extract3($value, $context) : $this->n7->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto::class, 'to', $this->n7::class, $e); + } + + return [ + 'to' => $v_to, + ]; + } + + private function recursive5(): bool + { + if ($this->recursive5 !== null) { + return $this->recursive5; + } + + $this->recursive5 = true; + + return $this->recursive5 = ($this->ie1 ? $this->recursive3() : $this->n7 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate6(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate6($data, $context, $object); + } + + return $this->hydrate6($data, $context); + } + + public function hydrate6(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r6->newInstanceWithoutConstructor(); + + if (\array_key_exists('value', $data)) { + try { + $value = $this->n8->denormalize($data['value'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class, 'value', $this->n8::class, $e); + } + + try { + $object->value = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class, 'value', $e); + } + } + + return $object; + } + + public function extract6(object $object, array $context): array + { + if ($this->tracked6) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class; + + try { + try { + $v_value = $this->n8->normalize($object->value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class, 'value', $this->n8::class, $e); + } + + return [ + 'value' => $v_value, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_value = $this->n8->normalize($object->value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto::class, 'value', $this->n8::class, $e); + } + + return [ + 'value' => $v_value, + ]; + } + + private function recursive6(): bool + { + if ($this->recursive6 !== null) { + return $this->recursive6; + } + + $this->recursive6 = true; + + return $this->recursive6 = $this->n8 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate7(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate7($data, $context, $object); + } + + return $this->hydrate7($data, $context); + } + + public function hydrate7(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r7->newInstanceWithoutConstructor(); + + if (\array_key_exists('admin', $data)) { + $value = $data['admin']; + + try { + $object->admin = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, 'admin', $e); + } + } + + if (\array_key_exists('name', $data)) { + $value = $data['name']; + + try { + $object->name = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, 'name', $e); + } + } + + if (\array_key_exists('email', $data)) { + try { + $value = $data['email']; + + if ($this->ih4 && \is_array($value)) { + $value = ($this->h8)($value, $context); + } else { + $value = $this->n9->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, 'email', $this->n9::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, 'email', $e); + } + } else { + $object->email = $this->d0->getDefaultValue(); + } + + return $object; + } + + public function extract7(object $object, array $context): array + { + if ($this->tracked7) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class; + + try { + try { + $value = $object->email; + $v_email = $this->ie4 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Email ? ($this->e8)($value, $context) : $this->n9->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, 'email', $this->n9::class, $e); + } + + return [ + 'admin' => $object->admin, + 'name' => $object->name, + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $value = $object->email; + $v_email = $this->ie4 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Email ? ($this->e8)($value, $context) : $this->n9->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto::class, 'email', $this->n9::class, $e); + } + + return [ + 'admin' => $object->admin, + 'name' => $object->name, + 'email' => $v_email, + ]; + } + + private function recursive7(): bool + { + if ($this->recursive7 !== null) { + return $this->recursive7; + } + + $this->recursive7 = true; + + return $this->recursive7 = ($this->ie4 ? false : $this->n9 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate8(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return ($this->h8)($data, $context, $object); + } + + return ($this->h8)($data, $context); + } + + public function fastHydrate9(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate9($data, $context, $object); + } + + return $this->hydrate9($data, $context); + } + + public function hydrate9(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r9->newInstanceWithoutConstructor(); + + if (\array_key_exists('status', $data)) { + try { + $value = $this->n10->denormalize($data['status'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'status', $this->n10::class, $e); + } + + try { + $object->status = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'status', $e); + } + } + + if (\array_key_exists('dateTimeImmutable', $data)) { + try { + $value = $this->n11->denormalize($data['dateTimeImmutable'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeImmutable', $this->n11::class, $e); + } + + try { + $object->dateTimeImmutable = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeImmutable', $e); + } + } + + if (\array_key_exists('dateTime', $data)) { + try { + $value = $this->n12->denormalize($data['dateTime'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTime', $this->n12::class, $e); + } + + try { + $object->dateTime = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTime', $e); + } + } + + if (\array_key_exists('dateTimeZone', $data)) { + try { + $value = $this->n13->denormalize($data['dateTimeZone'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeZone', $this->n13::class, $e); + } + + try { + $object->dateTimeZone = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeZone', $e); + } + } + + if (\array_key_exists('array', $data)) { + $value = $data['array']; + + try { + $object->array = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'array', $e); + } + } + + return $object; + } + + public function extract9(object $object, array $context): array + { + if ($this->tracked9) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class; + + try { + try { + $v_status = $this->n10->normalize($object->status, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'status', $this->n10::class, $e); + } + + try { + $v_dateTimeImmutable = $this->n11->normalize($object->dateTimeImmutable, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeImmutable', $this->n11::class, $e); + } + + try { + $v_dateTime = $this->n12->normalize($object->dateTime, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTime', $this->n12::class, $e); + } + + try { + $v_dateTimeZone = $this->n13->normalize($object->dateTimeZone, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeZone', $this->n13::class, $e); + } + + return [ + 'status' => $v_status, + 'dateTimeImmutable' => $v_dateTimeImmutable, + 'dateTime' => $v_dateTime, + 'dateTimeZone' => $v_dateTimeZone, + 'array' => $object->array, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_status = $this->n10->normalize($object->status, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'status', $this->n10::class, $e); + } + + try { + $v_dateTimeImmutable = $this->n11->normalize($object->dateTimeImmutable, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeImmutable', $this->n11::class, $e); + } + + try { + $v_dateTime = $this->n12->normalize($object->dateTime, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTime', $this->n12::class, $e); + } + + try { + $v_dateTimeZone = $this->n13->normalize($object->dateTimeZone, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto::class, 'dateTimeZone', $this->n13::class, $e); + } + + return [ + 'status' => $v_status, + 'dateTimeImmutable' => $v_dateTimeImmutable, + 'dateTime' => $v_dateTime, + 'dateTimeZone' => $v_dateTimeZone, + 'array' => $object->array, + ]; + } + + private function recursive9(): bool + { + if ($this->recursive9 !== null) { + return $this->recursive9; + } + + $this->recursive9 = true; + + return $this->recursive9 = $this->n10 instanceof HydratorAwareNormalizer + || $this->n11 instanceof HydratorAwareNormalizer + || $this->n12 instanceof HydratorAwareNormalizer + || $this->n13 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate10(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate10($data, $context, $object); + } + + return $this->hydrate10($data, $context); + } + + public function hydrate10(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r10->newInstanceWithoutConstructor(); + + if (\array_key_exists('status', $data)) { + try { + $value = $this->n14->denormalize($data['status'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'status', $this->n14::class, $e); + } + + try { + $object->status = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'status', $e); + } + } + + if (\array_key_exists('dateTimeImmutable', $data)) { + try { + $value = $this->n15->denormalize($data['dateTimeImmutable'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeImmutable', $this->n15::class, $e); + } + + try { + $object->dateTimeImmutable = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeImmutable', $e); + } + } + + if (\array_key_exists('dateTime', $data)) { + try { + $value = $this->n16->denormalize($data['dateTime'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTime', $this->n16::class, $e); + } + + try { + $object->dateTime = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTime', $e); + } + } else { + $object->dateTime = NULL; + } + + if (\array_key_exists('dateTimeZone', $data)) { + try { + $value = $this->n17->denormalize($data['dateTimeZone'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeZone', $this->n17::class, $e); + } + + try { + $object->dateTimeZone = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeZone', $e); + } + } else { + $object->dateTimeZone = NULL; + } + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n18->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'profileId', $this->n18::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'profileId', $e); + } + } else { + $object->profileId = NULL; + } + + return $object; + } + + public function extract10(object $object, array $context): array + { + if ($this->tracked10) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class; + + try { + try { + $v_status = $this->n14->normalize($object->status, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'status', $this->n14::class, $e); + } + + try { + $v_dateTimeImmutable = $this->n15->normalize($object->dateTimeImmutable, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeImmutable', $this->n15::class, $e); + } + + try { + $v_dateTime = $this->n16->normalize($object->dateTime, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTime', $this->n16::class, $e); + } + + try { + $v_dateTimeZone = $this->n17->normalize($object->dateTimeZone, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeZone', $this->n17::class, $e); + } + + try { + $v_profileId = $this->n18->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'profileId', $this->n18::class, $e); + } + + return [ + 'status' => $v_status, + 'dateTimeImmutable' => $v_dateTimeImmutable, + 'dateTime' => $v_dateTime, + 'dateTimeZone' => $v_dateTimeZone, + 'profileId' => $v_profileId, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_status = $this->n14->normalize($object->status, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'status', $this->n14::class, $e); + } + + try { + $v_dateTimeImmutable = $this->n15->normalize($object->dateTimeImmutable, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeImmutable', $this->n15::class, $e); + } + + try { + $v_dateTime = $this->n16->normalize($object->dateTime, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTime', $this->n16::class, $e); + } + + try { + $v_dateTimeZone = $this->n17->normalize($object->dateTimeZone, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'dateTimeZone', $this->n17::class, $e); + } + + try { + $v_profileId = $this->n18->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto::class, 'profileId', $this->n18::class, $e); + } + + return [ + 'status' => $v_status, + 'dateTimeImmutable' => $v_dateTimeImmutable, + 'dateTime' => $v_dateTime, + 'dateTimeZone' => $v_dateTimeZone, + 'profileId' => $v_profileId, + ]; + } + + private function recursive10(): bool + { + if ($this->recursive10 !== null) { + return $this->recursive10; + } + + $this->recursive10 = true; + + return $this->recursive10 = $this->n14 instanceof HydratorAwareNormalizer + || $this->n15 instanceof HydratorAwareNormalizer + || $this->n16 instanceof HydratorAwareNormalizer + || $this->n17 instanceof HydratorAwareNormalizer + || $this->n18 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate11(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate11($data, $context, $object); + } + + return $this->hydrate11($data, $context); + } + + public function hydrate11(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r11->newInstanceWithoutConstructor(); + + if (\array_key_exists('defaultArray', $data)) { + try { + $value = $this->n19->denormalize($data['defaultArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'defaultArray', $this->n19::class, $e); + } + + try { + $object->defaultArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'defaultArray', $e); + } + } else { + $object->defaultArray = []; + } + + if (\array_key_exists('listArray', $data)) { + try { + $value = $this->n20->denormalize($data['listArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'listArray', $this->n20::class, $e); + } + + try { + $object->listArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'listArray', $e); + } + } else { + $object->listArray = []; + } + + if (\array_key_exists('iterableArray', $data)) { + try { + $value = $this->n21->denormalize($data['iterableArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'iterableArray', $this->n21::class, $e); + } + + try { + $object->iterableArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'iterableArray', $e); + } + } else { + $object->iterableArray = []; + } + + if (\array_key_exists('hashMap', $data)) { + try { + $value = $this->n22->denormalize($data['hashMap'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'hashMap', $this->n22::class, $e); + } + + try { + $object->hashMap = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'hashMap', $e); + } + } else { + $object->hashMap = []; + } + + if (\array_key_exists('nested', $data)) { + try { + $value = $this->n23->denormalize($data['nested'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'nested', $this->n23::class, $e); + } + + try { + $object->nested = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'nested', $e); + } + } else { + $object->nested = []; + } + + if (\array_key_exists('jsonArray', $data)) { + $value = $data['jsonArray']; + + try { + $object->jsonArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'jsonArray', $e); + } + } else { + $object->jsonArray = NULL; + } + + if (\array_key_exists('shapeArray', $data)) { + try { + $value = $this->n24->denormalize($data['shapeArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'shapeArray', $this->n24::class, $e); + } + + try { + $object->shapeArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'shapeArray', $e); + } + } else { + $object->shapeArray = NULL; + } + + return $object; + } + + public function extract11(object $object, array $context): array + { + if ($this->tracked11) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class; + + try { + try { + $v_defaultArray = $this->n19->normalize($object->defaultArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'defaultArray', $this->n19::class, $e); + } + + try { + $v_listArray = $this->n20->normalize($object->listArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'listArray', $this->n20::class, $e); + } + + try { + $v_iterableArray = $this->n21->normalize($object->iterableArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'iterableArray', $this->n21::class, $e); + } + + try { + $v_hashMap = $this->n22->normalize($object->hashMap, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'hashMap', $this->n22::class, $e); + } + + try { + $v_nested = $this->n23->normalize($object->nested, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'nested', $this->n23::class, $e); + } + + try { + $v_shapeArray = $this->n24->normalize($object->shapeArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'shapeArray', $this->n24::class, $e); + } + + return [ + 'defaultArray' => $v_defaultArray, + 'listArray' => $v_listArray, + 'iterableArray' => $v_iterableArray, + 'hashMap' => $v_hashMap, + 'nested' => $v_nested, + 'jsonArray' => $object->jsonArray, + 'shapeArray' => $v_shapeArray, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_defaultArray = $this->n19->normalize($object->defaultArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'defaultArray', $this->n19::class, $e); + } + + try { + $v_listArray = $this->n20->normalize($object->listArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'listArray', $this->n20::class, $e); + } + + try { + $v_iterableArray = $this->n21->normalize($object->iterableArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'iterableArray', $this->n21::class, $e); + } + + try { + $v_hashMap = $this->n22->normalize($object->hashMap, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'hashMap', $this->n22::class, $e); + } + + try { + $v_nested = $this->n23->normalize($object->nested, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'nested', $this->n23::class, $e); + } + + try { + $v_shapeArray = $this->n24->normalize($object->shapeArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto::class, 'shapeArray', $this->n24::class, $e); + } + + return [ + 'defaultArray' => $v_defaultArray, + 'listArray' => $v_listArray, + 'iterableArray' => $v_iterableArray, + 'hashMap' => $v_hashMap, + 'nested' => $v_nested, + 'jsonArray' => $object->jsonArray, + 'shapeArray' => $v_shapeArray, + ]; + } + + private function recursive11(): bool + { + if ($this->recursive11 !== null) { + return $this->recursive11; + } + + $this->recursive11 = true; + + return $this->recursive11 = $this->n19 instanceof HydratorAwareNormalizer + || $this->n20 instanceof HydratorAwareNormalizer + || $this->n21 instanceof HydratorAwareNormalizer + || $this->n22 instanceof HydratorAwareNormalizer + || $this->n23 instanceof HydratorAwareNormalizer + || $this->n24 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate12(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate12($data, $context, $object); + } + + return $this->hydrate12($data, $context); + } + + public function hydrate12(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r12->newInstanceWithoutConstructor(); + + if (\array_key_exists('status', $data)) { + try { + $value = $this->n25->denormalize($data['status'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'status', $this->n25::class, $e); + } + + try { + $object->status = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'status', $e); + } + } + + if (\array_key_exists('profileCreated', $data)) { + try { + $value = $data['profileCreated']; + + if ($this->ih5 && \is_array($value)) { + $value = $this->hydrate13($value, $context); + } else { + $value = $this->n26->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'profileCreated', $this->n26::class, $e); + } + + try { + $object->profileCreated = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'profileCreated', $e); + } + } + + if (\array_key_exists('defaultArray', $data)) { + try { + $value = $this->n29->denormalize($data['defaultArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'defaultArray', $this->n29::class, $e); + } + + try { + $object->defaultArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'defaultArray', $e); + } + } else { + $object->defaultArray = []; + } + + if (\array_key_exists('listArray', $data)) { + try { + $value = $this->n30->denormalize($data['listArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'listArray', $this->n30::class, $e); + } + + try { + $object->listArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'listArray', $e); + } + } else { + $object->listArray = []; + } + + if (\array_key_exists('iterableArray', $data)) { + try { + $value = $this->n31->denormalize($data['iterableArray'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'iterableArray', $this->n31::class, $e); + } + + try { + $object->iterableArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'iterableArray', $e); + } + } else { + $object->iterableArray = []; + } + + if (\array_key_exists('skillsHashMap', $data)) { + try { + $value = $data['skillsHashMap']; + + if ($this->ih6 && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = \is_array($item) ? $this->hydrate14($item, $context) : $this->n33->denormalize($item, $context); + } + + $value = $items; + } else { + $value = $this->n32->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'skillsHashMap', $this->n32::class, $e); + } + + try { + $object->skillsHashMap = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'skillsHashMap', $e); + } + } else { + $object->skillsHashMap = []; + } + + if (\array_key_exists('jsonArray', $data)) { + $value = $data['jsonArray']; + + try { + $object->jsonArray = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'jsonArray', $e); + } + } else { + $object->jsonArray = NULL; + } + + return $object; + } + + public function extract12(object $object, array $context): array + { + if ($this->tracked12) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class; + + try { + try { + $v_status = $this->n25->normalize($object->status, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'status', $this->n25::class, $e); + } + + try { + $value = $object->profileCreated; + $v_profileCreated = $this->ie5 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer ? $this->extract13($value, $context) : $this->n26->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'profileCreated', $this->n26::class, $e); + } + + try { + $v_defaultArray = $this->n29->normalize($object->defaultArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'defaultArray', $this->n29::class, $e); + } + + try { + $v_listArray = $this->n30->normalize($object->listArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'listArray', $this->n30::class, $e); + } + + try { + $v_iterableArray = $this->n31->normalize($object->iterableArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'iterableArray', $this->n31::class, $e); + } + + try { + $value = $object->skillsHashMap; + + if ($this->ie6 && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = $item instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill ? $this->extract14($item, $context) : $this->n33->normalize($item, $context); + } + + $v_skillsHashMap = $items; + } else { + $v_skillsHashMap = $this->n32->normalize($value, $context); + } + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'skillsHashMap', $this->n32::class, $e); + } + + return [ + 'status' => $v_status, + 'profileCreated' => $v_profileCreated, + 'defaultArray' => $v_defaultArray, + 'listArray' => $v_listArray, + 'iterableArray' => $v_iterableArray, + 'skillsHashMap' => $v_skillsHashMap, + 'jsonArray' => $object->jsonArray, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_status = $this->n25->normalize($object->status, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'status', $this->n25::class, $e); + } + + try { + $value = $object->profileCreated; + $v_profileCreated = $this->ie5 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer ? $this->extract13($value, $context) : $this->n26->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'profileCreated', $this->n26::class, $e); + } + + try { + $v_defaultArray = $this->n29->normalize($object->defaultArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'defaultArray', $this->n29::class, $e); + } + + try { + $v_listArray = $this->n30->normalize($object->listArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'listArray', $this->n30::class, $e); + } + + try { + $v_iterableArray = $this->n31->normalize($object->iterableArray, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'iterableArray', $this->n31::class, $e); + } + + try { + $value = $object->skillsHashMap; + + if ($this->ie6 && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = $item instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\Skill ? $this->extract14($item, $context) : $this->n33->normalize($item, $context); + } + + $v_skillsHashMap = $items; + } else { + $v_skillsHashMap = $this->n32->normalize($value, $context); + } + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto::class, 'skillsHashMap', $this->n32::class, $e); + } + + return [ + 'status' => $v_status, + 'profileCreated' => $v_profileCreated, + 'defaultArray' => $v_defaultArray, + 'listArray' => $v_listArray, + 'iterableArray' => $v_iterableArray, + 'skillsHashMap' => $v_skillsHashMap, + 'jsonArray' => $object->jsonArray, + ]; + } + + private function recursive12(): bool + { + if ($this->recursive12 !== null) { + return $this->recursive12; + } + + $this->recursive12 = true; + + return $this->recursive12 = $this->n25 instanceof HydratorAwareNormalizer + || ($this->ie5 ? $this->recursive13() : $this->n26 instanceof HydratorAwareNormalizer) + || $this->n29 instanceof HydratorAwareNormalizer + || $this->n30 instanceof HydratorAwareNormalizer + || $this->n31 instanceof HydratorAwareNormalizer + || ($this->ie6 ? false : $this->n32 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate13(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate13($data, $context, $object); + } + + return $this->hydrate13($data, $context); + } + + public function hydrate13(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r13->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n27->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'profileId', $this->n27::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'profileId', $e); + } + } + + if (\array_key_exists('email', $data)) { + try { + $value = $this->n28->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'email', $this->n28::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'email', $e); + } + } + + return $object; + } + + public function extract13(object $object, array $context): array + { + if ($this->tracked13) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class; + + try { + try { + $v_profileId = $this->n27->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'profileId', $this->n27::class, $e); + } + + try { + $v_email = $this->n28->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'email', $this->n28::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n27->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'profileId', $this->n27::class, $e); + } + + try { + $v_email = $this->n28->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithNormalizer::class, 'email', $this->n28::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } + + private function recursive13(): bool + { + if ($this->recursive13 !== null) { + return $this->recursive13; + } + + $this->recursive13 = true; + + return $this->recursive13 = $this->n27 instanceof HydratorAwareNormalizer + || $this->n28 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate14(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate14($data, $context, $object); + } + + return $this->hydrate14($data, $context); + } + + public function hydrate14(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r14->newInstanceWithoutConstructor(); + + if (\array_key_exists('name', $data)) { + $value = $data['name']; + + try { + $object->name = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\Skill::class, 'name', $e); + } + } + + return $object; + } + + public function extract14(object $object, array $context): array + { + + + return [ + 'name' => $object->name, + ]; + } + + public function fastHydrate15(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate15($data, $context, $object); + } + + return $this->hydrate15($data, $context); + } + + public function hydrate15(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r15->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n34->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'profileId', $this->n34::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'profileId', $e); + } + } + + if (\array_key_exists('email', $data)) { + try { + $value = $this->n35->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'email', $this->n35::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'email', $e); + } + } + + return $object; + } + + public function extract15(object $object, array $context): array + { + if ($this->tracked15) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class; + + try { + try { + $v_profileId = $this->n34->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'profileId', $this->n34::class, $e); + } + + try { + $v_email = $this->n35->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'email', $this->n35::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n34->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'profileId', $this->n34::class, $e); + } + + try { + $v_email = $this->n35->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\LazyProfileCreated::class, 'email', $this->n35::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'email' => $v_email, + ]; + } + + private function recursive15(): bool + { + if ($this->recursive15 !== null) { + return $this->recursive15; + } + + $this->recursive15 = true; + + return $this->recursive15 = $this->n34 instanceof HydratorAwareNormalizer + || $this->n35 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate16(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate16($data, $context, $object); + } + + return $this->hydrate16($data, $context); + } + + public function hydrate16(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r16->newInstanceWithoutConstructor(); + + if (\array_key_exists('email', $data)) { + try { + $value = $this->n36->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class, 'email', $this->n36::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class, 'email', $e); + } + } + + return $object; + } + + public function extract16(object $object, array $context): array + { + if ($this->tracked16) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class; + + try { + try { + $v_email = $this->n36->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class, 'email', $this->n36::class, $e); + } + + return [ + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_email = $this->n36->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\WrongNormalizer::class, 'email', $this->n36::class, $e); + } + + return [ + 'email' => $v_email, + ]; + } + + private function recursive16(): bool + { + if ($this->recursive16 !== null) { + return $this->recursive16; + } + + $this->recursive16 = true; + + return $this->recursive16 = $this->n36 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate17(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate17($data, $context, $object); + } + + return $this->hydrate17($data, $context); + } + + public function hydrate17(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r17->newInstanceWithoutConstructor(); + + if (\array_key_exists('profileId', $data)) { + try { + $value = $this->n37->denormalize($data['profileId'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'profileId', $this->n37::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'profileId', $e); + } + } + + if (\array_key_exists('valueObject', $data)) { + try { + $value = $this->n38->denormalize($data['valueObject'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'valueObject', $this->n38::class, $e); + } + + try { + $object->valueObject = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'valueObject', $e); + } + } + + return $object; + } + + public function extract17(object $object, array $context): array + { + if ($this->tracked17) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class; + + try { + try { + $v_profileId = $this->n37->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'profileId', $this->n37::class, $e); + } + + try { + $v_valueObject = $this->n38->normalize($object->valueObject, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'valueObject', $this->n38::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'valueObject' => $v_valueObject, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n37->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'profileId', $this->n37::class, $e); + } + + try { + $v_valueObject = $this->n38->normalize($object->valueObject, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\ProfileCreatedWithInlineNormalizer::class, 'valueObject', $this->n38::class, $e); + } + + return [ + 'profileId' => $v_profileId, + 'valueObject' => $v_valueObject, + ]; + } + + private function recursive17(): bool + { + if ($this->recursive17 !== null) { + return $this->recursive17; + } + + $this->recursive17 = true; + + return $this->recursive17 = $this->n37 instanceof HydratorAwareNormalizer + || $this->n38 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate18(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return ($this->h18)($data, $context, $object); + } + + return ($this->h18)($data, $context); + } + + private function recursive18(): bool + { + if ($this->recursive18 !== null) { + return $this->recursive18; + } + + $this->recursive18 = true; + + return $this->recursive18 = $this->n39 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate19(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return ($this->h19)($data, $context, $object); + } + + return ($this->h19)($data, $context); + } + + private function recursive19(): bool + { + if ($this->recursive19 !== null) { + return $this->recursive19; + } + + $this->recursive19 = true; + + return $this->recursive19 = $this->n40 instanceof HydratorAwareNormalizer + || $this->n41 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate20(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate20($data, $context, $object); + } + + return $this->hydrate20($data, $context); + } + + public function hydrate20(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r20->newInstanceWithoutConstructor(); + + if (\array_key_exists('id', $data)) { + try { + $value = $this->n42->denormalize($data['id'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'profileId', $this->n42::class, $e); + } + + try { + $object->profileId = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'profileId', $e); + } + } + + if (\array_key_exists('email', $data)) { + try { + $value = $this->n43->denormalize($data['email'], $context); + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'email', $this->n43::class, $e); + } + + try { + $object->email = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'email', $e); + } + } + + return $object; + } + + public function extract20(object $object, array $context): array + { + if ($this->tracked20) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class; + + try { + try { + $v_profileId = $this->n42->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'profileId', $this->n42::class, $e); + } + + try { + $v_email = $this->n43->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'email', $this->n43::class, $e); + } + + return [ + 'id' => $v_profileId, + 'email' => $v_email, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $v_profileId = $this->n42->normalize($object->profileId, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'profileId', $this->n42::class, $e); + } + + try { + $v_email = $this->n43->normalize($object->email, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography\Fixture\SensitiveDataProfileCreated::class, 'email', $this->n43::class, $e); + } + + return [ + 'id' => $v_profileId, + 'email' => $v_email, + ]; + } + + private function recursive20(): bool + { + if ($this->recursive20 !== null) { + return $this->recursive20; + } + + $this->recursive20 = true; + + return $this->recursive20 = $this->n42 instanceof HydratorAwareNormalizer + || $this->n43 instanceof HydratorAwareNormalizer; + } + + public function fastHydrate21(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate21($data, $context, $object); + } + + return $this->hydrate21($data, $context); + } + + public function hydrate21(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r21->newInstanceWithoutConstructor(); + + if (\array_key_exists('child', $data)) { + try { + $value = $data['child']; + + if ($this->ih7 && \is_array($value)) { + $value = $this->hydrate22($value, $context); + } else { + $value = $this->n44->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'child', $this->n44::class, $e); + } + + try { + $object->child = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'child', $e); + } + } + + if (\array_key_exists('items', $data)) { + try { + $value = $data['items']; + + if ($this->ih8 && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = \is_array($item) ? $this->hydrate22($item, $context) : $this->n46->denormalize($item, $context); + } + + $value = $items; + } else { + $value = $this->n45->denormalize($value, $context); + } + } catch (Throwable $e) { + throw new DenormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'items', $this->n45::class, $e); + } + + try { + $object->items = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'items', $e); + } + } else { + $object->items = []; + } + + return $object; + } + + public function extract21(object $object, array $context): array + { + if ($this->tracked21) { + $id = \spl_object_id($object); + + if (isset($this->callStack[$id])) { + $references = \array_values($this->callStack); + $references[] = $object::class; + + throw new CircularReference($references); + } + + $this->callStack[$id] = \Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class; + + try { + try { + $value = $object->child; + $v_child = $this->ie7 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture ? $this->extract22($value, $context) : $this->n44->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'child', $this->n44::class, $e); + } + + try { + $value = $object->items; + + if ($this->ie8 && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = $item instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture ? $this->extract22($item, $context) : $this->n46->normalize($item, $context); + } + + $v_items = $items; + } else { + $v_items = $this->n45->normalize($value, $context); + } + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'items', $this->n45::class, $e); + } + + return [ + 'child' => $v_child, + 'items' => $v_items, + ]; + } finally { + unset($this->callStack[$id]); + } + } + + try { + $value = $object->child; + $v_child = $this->ie7 && $value instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture ? $this->extract22($value, $context) : $this->n44->normalize($value, $context); + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'child', $this->n44::class, $e); + } + + try { + $value = $object->items; + + if ($this->ie8 && \is_array($value)) { + $items = []; + + foreach ($value as $key => $item) { + $items[$key] = $item instanceof \Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture ? $this->extract22($item, $context) : $this->n46->normalize($item, $context); + } + + $v_items = $items; + } else { + $v_items = $this->n45->normalize($value, $context); + } + } catch (CircularReference $e) { + throw $e; + } catch (Throwable $e) { + throw new NormalizationFailure(\Patchlevel\Hydrator\Tests\Unit\Fixture\NestedLifecycleDto::class, 'items', $this->n45::class, $e); + } + + return [ + 'child' => $v_child, + 'items' => $v_items, + ]; + } + + private function recursive21(): bool + { + if ($this->recursive21 !== null) { + return $this->recursive21; + } + + $this->recursive21 = true; + + return $this->recursive21 = ($this->ie7 ? false : $this->n44 instanceof HydratorAwareNormalizer) + || ($this->ie8 ? false : $this->n45 instanceof HydratorAwareNormalizer); + } + + public function fastHydrate22(array $data, array $context): object + { + if (\array_key_exists(Hydrator::OBJECT_TO_POPULATE, $context)) { + $object = $context[Hydrator::OBJECT_TO_POPULATE]; + unset($context[Hydrator::OBJECT_TO_POPULATE]); + + return $this->hydrate22($data, $context, $object); + } + + return $this->hydrate22($data, $context); + } + + public function hydrate22(array $data, array $context, object|null $object = null): object + { + $object ??= $this->r22->newInstanceWithoutConstructor(); + + if (\array_key_exists('name', $data)) { + $value = $data['name']; + + try { + $object->name = $value; + } catch (TypeError $e) { + throw new TypeMismatch(\Patchlevel\Hydrator\Tests\Unit\Fixture\LifecycleFixture::class, 'name', $e); + } + } + + return $object; + } + + public function extract22(object $object, array $context): array + { + + + return [ + 'name' => $object->name, + ]; + } + + private function hydrator(): StackHydrator + { + return $this->hydrator ?? throw new HydratorNotSet(self::class); + } + + /** @param array $expected property name => [field name, has normalizer] */ + private function metadata(string $class, array $expected): ClassMetadata + { + $metadata = $this->hydrator()->metadata($class); + $actual = []; + + foreach ($metadata->properties as $property) { + $actual[$property->propertyName] = [$property->fieldName, $property->normalizer !== null]; + } + + if ($actual !== $expected) { + throw new OutdatedGeneratedMiddleware($class); + } + + return $metadata; + } + + private function normalizer(ClassMetadata $metadata, string $property): Normalizer + { + return $metadata->properties[$property]->normalizer ?? throw new OutdatedGeneratedMiddleware($metadata->className); + } + + private function promotedDefault(ClassMetadata $metadata, string $property): ReflectionParameter + { + return $metadata->promotedConstructorDefaults()[$property] ?? throw new OutdatedGeneratedMiddleware($metadata->className); + } + + private function inlineObject(Normalizer $normalizer, string $class, Skip $direction): bool + { + if (!$normalizer instanceof ObjectNormalizer) { + return false; + } + + try { + if ($normalizer->className() !== $class) { + return false; + } + } catch (Throwable) { + return false; + } + + return $this->inlinable($class, $direction); + } + + private function inlineArray(Normalizer $normalizer, string $class, Skip $direction): bool + { + return $normalizer instanceof ArrayNormalizer && $this->inlineObject($normalizer->innerNormalizer(), $class, $direction); + } + + /** + * Nested objects are only inlined when this does not change the behaviour: the nested class must be handled + * by this middleware, must not be lazy and every other middleware must skip the class in this direction. + */ + private function inlinable(string $class, Skip $direction): bool + { + try { + $metadata = $this->hydrator()->metadata($class); + } catch (Throwable) { + return false; + } + + if ($metadata->normalizer !== null) { + return false; + } + + if (\PHP_VERSION_ID >= 80400 && ($metadata->lazy ?? $this->hydrator()->defaultLazy())) { + return false; + } + + return $this->exclusive($metadata, $direction); + } + + /** Whether every other middleware skips the class in this direction, so this middleware is the only one to run. */ + private function exclusive(ClassMetadata $metadata, Skip $direction): bool + { + foreach ($this->others as $middleware) { + if (!$middleware instanceof SkippableMiddleware) { + return false; + } + + $skip = $middleware->skip($metadata); + + if ($skip !== Skip::Both && $skip !== $direction) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/tests/Unit/Fixture/AsymmetricVisibilityDto.php b/tests/Unit/Fixture/AsymmetricVisibilityDto.php new file mode 100644 index 00000000..4d072150 --- /dev/null +++ b/tests/Unit/Fixture/AsymmetricVisibilityDto.php @@ -0,0 +1,16 @@ + $skip */ + public function __construct( + private readonly array $skip = [], + ) { + } + + /** @var array */ + public array $hydrated = []; + + /** @var array */ + public array $extracted = []; + + /** + * @param ClassMetadata $metadata + * @param array $data + * @param array $context + * + * @return T + * + * @template T of object + */ + public function hydrate(ClassMetadata $metadata, array $data, array $context, Stack $stack): object + { + $this->hydrated[$metadata->className] = ($this->hydrated[$metadata->className] ?? 0) + 1; + + return $stack->next()->hydrate($metadata, $data, $context, $stack); + } + + /** + * @param ClassMetadata $metadata + * @param T $object + * @param array $context + * + * @return array + * + * @template T of object + */ + public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array + { + $this->extracted[$metadata->className] = ($this->extracted[$metadata->className] ?? 0) + 1; + + return $stack->next()->extract($metadata, $object, $context, $stack); + } + + /** + * @param ClassMetadata $metadata + * + * @template T of object + */ + public function skip(ClassMetadata $metadata): Skip + { + return $this->skip[$metadata->className] ?? Skip::None; + } +} diff --git a/tests/Unit/Fixture/NestedLifecycleDto.php b/tests/Unit/Fixture/NestedLifecycleDto.php new file mode 100644 index 00000000..994f5d5a --- /dev/null +++ b/tests/Unit/Fixture/NestedLifecycleDto.php @@ -0,0 +1,15 @@ + $items */ + public function __construct( + public LifecycleFixture $child, + public array $items = [], + ) { + } +} diff --git a/tests/Unit/Fixture/PrivateChildDto.php b/tests/Unit/Fixture/PrivateChildDto.php new file mode 100644 index 00000000..6e3cd349 --- /dev/null +++ b/tests/Unit/Fixture/PrivateChildDto.php @@ -0,0 +1,23 @@ +name; + } +} diff --git a/tests/Unit/Fixture/PrivateParentDto.php b/tests/Unit/Fixture/PrivateParentDto.php new file mode 100644 index 00000000..fa2e6d3d --- /dev/null +++ b/tests/Unit/Fixture/PrivateParentDto.php @@ -0,0 +1,25 @@ +email; + } + + public function note(): string + { + return $this->note; + } +} diff --git a/tests/Unit/Fixture/PrivatePropertiesDto.php b/tests/Unit/Fixture/PrivatePropertiesDto.php new file mode 100644 index 00000000..8f0a735b --- /dev/null +++ b/tests/Unit/Fixture/PrivatePropertiesDto.php @@ -0,0 +1,31 @@ +profileId; + } + + public function name(): string + { + return $this->name; + } + + public function age(): int + { + return $this->age; + } +} diff --git a/tests/Unit/StackHydratorBuilderTest.php b/tests/Unit/StackHydratorBuilderTest.php index 274d8dbe..1842c154 100644 --- a/tests/Unit/StackHydratorBuilderTest.php +++ b/tests/Unit/StackHydratorBuilderTest.php @@ -7,9 +7,11 @@ use Patchlevel\Hydrator\Extension; use Patchlevel\Hydrator\Guesser\ChainGuesser; use Patchlevel\Hydrator\Guesser\Guesser; +use Patchlevel\Hydrator\HydratorFactoryAlreadySet; use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory; use Patchlevel\Hydrator\Metadata\EnrichingMetadataFactory; use Patchlevel\Hydrator\Metadata\MetadataEnricher; +use Patchlevel\Hydrator\Metadata\MetadataFactory; use Patchlevel\Hydrator\Metadata\Psr16MetadataFactory; use Patchlevel\Hydrator\Metadata\Psr6MetadataFactory; use Patchlevel\Hydrator\Middleware\Middleware; @@ -128,6 +130,37 @@ public function testUseExtension(): void $builder->useExtension($extension); } + public function testHydratorFactory(): void + { + $subclass = new class (new AttributeMetadataFactory(), [$this->createMock(Middleware::class)]) extends StackHydrator { + }; + + $builder = new StackHydratorBuilder(); + $builder->setHydratorFactory( + static function (MetadataFactory $metadataFactory, array $middlewares, bool $defaultLazy) use ($subclass): StackHydrator { + self::assertInstanceOf(EnrichingMetadataFactory::class, $metadataFactory); + self::assertCount(1, $middlewares); + self::assertTrue($defaultLazy); + + return $subclass; + }, + ); + $builder->addMiddleware($this->createMock(Middleware::class)); + $builder->enableDefaultLazy(); + + self::assertSame($subclass, $builder->build()); + } + + public function testHydratorFactoryCanOnlyBeSetOnce(): void + { + $builder = new StackHydratorBuilder(); + $builder->setHydratorFactory(static fn (MetadataFactory $metadataFactory, array $middlewares, bool $defaultLazy): StackHydrator => new StackHydrator($metadataFactory, $middlewares, $defaultLazy)); + + $this->expectException(HydratorFactoryAlreadySet::class); + + $builder->setHydratorFactory(static fn (MetadataFactory $metadataFactory, array $middlewares, bool $defaultLazy): StackHydrator => new StackHydrator($metadataFactory, $middlewares, $defaultLazy)); + } + public function testCachePsr6(): void { $cache = $this->createMock(CacheItemPoolInterface::class); From 90fa96ab4ce46ccefb8c162addfba5bb1233d3d8 Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Fri, 25 Sep 2026 14:32:29 +0200 Subject: [PATCH 2/2] Update benchmarks --- .github/workflows/benchmark.yml | 62 +- Makefile | 20 +- composer.json | 5 - composer.lock | 6507 +++++++++++++++++ phpbench.json | 77 +- tests/Benchmark/EventSauceHydratorBench.php | 132 - tests/Benchmark/Fixture/ProfileCreated.php | 3 - tests/Benchmark/Fixture/ProfileIdCaster.php | 39 - .../GeneratedEventSauceHydratorBench.php | 145 - tests/Benchmark/GeneratedHydratorBench.php | 40 +- ...GeneratedHydratorWithCryptographyBench.php | 39 +- tests/Benchmark/HydratorBench.php | 135 - .../HydratorWithCryptographyBench.php | 154 - tests/Benchmark/JMSHydratorBench.php | 141 - tests/Benchmark/SerdeHydratorBench.php | 148 - tests/Benchmark/StackHydratorBench.php | 36 +- .../StackHydratorWithCryptographyBench.php | 39 +- ...nch.php => StackHydratorWithLazyBench.php} | 38 +- tests/Benchmark/SymfonyHydratorBench.php | 141 - 19 files changed, 6589 insertions(+), 1312 deletions(-) create mode 100644 composer.lock delete mode 100644 tests/Benchmark/EventSauceHydratorBench.php delete mode 100644 tests/Benchmark/Fixture/ProfileIdCaster.php delete mode 100644 tests/Benchmark/GeneratedEventSauceHydratorBench.php delete mode 100644 tests/Benchmark/HydratorBench.php delete mode 100644 tests/Benchmark/HydratorWithCryptographyBench.php delete mode 100644 tests/Benchmark/JMSHydratorBench.php delete mode 100644 tests/Benchmark/SerdeHydratorBench.php rename tests/Benchmark/{HydratorWithLazyBench.php => StackHydratorWithLazyBench.php} (72%) delete mode 100644 tests/Benchmark/SymfonyHydratorBench.php diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 42d768ee..4a280557 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - ini-values: memory_limit=-1, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M, xdebug.mode=off + ini-values: memory_limit=-1 - name: "Checkout base" uses: actions/checkout@v7 @@ -38,7 +38,7 @@ jobs: dependency-versions: ${{ matrix.dependencies }} - name: "phpbench on base" - run: "vendor/bin/phpbench run tests/Benchmark --progress=none --report=default --tag=base" + run: "vendor/bin/phpbench run tests/Benchmark --progress=none --tag=base" - name: "Checkout" uses: actions/checkout@v7 @@ -50,18 +50,35 @@ jobs: dependency-versions: ${{ matrix.dependencies }} - name: "phpbench diff" + id: bench + continue-on-error: true run: "vendor/bin/phpbench run tests/Benchmark --progress=none --report=diff --ref=base > bench.txt" - - name: "Get Bench Result" - id: phpbench - run: | - echo 'BENCH_RESULT<> $GITHUB_ENV - cat bench.txt >> $GITHUB_ENV - echo 'EOF' >> $GITHUB_ENV - - uses: actions/github-script@v9 with: script: | + const fs = require('fs'); + + // the diff report prints one section per benchmark class: a title line, a console table, a blank line. + // The console table becomes a markdown table by dropping the outer borders and turning the border + // below the header into the separator row. + const sections = fs.readFileSync('bench.txt', 'utf8').trim().split(/\n\s*\n/).map((section) => { + const [title, , header, separator, ...rows] = section.trim().split('\n'); + const table = [header, separator.replace(/\+/g, '|'), ...rows.filter((row) => !row.startsWith('+'))]; + + return `### ${title.replace(/Bench$/, '')}\n\n${table.join('\n')}`; + }).join('\n\n'); + + const failed = ${{ steps.bench.outcome == 'failure' }}; + const commentBody = `Hello :wave: + + Here are the most recent benchmark results, compared against \`${{ github.base_ref }}\`. Time is the mode over the iterations in the \`its\` column, with the relative standard deviation in brackets. + + ${sections} + + ${failed ? ':warning: At least one benchmark got slower than the tolerance in phpbench.json allows.' : ''} + This comment gets updated every time a new commit comes in.`; + // Get the existing comments. const {data: comments} = await github.rest.issues.listComments({ owner: context.repo.owner, @@ -69,27 +86,8 @@ jobs: issue_number: context.payload.number, }) - // Find any comment already made by the bot. - const botComment = comments.find(comment => comment.user.id === 41898282) - const commentBody = ` - - Hello :wave: - -
- here is the most recent benchmark result: - -

- - \`\`\` - ${{ env.BENCH_RESULT }} - \`\`\` - -

-
- - This comment gets update everytime a new commit comes in! - - `; + // Find any comment already made by the bot. + const botComment = comments.find(comment => comment.user.id === 41898282) if (!context.payload.pull_request.head.repo.full_name.startsWith('patchlevel/')) { console.log('Not attempting to write comment on PR from fork'); @@ -110,3 +108,7 @@ jobs: }) } } + + - name: "Fail on regression" + if: steps.bench.outcome == 'failure' + run: "exit 1" diff --git a/Makefile b/Makefile index 49cc8e18..9c351c11 100644 --- a/Makefile +++ b/Makefile @@ -38,22 +38,14 @@ test: phpunit snapshot: vendor ## regenerate the snapshot of the generated middleware UPDATE_SNAPSHOTS=1 vendor/bin/phpunit --no-coverage --filter testGeneratedCodeMatchesSnapshot -# benchmarks need opcache and must not run under xdebug, otherwise the numbers are meaningless -PHPBENCH_OPTS = --php-config='{"opcache.enable_cli": 1, "xdebug.mode": "off", "memory_limit": "-1"}' - .PHONY: benchmark -benchmark: vendor ## run benchmarks - vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --report=default - -.PHONY: benchmark-fast -benchmark-fast: vendor ## run only the fast benchmarks (our hydrators and the generated eventsauce mapper) with 1M objects - vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --iterations=3 --revs=1 --report='{"generator":"expression","cols":["benchmark","subject","mode","rstdev"]}' --filter='Benchmark\\(StackHydratorBench|GeneratedHydratorBench|GeneratedHydratorWithCryptographyBench|HydratorWithCryptographyBench|GeneratedEventSauceHydratorBench)::bench(Hydrate|Extract)1000000Objects$$' - -.PHONY: benchmark-diff-test -benchmark-diff-test: vendor ## run benchmarks - vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --revs=1 --report=default --progress=none --tag=base - vendor/bin/phpbench run tests/Benchmark $(PHPBENCH_OPTS) --revs=1 --report=diff --progress=none --ref=base +benchmark: vendor ## run all benchmarks + vendor/bin/phpbench run --report=diff +.PHONY: benchmark-diff +benchmark-diff: vendor ## run the benchmarks twice and compare the second run against the first, like the CI does + vendor/bin/phpbench run --progress=none --tag=base + vendor/bin/phpbench run --progress=none --report=diff --ref=base .PHONY: docs docs: docs-extract-php docs-php-lint docs-phpcs docs-inject-php ## check and format docs code diff --git a/composer.json b/composer.json index 428f47f8..0f796930 100644 --- a/composer.json +++ b/composer.json @@ -30,10 +30,7 @@ "symfony/type-info": "^7.3.0 || ^8.0.0" }, "require-dev": { - "crell/serde": "^1.5", - "eventsauce/object-hydrator": "^1.8", "infection/infection": "^0.32.4", - "jms/serializer": "^3.32", "patchlevel/coding-standard": "^1.3.0", "phpat/phpat": "^0.12.2", "phpbench/phpbench": "^1.4.3", @@ -41,8 +38,6 @@ "phpstan/phpstan-phpunit": "^2.0.15", "phpunit/phpunit": "^11.5.53", "symfony/cache": "^6.4.0 || ^7.0.0 || ^8.0.0", - "symfony/serializer": "^8.0", - "symfony/property-access": "^8.0", "symfony/var-dumper": "^5.4.29 || ^6.4.0 || ^7.0.0 || ^8.0.0", "wnx/commonmark-markdown-renderer": "^1.6" }, diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..cb32eb05 --- /dev/null +++ b/composer.lock @@ -0,0 +1,6507 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "8325c0e87a9d0da748dae8c076093197", + "packages": [ + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+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/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": "symfony/type-info", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "ceb48db5b38d6a48640c414be0c69d53980ae5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/ceb48db5b38d6a48640c414be0c69d53980ae5c5", + "reference": "ceb48db5b38d6a48640c414be0c69d53980ae5c5", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/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" + } + ], + "packages-dev": [ + { + "name": "colinodell/json5", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/colinodell/json5.git", + "reference": "5724d21bc5c910c2560af1b8915f0cc0163579c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/colinodell/json5/zipball/5724d21bc5c910c2560af1b8915f0cc0163579c8", + "reference": "5724d21bc5c910c2560af1b8915f0cc0163579c8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.0" + }, + "require-dev": { + "mikehaertl/php-shellcommand": "^1.7.0", + "phpstan/phpstan": "^1.10.57", + "scrutinizer/ocular": "^1.9", + "squizlabs/php_codesniffer": "^3.8.1", + "symfony/finder": "^6.0|^7.0", + "symfony/phpunit-bridge": "^7.0.3" + }, + "bin": [ + "bin/json5" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "files": [ + "src/global.php" + ], + "psr-4": { + "ColinODell\\Json5\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Developer" + } + ], + "description": "UTF-8 compatible JSON5 parser for PHP", + "homepage": "https://github.com/colinodell/json5", + "keywords": [ + "JSON5", + "json", + "json5_decode", + "json_decode" + ], + "support": { + "issues": "https://github.com/colinodell/json5/issues", + "source": "https://github.com/colinodell/json5/tree/v3.0.0" + }, + "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://www.patreon.com/colinodell", + "type": "patreon" + } + ], + "time": "2024-02-09T13:06:12+00:00" + }, + { + "name": "composer/pcre", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<2.2.2" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-07T11:47:49+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2026-05-06T08:26:05+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/annotations", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.10.28", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "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": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.2" + }, + "abandoned": true, + "time": "2024-09-05T10:17:24+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e96fe45e92a54233726014a7cc7340abf29bb14c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e96fe45e92a54233726014a7cc7340abf29bb14c", + "reference": "e96fe45e92a54233726014a7cc7340abf29bb14c", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10.5.58 || ^12.5.4" + }, + "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.2" + }, + "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": "2026-06-14T20:44:06+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "infection/abstract-testframework-adapter", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/infection/abstract-testframework-adapter.git", + "reference": "b24bf3e850f70cd20a10621f08c3cef66f147ac8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/abstract-testframework-adapter/zipball/b24bf3e850f70cd20a10621f08c3cef66f147ac8", + "reference": "b24bf3e850f70cd20a10621f08c3cef66f147ac8", + "shasum": "" + }, + "require": { + "php": "^8.3" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.18", + "fidry/makefile": "^1.0", + "friendsofphp/php-cs-fixer": "^3.95.2", + "phpunit/phpunit": "^12.0 || ^13.0", + "rector/rector": "^2.4.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\AbstractTestFramework\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Abstract Test Framework Adapter for Infection", + "support": { + "issues": "https://github.com/infection/abstract-testframework-adapter/issues", + "source": "https://github.com/infection/abstract-testframework-adapter/tree/0.5.1" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2026-05-28T19:10:29+00:00" + }, + { + "name": "infection/extension-installer", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/infection/extension-installer.git", + "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/extension-installer/zipball/9b351d2910b9a23ab4815542e93d541e0ca0cdcf", + "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.9 || ^2.0", + "friendsofphp/php-cs-fixer": "^2.18, <2.19", + "infection/infection": "^0.15.2", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.10", + "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpstan/phpstan-webmozart-assert": "^0.12.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.8" + }, + "type": "composer-plugin", + "extra": { + "class": "Infection\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Infection\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Infection Extension Installer", + "support": { + "issues": "https://github.com/infection/extension-installer/issues", + "source": "https://github.com/infection/extension-installer/tree/0.1.2" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2021-10-20T22:08:34+00:00" + }, + { + "name": "infection/include-interceptor", + "version": "0.2.5", + "source": { + "type": "git", + "url": "https://github.com/infection/include-interceptor.git", + "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/include-interceptor/zipball/0cc76d95a79d9832d74e74492b0a30139904bdf7", + "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7", + "shasum": "" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "infection/infection": "^0.15.0", + "phan/phan": "^2.4 || ^3", + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "^0.12.8", + "phpunit/phpunit": "^8.5", + "vimeo/psalm": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\StreamWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Stream Wrapper: Include Interceptor. Allows to replace included (autoloaded) file with another one.", + "support": { + "issues": "https://github.com/infection/include-interceptor/issues", + "source": "https://github.com/infection/include-interceptor/tree/0.2.5" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2021-08-09T10:03:57+00:00" + }, + { + "name": "infection/infection", + "version": "0.32.7", + "source": { + "type": "git", + "url": "https://github.com/infection/infection.git", + "reference": "066ee69f1e8e6dec8965a79d5ba020656c590b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/infection/zipball/066ee69f1e8e6dec8965a79d5ba020656c590b5e", + "reference": "066ee69f1e8e6dec8965a79d5ba020656c590b5e", + "shasum": "" + }, + "require": { + "colinodell/json5": "^3.0", + "composer-runtime-api": "^2.0", + "composer/xdebug-handler": "^3.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "fidry/cpu-core-counter": "^1.0", + "infection/abstract-testframework-adapter": "^0.5.0", + "infection/extension-installer": "^0.1.0", + "infection/include-interceptor": "^0.2.5", + "infection/mutator": "^0.4", + "justinrainbow/json-schema": "^6.0", + "nikic/php-parser": "^5.6.2", + "ondram/ci-detector": "^4.1.0", + "php": "^8.3", + "psr/log": "^2.0 || ^3.0", + "sanmai/di-container": "^0.1.12", + "sanmai/duoclock": "^0.1.0", + "sanmai/later": "^0.1.7", + "sanmai/pipeline": "^7.2", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^6.4 || ^7.0 || ^8.0", + "thecodingmachine/safe": "^v3.0", + "webmozart/assert": "^1.11 || ^2.0" + }, + "conflict": { + "antecedent/patchwork": "<2.1.25", + "dg/bypass-finals": "<1.4.1" + }, + "require-dev": { + "carthage-software/mago": "^1.20", + "ext-simplexml": "*", + "fidry/makefile": "^1.0", + "fig/log-test": "^1.2", + "phpat/phpat": "^0.12.4", + "phpbench/phpbench": "^1.4", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + "phpunit/phpunit": "^11.5.27", + "rector/rector": "^2.2.4", + "shipmonk/dead-code-detector": "^0.15", + "shipmonk/name-collision-detector": "^2.1", + "sidz/phpstan-rules": "^0.5.1", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", + "thecodingmachine/phpstan-safe-rule": "^1.4", + "webmozarts/strict-phpunit": "^7.15" + }, + "bin": [ + "bin/infection" + ], + "type": "library", + "autoload": { + "psr-4": { + "Infection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com", + "homepage": "https://twitter.com/maks_rafalko" + }, + { + "name": "Oleg Zhulnev", + "homepage": "https://github.com/sidz" + }, + { + "name": "Gert de Pagter", + "homepage": "https://github.com/BackEndTea" + }, + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com", + "homepage": "https://twitter.com/tfidry" + }, + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://www.alexeykopytko.com" + }, + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.", + "keywords": [ + "coverage", + "mutant", + "mutation framework", + "mutation testing", + "testing", + "unit testing" + ], + "support": { + "issues": "https://github.com/infection/infection/issues", + "source": "https://github.com/infection/infection/tree/0.32.7" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2026-04-23T21:11:41+00:00" + }, + { + "name": "infection/mutator", + "version": "0.4.1", + "source": { + "type": "git", + "url": "https://github.com/infection/mutator.git", + "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/mutator/zipball/3c976d721b02b32f851ee4e15d553ef1e9186d1d", + "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\Mutator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Mutator interface to implement custom mutators (mutation operators) for Infection", + "support": { + "issues": "https://github.com/infection/mutator/issues", + "source": "https://github.com/infection/mutator/tree/0.4.1" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2025-04-29T08:19:52+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.13.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "30de5e9b3fa04253af272a7d932a38cd026c311b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/30de5e9b3fa04253af272a7d932a38cd026c311b", + "reference": "30de5e9b3fa04253af272a7d932a38cd026c311b", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "dev-main", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Danny van der Sluijs", + "email": "danny.vandersluijs@icloud.com", + "role": "Maintainer" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.13.0" + }, + "time": "2026-09-23T19:46:42+00:00" + }, + { + "name": "league/commonmark", + "version": "2.10.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "6efbd9c472b91db0a3350fcd601c8332c2382e1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6efbd9c472b91db0a3350fcd601c8332c2382e1f", + "reference": "6efbd9c472b91db0a3350fcd601c8332c2382e1f", + "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-09-21T13:07:34+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": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+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": "nikic/php-parser", + "version": "v5.9.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" + }, + "time": "2026-09-13T18:51:52+00:00" + }, + { + "name": "ondram/ci-detector", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/OndraM/ci-detector.git", + "reference": "8b0223b5ed235fd377c75fdd1bfcad05c0f168b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/OndraM/ci-detector/zipball/8b0223b5ed235fd377c75fdd1bfcad05c0f168b8", + "reference": "8b0223b5ed235fd377c75fdd1bfcad05c0f168b8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.13.2", + "lmc/coding-standard": "^3.0.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.1.0", + "phpstan/phpstan": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpunit/phpunit": "^9.6.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "OndraM\\CiDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Machulda", + "email": "ondrej.machulda@gmail.com" + } + ], + "description": "Detect continuous integration environment and provide unified access to properties of current build", + "keywords": [ + "CircleCI", + "Codeship", + "Wercker", + "adapter", + "appveyor", + "aws", + "aws codebuild", + "azure", + "azure devops", + "azure pipelines", + "bamboo", + "bitbucket", + "buddy", + "ci-info", + "codebuild", + "continuous integration", + "continuousphp", + "devops", + "drone", + "github", + "gitlab", + "interface", + "jenkins", + "pipelines", + "sourcehut", + "teamcity", + "travis" + ], + "support": { + "issues": "https://github.com/OndraM/ci-detector/issues", + "source": "https://github.com/OndraM/ci-detector/tree/4.2.0" + }, + "time": "2024-03-12T13:22:30+00:00" + }, + { + "name": "patchlevel/coding-standard", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/patchlevel/coding-standard.git", + "reference": "a5b1d54f7bbe3d822603679d8c55cff2a7c7fc9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/patchlevel/coding-standard/zipball/a5b1d54f7bbe3d822603679d8c55cff2a7c7fc9b", + "reference": "a5b1d54f7bbe3d822603679d8c55cff2a7c7fc9b", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2 || ^1.0.0", + "php": "^7.4 || ^8.0", + "slevomat/coding-standard": "^8.12.1", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Badura", + "email": "daniel.badura@patchlevel.de" + }, + { + "name": "David Badura", + "email": "david.badura@patchlevel.de" + } + ], + "description": "The Patchlevel Coding Standard is a set of PHPCS rules applied to all Patchlevel projects.", + "homepage": "https://github.com/patchlevel/coding-standard", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "patchlevel", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/patchlevel/coding-standard/issues", + "source": "https://github.com/patchlevel/coding-standard/tree/1.3.0" + }, + "time": "2023-06-02T10:05:08+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpat/phpat", + "version": "0.12.5", + "source": { + "type": "git", + "url": "https://github.com/carlosas/phpat.git", + "reference": "408d16fdd893e1efc4a3c4a851f407204c69781a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/carlosas/phpat/zipball/408d16fdd893e1efc4a3c4a851f407204c69781a", + "reference": "408d16fdd893e1efc4a3c4a851f407204c69781a", + "shasum": "" + }, + "require": { + "php": "^8.1", + "phpstan/phpstan": "^2.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.87", + "kubawerlos/php-cs-fixer-custom-fixers": "3.32.*", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^6.13" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "PHPat\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carlos Alandete Sastre", + "email": "carlos.alandete@gmail.com" + } + ], + "description": "PHP Architecture Tester", + "support": { + "issues": "https://github.com/carlosas/phpat/issues", + "source": "https://github.com/carlosas/phpat/tree/0.12.5" + }, + "time": "2026-09-15T18:11:37+00:00" + }, + { + "name": "phpbench/container", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196", + "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "php-cs-fixer/shim": "^3.89", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.3" + }, + "time": "2025-11-06T09:05:13+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", + "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^8.2", + "phpbench/container": "^2.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^6.1 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.1 || ^7.0 || ^8.0", + "symfony/finder": "^6.1 || ^7.0 || ^8.0", + "symfony/options-resolver": "^6.1 || ^7.0 || ^8.0", + "symfony/process": "^6.1 || ^7.0 || ^8.0", + "webmozart/glob": "^4.6" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "ergebnis/composer-normalize": "^2.39", + "jangregor/phpstan-prophecy": "^2.0", + "php-cs-fixer/shim": "^3.9", + "phpspec/prophecy": "^1.22", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^11.5", + "rector/rector": "^2.2", + "sebastian/exporter": "^6.3.2", + "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", + "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "keywords": [ + "benchmarking", + "optimization", + "performance", + "profiling", + "testing" + ], + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.7.0" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2026-06-08T19:09:20+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.5" + }, + "time": "2026-08-31T16:05:28+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.16", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46a6d9060e5a7763adfcc21ebcb8b504ebdbcb92", + "reference": "46a6d9060e5a7763adfcc21ebcb8b504ebdbcb92", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-09-25T09:31:51+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "2.0.18", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f5dc20ff8082d02339b60cab68ec3eb0d859fb30", + "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30", + "shasum": "" + }, + "require": { + "phar-io/version": "^3.2", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.2.3" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^5", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "shipmonk/name-collision-detector": "^2.1" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.18" + }, + "time": "2026-07-04T12:16:09+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.46" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-12-24T07:01:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.56", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:52:39+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/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/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": "sanmai/di-container", + "version": "0.1.23", + "source": { + "type": "git", + "url": "https://github.com/sanmai/di-container.git", + "reference": "8cf59c091f33297389d0a5a27ea0d688df15c376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/di-container/zipball/8cf59c091f33297389d0a5a27ea0d688df15c376", + "reference": "8cf59c091f33297389d0a5a27ea0d688df15c376", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1.2 || ^2.0", + "sanmai/pipeline": "^7.10" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.31", + "php-coveralls/php-coveralls": "^2.4.1", + "phpbench/phpbench": "^1.4", + "phpstan/extension-installer": "^1.4", + "phpunit/phpunit": "^11.5.25", + "sanmai/phpstan-rules": "^0.3.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + }, + "preferred-install": "dist" + }, + "autoload": { + "psr-4": { + "DIContainer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://github.com/sanmai" + }, + { + "name": "Maks Rafalko", + "homepage": "https://twitter.com/maks_rafalko" + }, + { + "name": "Théo FIDRY", + "homepage": "https://twitter.com/tfidry" + } + ], + "description": "dependency injection container with automatic constructor dependency resolution", + "keywords": [ + "Autowiring", + "constructor di", + "di container", + "psr 11" + ], + "support": { + "issues": "https://github.com/sanmai/di-container/issues", + "source": "https://github.com/sanmai/di-container/tree/0.1.23" + }, + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2026-08-11T00:58:41+00:00" + }, + { + "name": "sanmai/duoclock", + "version": "0.1.3", + "source": { + "type": "git", + "url": "https://github.com/sanmai/DuoClock.git", + "reference": "47461e3ff65b7308635047831a55615652e7be1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/DuoClock/zipball/47461e3ff65b7308635047831a55615652e7be1a", + "reference": "47461e3ff65b7308635047831a55615652e7be1a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.29", + "php-coveralls/php-coveralls": "^2.4.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11.5.25", + "sanmai/phpstan-rules": "^0.3.1" + }, + "type": "library", + "extra": { + "preferred-install": "dist" + }, + "autoload": { + "psr-4": { + "DuoClock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" + } + ], + "description": "PHP time mocking for tests - PSR-20 clock with mockable sleep(), time(), and TimeSpy for PHPUnit testing", + "support": { + "issues": "https://github.com/sanmai/DuoClock/issues", + "source": "https://github.com/sanmai/DuoClock/tree/0.1.3" + }, + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2025-12-26T06:12:34+00:00" + }, + { + "name": "sanmai/later", + "version": "0.1.8", + "source": { + "type": "git", + "url": "https://github.com/sanmai/later.git", + "reference": "c56aeb8fa7fdf81eda2640a68b51884685963d13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/later/zipball/c56aeb8fa7fdf81eda2640a68b51884685963d13", + "reference": "c56aeb8fa7fdf81eda2640a68b51884685963d13", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.35.1", + "infection/infection": ">=0.27.6", + "phan/phan": ">=2", + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": ">=1.4.5", + "phpunit/phpunit": ">=9.5 <10", + "vimeo/psalm": ">=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Later\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" + } + ], + "description": "Later: deferred wrapper object", + "support": { + "issues": "https://github.com/sanmai/later/issues", + "source": "https://github.com/sanmai/later/tree/0.1.8" + }, + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2026-06-29T07:24:33+00:00" + }, + { + "name": "sanmai/pipeline", + "version": "7.10", + "source": { + "type": "git", + "url": "https://github.com/sanmai/pipeline.git", + "reference": "a8e4e57a6031efc7a92defd13fed9c1c4c73fc71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/a8e4e57a6031efc7a92defd13fed9c1c4c73fc71", + "reference": "a8e4e57a6031efc7a92defd13fed9c1c4c73fc71", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "esi/phpunit-coverage-check": ">2", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.32.3", + "league/pipeline": "^0.3 || ^1.0", + "php-coveralls/php-coveralls": "^2.4.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11 || ^12", + "sanmai/phpstan-rules": "^0.3.11", + "sanmai/phpunit-double-colon-syntax": "^0.1.1", + "vimeo/psalm": ">=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Pipeline\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" + } + ], + "description": "General-purpose collections pipeline", + "support": { + "issues": "https://github.com/sanmai/pipeline/issues", + "source": "https://github.com/sanmai/pipeline/tree/7.10" + }, + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2026-08-03T04:56:22+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:26:40+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2026-06-12T11:32:29+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.22.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/1dd80bf3b93692bedb21a6623c496887fad05fec", + "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", + "php": "^7.4 || ^8.0", + "phpstan/phpdoc-parser": "^2.3.0", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "require-dev": { + "phing/phing": "3.0.1|3.1.0", + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/phpstan": "2.1.24", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan-strict-rules": "2.0.6", + "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.10" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.22.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2025-09-13T08:53:30+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.6", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2026-08-06T00:17:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/cache", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "47616ab077b5aff4d0f718b96680a48599fc1a3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/47616ab077b5aff4d0f718b96680a48599fc1a3f", + "reference": "47616ab077b5aff4d0f718b96680a48599fc1a3f", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^8.1" + }, + "conflict": { + "ext-redis": "<6.1", + "ext-relay": "<0.12.1" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "^1.0.3", + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "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 extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v8.1.7" + }, + "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-09-08T13:39:13+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "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\\Cache\\": "" + } + }, + "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 caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-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/console", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "29afb89f4e941f68a6e90f28e3f52ff1f6793a7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/29afb89f4e941f68a6e90f28e3f52ff1f6793a7d", + "reference": "29afb89f4e941f68a6e90f28e3f52ff1f6793a7d", + "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.7" + }, + "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-09-13T10:55:57+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/filesystem", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "7599ebb855fede59413ddb7f15198d67bfcae7ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7599ebb855fede59413ddb7f15198d67bfcae7ba", + "reference": "7599ebb855fede59413ddb7f15198d67bfcae7ba", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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 basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/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/finder", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "4fbe46a3eb64abf8a57f0364075b91f4a233e062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/4fbe46a3eb64abf8a57f0364075b91f4a233e062", + "reference": "4fbe46a3eb64abf8a57f0364075b91f4a233e062", + "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.7" + }, + "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-09-10T19:14:39+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/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/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-deepclone", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-deepclone.git", + "reference": "70ba0627efc68e97ea392843458a2dd9d6dbd156" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/70ba0627efc68e97ea392843458a2dd9d6dbd156", + "reference": "70ba0627efc68e97ea392843458a2dd9d6dbd156", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "provide": { + "ext-deepclone": "*" + }, + "suggest": { + "ext-deepclone": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\DeepClone\\": "" + }, + "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 the deepclone extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "deepclone", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-deepclone/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-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-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-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/process", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/10823b09358e690df4ff943e24e8492bb1019fc3", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3", + "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.7" + }, + "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-09-02T12:40:29+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.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d950140b5f56f31901e5b7a0c04ffc3a3deb943c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d950140b5f56f31901e5b7a0c04ffc3a3deb943c", + "reference": "d950140b5f56f31901e5b7a0c04ffc3a3deb943c", + "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.7" + }, + "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-09-11T14:51:16+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "741a8c4c948b0bb9bd3653daacd3644c73c40ea5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/741a8c4c948b0bb9bd3653daacd3644c73c40ea5", + "reference": "741a8c4c948b0bb9bd3653daacd3644c73c40ea5", + "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.7" + }, + "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-09-03T16:02:27+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "802362f41128c430fd6685491e1fb72127fae78a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/802362f41128c430fd6685491e1fb72127fae78a", + "reference": "802362f41128c430fd6685491e1fb72127fae78a", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-deepclone": "^1.40" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "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 tools to export, instantiate, hydrate, clone and lazy-load PHP objects", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "deep-clone", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/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-29T06:30:03+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.1" + }, + "time": "2026-06-15T15:31:57+00:00" + }, + { + "name": "webmozart/glob", + "version": "4.7.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/glob.git", + "reference": "8a2842112d6916e61e0e15e316465b611f3abc17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/8a2842112d6916e61e0e15e316465b611f3abc17", + "reference": "8a2842112d6916e61e0e15e316465b611f3abc17", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/filesystem": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "support": { + "issues": "https://github.com/webmozarts/glob/issues", + "source": "https://github.com/webmozarts/glob/tree/4.7.0" + }, + "time": "2024-03-07T20:33:40+00:00" + }, + { + "name": "wnx/commonmark-markdown-renderer", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/stefanzweifel/commonmark-markdown-renderer.git", + "reference": "3a283076abd1a1ed043940f9be43cd35470cd0d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stefanzweifel/commonmark-markdown-renderer/zipball/3a283076abd1a1ed043940f9be43cd35470cd0d4", + "reference": "3a283076abd1a1ed043940f9be43cd35470cd0d4", + "shasum": "" + }, + "require": { + "league/commonmark": "^2.0", + "php": "^8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.0", + "rector/rector": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Wnx\\CommonmarkMarkdownRenderer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stefan Zweifel", + "email": "stefan@stefanzweifel.dev", + "role": "Developer" + } + ], + "description": "Render Markdown AST back to Markdown.", + "homepage": "https://github.com/stefanzweifel/commonmark-markdown-renderer", + "keywords": [ + "commonmark-markdown-renderer", + "markdown", + "renderer", + "wnx" + ], + "support": { + "issues": "https://github.com/stefanzweifel/commonmark-markdown-renderer/issues", + "source": "https://github.com/stefanzweifel/commonmark-markdown-renderer/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://github.com/stefanzweifel", + "type": "github" + } + ], + "time": "2025-11-23T20:14:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "ext-openssl": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/phpbench.json b/phpbench.json index 729b39d4..60c604cd 100644 --- a/phpbench.json +++ b/phpbench.json @@ -1,54 +1,37 @@ { - "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", "runner.file_pattern": "*Bench.php", + "runner.iterations": 5, + "runner.php_config": { + "memory_limit": "-1", + "opcache.enable_cli": 1, + "opcache.jit_buffer_size": 0, + "xdebug.mode": "off" + }, + "runner.assert": "mode(variant.time.avg) <= mode(baseline.time.avg) +/- 25%", "report.generators": { "diff": { - "generator": "component", - "partition": ["benchmark_name"], - "components": [ - { - "component": "section", - "title": "{{ first(frame[\"benchmark_name\"]) }}", - "components": [ - { - "component": "table_aggregate", - "partition": ["subject_name", "variant_name"], - "groups": - { - "time (kde mode)": - { - "cols": ["time"] - }, - "memory": - { - "cols": ["memory"] - } - }, - "row": - { - "subject": "first(partition[\"subject_name\"]) ~ \" (\" ~ first(partition[\"variant_name\"]) ~ \")\"", - "time": - { - "type": "expand", - "partition": "suite_tag", - "cols": - { - "Tag: {{ key }}": "mode(partition[\"result_time_avg\"]) as time ~ ' (' ~ rstdev(partition['result_time_avg']) ~ ')'" - } - }, - "memory": - { - "type": "expand", - "partition": "suite_tag", - "cols": - { - "Tag: {{ key }} ": "mode(partition[\"result_mem_peak\"]) as memory" - } - } - } - }] - }] + "generator": "expression", + "break": [ + "benchmark" + ], + "cols": [ + "subject", + "revs", + "its", + "memory", + "base", + "current", + "diff" + ], + "expressions": { + "memory": "if(first(has_baseline), (first(result_mem_peak) as memory) ~ ' ' ~ percent_diff(first(baseline_mem_peak), first(result_mem_peak)), first(result_mem_peak) as memory)", + "base": "if(first(has_baseline), mode(baseline_time_avg) as time ~ ' (' ~ rstdev(baseline_time_avg) ~ ')', '')", + "current": "mode(result_time_avg) as time ~ ' (' ~ rstdev(result_time_avg) ~ ')'", + "diff": "if(first(has_baseline), percent_diff(mode(baseline_time_avg), mode(result_time_avg), rstdev(result_time_avg)), '')" + } } } -} \ No newline at end of file +} diff --git a/tests/Benchmark/EventSauceHydratorBench.php b/tests/Benchmark/EventSauceHydratorBench.php deleted file mode 100644 index 45b4b2e5..00000000 --- a/tests/Benchmark/EventSauceHydratorBench.php +++ /dev/null @@ -1,132 +0,0 @@ -hydrator = new ObjectMapperUsingReflection(); - } - - public function setUp(): void - { - $object = $this->hydrator->hydrateObject( - ProfileCreated::class, - [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - - $this->hydrator->serializeObject($object); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->hydrateObject(ProfileCreated::class, [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->serializeObject($object); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->hydrateObject(ProfileCreated::class, [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->serializeObject($object); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrateObject(ProfileCreated::class, [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->serializeObject($object); - } - } -} diff --git a/tests/Benchmark/Fixture/ProfileCreated.php b/tests/Benchmark/Fixture/ProfileCreated.php index a4421646..5f28157c 100644 --- a/tests/Benchmark/Fixture/ProfileCreated.php +++ b/tests/Benchmark/Fixture/ProfileCreated.php @@ -4,7 +4,6 @@ namespace Patchlevel\Hydrator\Tests\Benchmark\Fixture; -use JMS\Serializer\Annotation\Type; use Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId; use Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData; @@ -13,12 +12,10 @@ final class ProfileCreated /** @param list $skills */ public function __construct( #[ProfileIdNormalizer] - #[ProfileIdCaster] #[DataSubjectId] public ProfileId $profileId, #[SensitiveData(fallback: 'unknown')] public string $name, - #[Type('list')] public array $skills = [], ) { } diff --git a/tests/Benchmark/Fixture/ProfileIdCaster.php b/tests/Benchmark/Fixture/ProfileIdCaster.php deleted file mode 100644 index 6672eb86..00000000 --- a/tests/Benchmark/Fixture/ProfileIdCaster.php +++ /dev/null @@ -1,39 +0,0 @@ -toString(); - } -} diff --git a/tests/Benchmark/GeneratedEventSauceHydratorBench.php b/tests/Benchmark/GeneratedEventSauceHydratorBench.php deleted file mode 100644 index f80d48fe..00000000 --- a/tests/Benchmark/GeneratedEventSauceHydratorBench.php +++ /dev/null @@ -1,145 +0,0 @@ -dump($classesToDump, $dumpedClassNamed); - file_put_contents(__DIR__ . '/../../var/cache/GeneratedEventSauceMapper.php', $code); - - require_once __DIR__ . '/../../var/cache/GeneratedEventSauceMapper.php'; - - // @phpstan-ignore class.notFound, assign.propertyType - $this->hydrator = new GeneratedEventSauceMapper(); - } - - public function setUp(): void - { - $object = $this->hydrator->hydrateObject( - ProfileCreated::class, - [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - - $this->hydrator->serializeObject($object); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->hydrateObject(ProfileCreated::class, [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->serializeObject($object); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->hydrateObject(ProfileCreated::class, [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->serializeObject($object); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrateObject(ProfileCreated::class, [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->serializeObject($object); - } - } -} diff --git a/tests/Benchmark/GeneratedHydratorBench.php b/tests/Benchmark/GeneratedHydratorBench.php index a391fa2b..2d8fd3f2 100644 --- a/tests/Benchmark/GeneratedHydratorBench.php +++ b/tests/Benchmark/GeneratedHydratorBench.php @@ -35,7 +35,7 @@ public function __construct() public function setUp(): void { - $this->hydrator->hydrate( + $object = $this->hydrator->hydrate( ProfileCreated::class, [ 'profileId' => '1', @@ -46,9 +46,11 @@ public function setUp(): void ], ], ); + + $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate(ProfileCreated::class, [ @@ -61,7 +63,7 @@ public function benchHydrate1Object(): void ]); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -107,36 +109,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php b/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php index 518483e2..efe76740 100644 --- a/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php +++ b/tests/Benchmark/GeneratedHydratorWithCryptographyBench.php @@ -53,7 +53,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate( @@ -69,7 +69,7 @@ public function benchHydrate1Object(): void ); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -118,39 +118,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/HydratorBench.php b/tests/Benchmark/HydratorBench.php deleted file mode 100644 index 0af5f89c..00000000 --- a/tests/Benchmark/HydratorBench.php +++ /dev/null @@ -1,135 +0,0 @@ -hydrator = (new StackHydratorBuilder()) - ->useExtension(new CoreExtension()) - ->build(); - } - - public function setUp(): void - { - $object = $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - - $this->hydrator->extract($object); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->extract($object); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->extract($object); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } -} diff --git a/tests/Benchmark/HydratorWithCryptographyBench.php b/tests/Benchmark/HydratorWithCryptographyBench.php deleted file mode 100644 index 1b93a77a..00000000 --- a/tests/Benchmark/HydratorWithCryptographyBench.php +++ /dev/null @@ -1,154 +0,0 @@ -store = new InMemoryCipherKeyStore(); - - $this->hydrator = (new StackHydratorBuilder()) - ->useExtension(new CoreExtension()) - ->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($this->store))) - ->build(); - } - - public function setUp(): void - { - $this->store->clear(); - - $object = $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - - $this->hydrator->extract($object); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->extract($object); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->extract($object); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } -} diff --git a/tests/Benchmark/JMSHydratorBench.php b/tests/Benchmark/JMSHydratorBench.php deleted file mode 100644 index 6954ac1f..00000000 --- a/tests/Benchmark/JMSHydratorBench.php +++ /dev/null @@ -1,141 +0,0 @@ -hydrator = SerializerBuilder::create()->build(); - } - - public function setUp(): void - { - $object = $this->hydrator->fromArray( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - - $this->hydrator->toArray($object); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->fromArray( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->toArray($object); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->fromArray( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->toArray($object); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->fromArray( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->toArray($object); - } - } -} diff --git a/tests/Benchmark/SerdeHydratorBench.php b/tests/Benchmark/SerdeHydratorBench.php deleted file mode 100644 index 0ae425fc..00000000 --- a/tests/Benchmark/SerdeHydratorBench.php +++ /dev/null @@ -1,148 +0,0 @@ -hydrator = new SerdeCommon($analyzer); - } - - public function setUp(): void - { - $object = $this->hydrator->deserialize( - [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - 'array', - ProfileCreated::class, - ); - - $this->hydrator->serialize($object, 'array'); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->deserialize( - [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - 'array', - ProfileCreated::class, - ); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->serialize($object, 'array'); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->deserialize( - [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - 'array', - ProfileCreated::class, - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->serialize($object, 'array'); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->deserialize( - [ - 'profile_id' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - 'array', - ProfileCreated::class, - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->serialize($object, 'array'); - } - } -} diff --git a/tests/Benchmark/StackHydratorBench.php b/tests/Benchmark/StackHydratorBench.php index 4b1f7cf8..7287694b 100644 --- a/tests/Benchmark/StackHydratorBench.php +++ b/tests/Benchmark/StackHydratorBench.php @@ -41,7 +41,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate(ProfileCreated::class, [ @@ -54,7 +54,7 @@ public function benchHydrate1Object(): void ]); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -100,36 +100,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/StackHydratorWithCryptographyBench.php b/tests/Benchmark/StackHydratorWithCryptographyBench.php index 5d5f54b9..44f4f37a 100644 --- a/tests/Benchmark/StackHydratorWithCryptographyBench.php +++ b/tests/Benchmark/StackHydratorWithCryptographyBench.php @@ -51,7 +51,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate( @@ -67,7 +67,7 @@ public function benchHydrate1Object(): void ); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchExtract1Object(): void { $object = new ProfileCreated( @@ -116,39 +116,4 @@ public function benchExtract1000Objects(): void $this->hydrator->extract($object); } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate( - ProfileCreated::class, - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->extract($object); - } - } } diff --git a/tests/Benchmark/HydratorWithLazyBench.php b/tests/Benchmark/StackHydratorWithLazyBench.php similarity index 72% rename from tests/Benchmark/HydratorWithLazyBench.php rename to tests/Benchmark/StackHydratorWithLazyBench.php index a85224b7..c8504280 100644 --- a/tests/Benchmark/HydratorWithLazyBench.php +++ b/tests/Benchmark/StackHydratorWithLazyBench.php @@ -11,7 +11,7 @@ use PhpBench\Attributes as Bench; #[Bench\BeforeMethods('setUp')] -final class HydratorWithLazyBench +final class StackHydratorWithLazyBench { private Hydrator $hydrator; @@ -40,7 +40,7 @@ public function setUp(): void $this->hydrator->extract($object); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1Object(): void { $this->hydrator->hydrate(ProfileCreated::class, [ @@ -53,7 +53,7 @@ public function benchHydrate1Object(): void ]); } - #[Bench\Revs(5)] + #[Bench\Revs(1000)] public function benchHydrate1ObjectTriggerInit(): void { $object = $this->hydrator->hydrate(ProfileCreated::class, [ @@ -99,36 +99,4 @@ public function benchHydrate1000ObjectsTriggerInit(): void $name = $object->name; } } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000ObjectsTriggerInit(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $object = $this->hydrator->hydrate(ProfileCreated::class, [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ]); - - $object = $object->name; - } - } } diff --git a/tests/Benchmark/SymfonyHydratorBench.php b/tests/Benchmark/SymfonyHydratorBench.php deleted file mode 100644 index c8f30631..00000000 --- a/tests/Benchmark/SymfonyHydratorBench.php +++ /dev/null @@ -1,141 +0,0 @@ -hydrator = new Serializer([new ObjectNormalizer()], []); - } - - public function setUp(): void - { - $object = $this->hydrator->denormalize( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - - $this->hydrator->normalize($object); - } - - #[Bench\Revs(5)] - public function benchHydrate1Object(): void - { - $this->hydrator->denormalize( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - } - - #[Bench\Revs(5)] - public function benchExtract1Object(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - $this->hydrator->normalize($object); - } - - #[Bench\Revs(3)] - public function benchHydrate1000Objects(): void - { - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->denormalize( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000; $i++) { - $this->hydrator->normalize($object); - } - } - - #[Bench\Revs(3)] - public function benchHydrate1000000Objects(): void - { - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->denormalize( - [ - 'profileId' => '1', - 'name' => 'foo', - 'skills' => [ - ['name' => 'php'], - ['name' => 'symfony'], - ], - ], - ProfileCreated::class, - ); - } - } - - #[Bench\Revs(3)] - public function benchExtract1000000Objects(): void - { - $object = new ProfileCreated( - ProfileId::fromString('1'), - 'foo', - [ - new Skill('php'), - new Skill('symfony'), - ], - ); - - for ($i = 0; $i < 1_000_000; $i++) { - $this->hydrator->normalize($object); - } - } -}