diff --git a/.openapi.php b/.openapi.php new file mode 100644 index 0000000..9b018aa --- /dev/null +++ b/.openapi.php @@ -0,0 +1,23 @@ + 'API Docs', + + 'description' => 'API Docs description.', + + /** + * 向全局头部参数存储中添加一个的头部参数。 + * @param string $name + * @param string $example + * @param string $description + */ + 'headers' => [ + + ], + + 'service' => [ + new ServersStorage('http://127.0.0.1','默认环境'), + ], +]; diff --git a/README.md b/README.md index fdc8d74..845aa56 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ # Languages -- [Complete documen-English](./docs/en/README.md) -- [完整文档-中文](./docs/zh/README.md) +- [Complete documen-English](https://astrals-organization.gitbook.io/php-serialize/php-serialize-en) +- [完整文档-中文](https://astrals-organization.gitbook.io/php-serialize) # php-serialize diff --git a/benchmarks/SerializeBench.php b/benchmarks/SerializeBench.php index 14e395b..448358c 100644 --- a/benchmarks/SerializeBench.php +++ b/benchmarks/SerializeBench.php @@ -83,7 +83,7 @@ public function setupObject(): void Revs(500), Iterations(5), BeforeMethods([ 'setupObjectCreation']), - Assert('mode(variant.time.avg) < 140 microseconds +/- 5%') + Assert('mode(variant.time.avg) < 120 microseconds +/- 5%') ] public function benchObjectCreation(): void { @@ -94,7 +94,7 @@ public function benchObjectCreation(): void Revs(5000), Iterations(5), BeforeMethods([ 'setupObjectCreation']), - Assert('mode(variant.time.avg) < 350 microseconds +/- 5%') + Assert('mode(variant.time.avg) < 300 microseconds +/- 5%') ] public function benchObjectCreationWithoutCache(): void { @@ -106,7 +106,7 @@ public function benchObjectCreationWithoutCache(): void Revs(500), Iterations(5), BeforeMethods(['setupObject']), - Assert('mode(variant.time.avg) < 80 microseconds +/- 5%') + Assert('mode(variant.time.avg) < 65 microseconds +/- 5%') ] public function benchObjectToArray(): void { @@ -117,7 +117,7 @@ public function benchObjectToArray(): void Revs(5000), Iterations(5), BeforeMethods(['setupObject']), - Assert('mode(variant.time.avg) < 270 microseconds +/- 5%') + Assert('mode(variant.time.avg) < 230 microseconds +/- 5%') ] public function benchObjectToArrayWithoutCache(): void { diff --git a/composer.json b/composer.json index 5e14d29..54983e1 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "phpdocumentor/reflection-docblock": "^5.5", "phpdocumentor/reflection": "^6.0", "fakerphp/faker": "^1.23", - "psr/simple-cache": "^3.0" + "psr/simple-cache": "^3.0", + "symfony/console": "^6.4" }, "require-dev" : { "phpstan/phpstan": "^2.0.2", @@ -41,6 +42,9 @@ "config": { "allow-plugins": { "pestphp/pest-plugin": true + }, + "platform": { + "php": "8.1.32" } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..80ba5cc --- /dev/null +++ b/composer.lock @@ -0,0 +1,6825 @@ +{ + "_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": "6a9a46d6b2d0f3759c5c4de4bcc10d39", + "packages": [ + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T20:23:39+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "illuminate/collections", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "48de3d6bc6aa779112ddcb608a3a96fc975d89d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/48de3d6bc6aa779112ddcb608a3a96fc975d89d8", + "reference": "48de3d6bc6aa779112ddcb608a3a96fc975d89d8", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "php": "^8.1" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "3ee34ac306fafc2a6f19cd7cd68c9af389e432a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/3ee34ac306fafc2a6f19cd7cd68c9af389e432a5", + "reference": "3ee34ac306fafc2a6f19cd7cd68c9af389e432a5", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "f90663a69f926105a70b78060a31f3c64e2d1c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/f90663a69f926105a70b78060a31f3c64e2d1c74", + "reference": "f90663a69f926105a70b78060a31f3c64e2d1c74", + "shasum": "" + }, + "require": { + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-11-21T14:02:44+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-05T12:46:42+00:00" + }, + { + "name": "illuminate/support", + "version": "v10.48.28", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "6d09b480d34846245d9288f4dcefb17a73ce6e6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/6d09b480d34846245d9288f4dcefb17a73ce6e6a", + "reference": "6d09b480d34846245d9288f4dcefb17a73ce6e6a", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^10.0", + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "nesbot/carbon": "^2.67", + "php": "^8.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^10.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the composer class (^6.2).", + "symfony/uid": "Required to use Str::ulid() (^6.2).", + "symfony/var-dumper": "Required to use the dd function (^6.2).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-12-10T14:47:55+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.73.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "<6", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-01-08T20:10:23+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "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.0-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.5.0" + }, + "time": "2025-05-31T08:24:38+00:00" + }, + { + "name": "phpdocumentor/reflection", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/Reflection.git", + "reference": "d91b3270832785602adcc24ae2d0974ba99a8ff8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/d91b3270832785602adcc24ae2d0974ba99a8ff8", + "reference": "d91b3270832785602adcc24ae2d0974ba99a8ff8", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "nikic/php-parser": "~4.18 || ^5.0", + "php": "8.1.*|8.2.*|8.3.*|8.4.*", + "phpdocumentor/reflection-common": "^2.1", + "phpdocumentor/reflection-docblock": "^5", + "phpdocumentor/type-resolver": "^1.2", + "symfony/polyfill-php80": "^1.28", + "webmozart/assert": "^1.7" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/coding-standard": "^13.0", + "eliashaeussler/phpunit-attributes": "^1.7", + "mikey179/vfsstream": "~1.2", + "mockery/mockery": "~1.6.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^10.0", + "psalm/phar": "^6.0", + "rector/rector": "^1.0.0", + "squizlabs/php_codesniffer": "^3.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-5.x": "5.3.x-dev", + "dev-6.x": "6.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/php-parser/Modifiers.php" + ], + "psr-4": { + "phpDocumentor\\": "src/phpDocumentor" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Reflection library to do Static Analysis for PHP Projects", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/Reflection/issues", + "source": "https://github.com/phpDocumentor/Reflection/tree/6.3.0" + }, + "time": "2025-06-06T13:39:18+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "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.1.0" + }, + "time": "2025-02-19T13:28:12+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/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/console", + "version": "v6.4.22", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.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/v6.4.22" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-07T07:05:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-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.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "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.6-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.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.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/v6.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-18T15:23:29+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.4.22", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/7e3b3b7146c6fab36ddff304a8041174bf6e17ad", + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.22" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-29T07:06:44+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-27T08:32:26+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@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/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30", + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^0.5.1 || ^1.0.0", + "jean85/pretty-package-versions": "^2.0.5", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "phpunit/php-code-coverage": "^10.1.7", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-timer": "^6.0", + "phpunit/phpunit": "^10.4.2", + "sebastian/environment": "^6.0.1", + "symfony/console": "^6.3.4 || ^7.0.0", + "symfony/process": "^6.3.4 || ^7.0.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "infection/infection": "^0.27.6", + "phpstan/phpstan": "^1.10.40", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/filesystem": "^6.3.1 || ^7.0.0" + }, + "bin": [ + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2023-10-31T09:24:17+00:00" + }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^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.3.2" + }, + "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-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "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-09-19T14:15:21+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": "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" + }, + "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": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "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": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "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.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.2", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.2" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-06-11T20:42:19+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.75.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.2", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.1 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.4 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", + "symfony/polyfill-mbstring": "^1.31", + "symfony/polyfill-php80": "^1.31", + "symfony/polyfill-php81": "^1.31", + "symfony/process": "^5.4 || ^6.4 || ^7.2", + "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.6", + "infection/infection": "^0.29.14", + "justinrainbow/json-schema": "^5.3 || ^6.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12", + "symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3", + "symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-03-31T18:40:42+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "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.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v7.12.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/995245421d3d7593a6960822063bdba4f5d7cf1a", + "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.17.0", + "nunomaduro/termwind": "^1.17.0", + "php": "^8.1.0", + "symfony/console": "^6.4.17" + }, + "conflict": { + "laravel/framework": ">=11.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.4.8", + "laravel/framework": "^10.48.29", + "laravel/pint": "^1.21.2", + "laravel/sail": "^1.41.0", + "laravel/sanctum": "^3.3.3", + "laravel/tinker": "^2.10.1", + "nunomaduro/larastan": "^2.10.0", + "orchestra/testbench-core": "^8.35.0", + "pestphp/pest": "^2.36.0", + "phpunit/phpunit": "^10.5.36", + "sebastian/environment": "^6.1.0", + "spatie/laravel-ignition": "^2.9.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-03-14T22:35:49+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301", + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.4.15" + }, + "require-dev": { + "illuminate/console": "^10.48.24", + "illuminate/support": "^10.48.24", + "laravel/pint": "^1.18.2", + "pestphp/pest": "^2.36.0", + "pestphp/pest-plugin-mock": "2.0.0", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^6.4.15", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2024-11-21T10:36:35+00:00" + }, + { + "name": "pestphp/pest", + "version": "v2.36.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "f8c88bd14dc1772bfaf02169afb601ecdf2724cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/f8c88bd14dc1772bfaf02169afb601ecdf2724cd", + "reference": "f8c88bd14dc1772bfaf02169afb601ecdf2724cd", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.3.1", + "nunomaduro/collision": "^7.11.0|^8.4.0", + "nunomaduro/termwind": "^1.16.0|^2.1.0", + "pestphp/pest-plugin": "^2.1.1", + "pestphp/pest-plugin-arch": "^2.7.0", + "php": "^8.1.0", + "phpunit/phpunit": "^10.5.36" + }, + "conflict": { + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">10.5.36", + "sebastian/exporter": "<5.1.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "^2.17.0", + "pestphp/pest-plugin-type-coverage": "^2.8.7", + "symfony/process": "^6.4.0|^7.1.5" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v2.36.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-10-15T15:30:56+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e05d2859e08c2567ee38ce8b005d044e72648c0b", + "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.1" + }, + "conflict": { + "pestphp/pest": "<2.2.3" + }, + "require-dev": { + "composer/composer": "^2.5.8", + "pestphp/pest": "^2.16.0", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v2.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2023-08-22T08:40:06+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/d23b2d7498475354522c3818c42ef355dca3fcda", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda", + "shasum": "" + }, + "require": { + "nunomaduro/collision": "^7.10.0|^8.1.0", + "pestphp/pest-plugin": "^2.1.1", + "php": "^8.1", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" + }, + "require-dev": { + "pestphp/pest": "^2.33.0", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-01-26T09:46:42+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": "phpbench/container", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/a59b929e00b87b532ca6d0edd8eca0967655af33", + "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "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.2" + }, + "time": "2023-10-30T13:38:26+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", + "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^8.1", + "phpbench/container": "^2.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^6.1 || ^7.0", + "symfony/filesystem": "^6.1 || ^7.0", + "symfony/finder": "^6.1 || ^7.0", + "symfony/options-resolver": "^6.1 || ^7.0", + "symfony/process": "^6.1 || ^7.0", + "webmozart/glob": "^4.6" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "ergebnis/composer-normalize": "^2.39", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^1.0", + "phpspec/prophecy": "dev-master", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.4 || ^11.0", + "rector/rector": "^1.2", + "symfony/error-handler": "^6.1 || ^7.0", + "symfony/var-dumper": "^6.1 || ^7.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.4.1" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2025-03-12T08:01:40+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.17", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", + "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" + ], + "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": "2025-05-21T20:55:28+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "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": "10.1.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/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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", + "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", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", + "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.2", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.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/10.5.36" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-10-08T15:36:51+00:00" + }, + { + "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/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": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-01-01T16:37:48+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.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/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.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", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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" + } + ], + "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", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "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" + }, + { + "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/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-18T14:56:07+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-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/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "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" + }, + { + "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/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-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/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "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" + }, + { + "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/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:17:12+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.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": "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/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.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/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "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" + } + ], + "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", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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" + } + ], + "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", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "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" + }, + { + "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", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:05:40+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "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 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", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "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.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.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/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:07:50+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.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/v6.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-29T13:51:37+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.4.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "368128ad168f20e22c32159b9f761e456cec0c78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", + "reference": "368128ad168f20e22c32159b9f761e456cec0c78", + "shasum": "" + }, + "require": { + "php": ">=8.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/v6.4.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-20T10:57:02+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "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\\Php81\\": "" + }, + "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.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "shasum": "" + }, + "require": { + "php": ">=8.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/v6.4.20" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-10T17:11:00+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.4.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", + "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.4.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-21T10:06:30+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.5", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "cf6fb197b676ba716837c886baca842e4db29005" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/cf6fb197b676ba716837c886baca842e4db29005", + "reference": "cf6fb197b676ba716837c886baca842e4db29005", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.5" + }, + "time": "2025-04-20T20:23:40+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "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.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+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" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.1.32" + }, + "plugin-api-version": "2.6.0" +} diff --git a/docs/.gitbook.yaml b/docs/.gitbook.yaml new file mode 100644 index 0000000..692a078 --- /dev/null +++ b/docs/.gitbook.yaml @@ -0,0 +1,4 @@ +root: ./ +structure: + readme: README.md + summary: SUMMARY.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 4fe1acb..4cc0adf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,7 +39,7 @@ $userArray = $user->toArray(); // ] ``` -#### Other Features +### Other Features 1. **Immutability**: Read-only properties cannot be modified after construction @@ -334,11 +334,11 @@ echo $profile->username; // Output null 1. Can mix basic and object types 2. Object Hierarchy Matching - - For multiple object types, the most matching type will be selected - - Supports intelligent matching of inheritance hierarchy + - For multiple object types, the most matching type will be selected + - Supports intelligent matching of inheritance hierarchy 3. Dynamic Type Handling - - Automatically handles input of different types - - Provides more flexible data modeling + - Automatically handles input of different types + - Provides more flexible data modeling ```php use Astral\Serialize\Serialize; @@ -926,7 +926,7 @@ $userArray = $user->toArray( outputGroups: ['api'] // Use only output mappings in 'api' group ); ``` - + ##### Global Class Mapping ```php diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index b7b226b..3ecce96 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,4 +1,5 @@ -# Table of contents +# Summary -* [Astral Serialize Documentation](README.md) -* [Astral Serialize 文档](zh/README.md) +* [介绍](description.md) +* [Quick Start](getting-started.md) +* [Type Conversion](base-mapper.md) \ No newline at end of file diff --git a/docs/base-mapper.md b/docs/base-mapper.md new file mode 100644 index 0000000..9fca977 --- /dev/null +++ b/docs/base-mapper.md @@ -0,0 +1,70 @@ +### 类型转换 + +#### 基本类型转换 + +##### 方式一:构造函数属性提升 + +```php +use Astral\Serialize\Serialize; + +class Profile extends Serialize { + public function __construct( + public string $username, + public int $score, + public float $balance, + public bool $isActive + ) {} +} +``` + +##### 方式二:传统属性定义 + +```php +use Astral\Serialize\Serialize; + +class Profile extends Serialize { + public string $username; + public int $score; + public float $balance; + public bool $isActive; +} + +// 两种方式都支持相同的类型转换 +$profile = Profile::from([ + 'username' => 123, // 整数转换为字符串 + 'score' => '100', // 字符串转换为整数 + 'balance' => '99.99', // 字符串转换为浮点数 + 'isActive' => 1 // 数字转换为布尔值 +]); + +// 转换为数组 +$profileArray = $profile->toArray(); +``` + +##### 方式三:只读属性 + +```php +use Astral\Serialize\Serialize; + +class Profile extends Serialize { + public readonly string $username; + public readonly int $score; + public readonly float $balance; + public readonly bool $isActive; + + // 手动初始化 + public function __construct( + string $username, + int $score, + float $balance, + bool $isActive + ) { + $this->username = $username; + $this->score = $score; + $this->balance = $balance; + $this->isActive = $isActive; + } +} +``` + +无论使用哪种方式,`Serialize` 类都能正常工作,并提供相同的类型转换和序列化功能。 \ No newline at end of file diff --git a/docs/description.md b/docs/description.md new file mode 100644 index 0000000..aac5fa9 --- /dev/null +++ b/docs/description.md @@ -0,0 +1,16 @@ +# php-serialize + +**php-serialize** is a powerful attribute-based serialization library for PHP (requires **PHP ≥ 8.1**). +It allows you to map objects to arrays/JSON and **automatically generate OpenAPI documentation** based on the same attributes. + +> 🚀 Unified solution for API data serialization and documentation generation. + +## ✨ Features + +- 🏷️ Property aliasing with +- 🔄 Automatic type casting (e.g. `DateTime ↔ string`) +- 🔁 Deep object nesting support +- ❌ Skip/exclude fields with +- 🧩 Recursive DTO serialization +- 🧬 **Auto-generate OpenAPI schema** using object definitions +- ⚙️ Framework-agnostic — works with Laravel, Symfony, etc. \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..9a2108e --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,94 @@ +## 快速开始 + +### 安装 + +使用 Composer 安装: + +```bash +composer require astral/serialize +``` + +### 基本用法 + +```php +use Astral\Serialize\Serialize; + +class User extends Serialize { + public string $name, + public int $age +} + +// 从数组创建对象 +$user = User::from([ + 'name' => '张三', + 'age' => 30 +]); + +// 访问对象属性 +echo $user->name; // 输出: 张三 +echo $user->age; // 输出: 30 + +// 转换为数组 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'name' => '张三', +// 'age' => 30 +// ] +``` + +### 其他特性 + +#### **不可变性**:只读属性在构造后无法修改 + +```php +use Astral\Serialize\Serialize; + +class User extends Serialize { + public function __construct( + public readonly string $name, + public readonly int $age + ) {} +} + +$user = User::from([ + 'name' => '张三', + 'age' => 30 +]); + +try { + $user->name = '李四'; // 编译时错误:无法修改只读属性 +} catch (Error $e) { + echo "只读属性不能被重新赋值"; +} +``` + +#### **类型安全的初始化** + +```php +$user = User::from([ + 'name' => 123, // 整数会被转换为字符串 + 'age' => '35' // 字符串会被转换为整数 +]); + +echo $user->name; // 输出: "123" +echo $user->age; // 输出: 35 +``` + +#### **构造函数初始化** + +```php +use Astral\Serialize\Serialize; + +class User extends Serialize { + public function __construct( + public readonly string $name, + public readonly int $age + ) { + // 可以在构造函数中添加额外的验证或处理逻辑 + if (strlen($name) < 2) { + throw new \InvalidArgumentException('名称太短'); + } + } +} +``` \ No newline at end of file diff --git a/docs/zh/.gitbook.yaml b/docs/zh/.gitbook.yaml new file mode 100644 index 0000000..692a078 --- /dev/null +++ b/docs/zh/.gitbook.yaml @@ -0,0 +1,4 @@ +root: ./ +structure: + readme: README.md + summary: SUMMARY.md \ No newline at end of file diff --git a/docs/zh/README.md b/docs/zh/README.md index cadea6d..9b10cc7 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -1,1595 +1,16 @@ -# Astral Serialize 文档 +# php-serialize -## 快速开始 +**php-serialize** 是一个功能强大的基于属性(attribute)的 PHP 序列化库(需要 **PHP ≥ 8.1**)。 +它允许你将对象映射为数组或 JSON,并且可以基于相同的属性 **自动生成 OpenAPI 文档**。 -### 安装 +> 🚀 统一解决方案,支持 API 数据序列化和文档生成。 -使用 Composer 安装: +## ✨ 功能特色 -```bash -composer require astral/serialize -``` - -### 基本用法 - -```php -use Astral\Serialize\Serialize; - -class User extends Serialize { - public string $name, - public int $age -} - -// 从数组创建对象 -$user = User::from([ - 'name' => '张三', - 'age' => 30 -]); - -// 访问对象属性 -echo $user->name; // 输出: 张三 -echo $user->age; // 输出: 30 - -// 转换为数组 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'name' => '张三', -// 'age' => 30 -// ] -``` - -#### 其他特性 - -1. **不可变性**:只读属性在构造后无法修改 - -```php -use Astral\Serialize\Serialize; - -class User extends Serialize { - public function __construct( - public readonly string $name, - public readonly int $age - ) {} -} - -$user = User::from([ - 'name' => '张三', - 'age' => 30 -]); - -try { - $user->name = '李四'; // 编译时错误:无法修改只读属性 -} catch (Error $e) { - echo "只读属性不能被重新赋值"; -} -``` - -2. **类型安全的初始化** - -```php -$user = User::from([ - 'name' => 123, // 整数会被转换为字符串 - 'age' => '35' // 字符串会被转换为整数 -]); - -echo $user->name; // 输出: "123" -echo $user->age; // 输出: 35 -``` - -3. **构造函数初始化** - -```php -use Astral\Serialize\Serialize; - -class User extends Serialize { - public function __construct( - public readonly string $name, - public readonly int $age - ) { - // 可以在构造函数中添加额外的验证或处理逻辑 - if (strlen($name) < 2) { - throw new \InvalidArgumentException('名称太短'); - } - } -} -``` - -## DTO 转换 - -### 类型转换 - -#### 基本类型转换 - -##### 方式一:构造函数属性提升 - -```php -use Astral\Serialize\Serialize; - -class Profile extends Serialize { - public function __construct( - public string $username, - public int $score, - public float $balance, - public bool $isActive - ) {} -} -``` - -##### 方式二:传统属性定义 - -```php -use Astral\Serialize\Serialize; - -class Profile extends Serialize { - public string $username; - public int $score; - public float $balance; - public bool $isActive; -} - -// 两种方式都支持相同的类型转换 -$profile = Profile::from([ - 'username' => 123, // 整数转换为字符串 - 'score' => '100', // 字符串转换为整数 - 'balance' => '99.99', // 字符串转换为浮点数 - 'isActive' => 1 // 数字转换为布尔值 -]); - -// 转换为数组 -$profileArray = $profile->toArray(); -``` - -##### 方式三:只读属性 - -```php -use Astral\Serialize\Serialize; - -class Profile extends Serialize { - public readonly string $username; - public readonly int $score; - public readonly float $balance; - public readonly bool $isActive; - - // 手动初始化 - public function __construct( - string $username, - int $score, - float $balance, - bool $isActive - ) { - $this->username = $username; - $this->score = $score; - $this->balance = $balance; - $this->isActive = $isActive; - } -} -``` - -无论使用哪种方式,`Serialize` 类都能正常工作,并提供相同的类型转换和序列化功能。 - -#### 枚举转换 - -枚举转换提供了强大且灵活的枚举处理机制,支持多种枚举类型和转换场景。 - -- 支持 `tryFrom()` 和 `cases()` 方法的枚举类型 -- 输入时自动将字符串转换为枚举实例 -- 输出时自动将枚举转换为字符串(枚举名称) -- 提供灵活且安全的枚举处理机制 - -##### 普通枚举 - -```php -enum UserRole { - case ADMIN; - case EDITOR; - case VIEWER; -} - -class ComplexUser extends Serialize { - - public UserRole $role; - - // 支持多种枚举类型 - public UserStatus|UserRole $mixedStatus; -} - -$complexUser = ComplexUser::from([ - 'role' => 'ADMIN', // 自动转换为 UserRole::ADMIN - 'mixedStatus' => 'ACTIVE' // 可以是 UserStatus 或 UserRole -]); - -echo $complexUser->role; // 返回 UserRole枚举实例 - -$complexUserArray = $complexUser->toArray(); -// $complexUserArray 的内容: -// [ -// 'role' => 'ADMIN', -// 'mixedStatus' => 'ACTIVE' -// ] -``` - -##### 回退枚举 - -```php -use Astral\Serialize\Serialize; - -// BackedEnum -enum UserStatus: string { - case ACTIVE = 'active'; - case INACTIVE = 'inactive'; - case SUSPENDED = 'suspended'; -} - -// 定义带有枚举的用户类 -class User extends Serialize { - public string $name; - - // 支持 UnitEnum 和 BackedEnum - public UserStatus $status; - - // 支持多枚举类型 - public UserStatus|string $alternateStatus; -} - -// 创建用户对象 -$user = User::from([ - 'name' => '张三', - 'status' => 'active', // 自动转换为 UserStatus::ACTIVE - 'alternateStatus' => 'inactive' // 支持字符串或枚举值 -]); - -var_dump($user->status); // 输出: UserStatus::ACTIVE - -// 转换为数组 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'name' => '张三', -// 'status' => 'ACTIVE', // 输出枚举名称 -// 'alternateStatus' => 'INACTIVE' -// ] -``` - -#### Null 值转换规则详细示例 - -当属性不是可空类型(`?type`)时,`null` 值会根据目标类型自动转换: - -```php -use Astral\Serialize\Serialize; - -class NullConversionProfile extends Serialize { - public string $username; - public int $score; - public float $balance; - public array $tags; - public object $metadata; -} - -// Null 值转换示例 -$profile = NullConversionProfile::from([ - 'username' => null, // 转换为空字符串 '' - 'score' => null, // 转换为 0 - 'balance' => null, // 转换为 0.0 - 'tags' => null, // 转换为空数组 [] - 'metadata' => null // 转换为空对象 new stdClass() -]); - -// 验证转换结果 -echo $profile->username; // 输出: ""(空字符串) -echo $profile->score; // 输出: 0 -echo $profile->balance; // 输出: 0.0 -var_dump($profile->tags); // 输出: array(0) {} -var_dump($profile->metadata); // 输出: object(stdClass)#123 (0) {} - -// 布尔值的特殊处理 -try { - NullConversionProfile::from([ - 'isActive' => null // 这将抛出类型错误 - ]); -} catch (\TypeError $e) { - echo "布尔类型不支持 null 值:" . $e->getMessage(); -} -``` - -#### 可空类型的方案 - -对于需要接受 `null` 的场景,使用可空类型: - -```php -use Astral\Serialize\Serialize; - -class FlexibleProfile extends Serialize { - public function __construct( - public ?string $username, - public ?int $score, - public ?object $metadata, - public ?array $tags - ) {} -} - -// 创建包含 null 值的对象 -$profile = FlexibleProfile::from([ - 'username' => null, // 允许 null - 'score' => null, // 允许 null - 'metadata' => null, // 允许 null - 'tags' => null // 允许 null -]); - -// 转换为数组 -$profileArray = $profile->toArray(); -// $profileArray 的内容: -// [ -// 'username' => null, -// 'score' => null, -// 'metadata' => null, -// 'tags' => null -// ] - -// 验证可空类型的行为 -echo $profile->username; // 输出 null -``` - -#### 联合类型 - -1. 可以混合使用基本类型和对象类型 -2. 对象层级匹配 - 对于多个对象类型,会选择最匹配的类型 - 支持继承层级的智能匹配 -3. 动态类型处理 - 自动处理不同类型的输入 - 提供更加灵活的数据建模方式 - -```php -use Astral\Serialize\Serialize; - -// 定义一个基础用户类 -class User extends Serialize { - public string $name; - public int $age; -} - -// 定义一个管理员用户类 -class AdminUser extends User { - public string $role; -} - -class FlexibleData extends Serialize { - // 支持整数或字符串类型的标识符 - public int|string $flexibleId; - - // 支持用户对象或整数标识符 - public User|int $userIdentifier; - - // 支持多种复杂的联合类型 - public AdminUser|User|int $complexIdentifier; -} - -// 场景1:使用整数作为 flexibleId -$data1 = FlexibleData::from([ - 'flexibleId' => 123, - 'userIdentifier' => 456, - 'complexIdentifier' => 789 -]); - -$data1Array = $data1->toArray(); -// $data1Array 的内容: -// [ -// 'flexibleId' => 123, -// 'userIdentifier' => 456, -// 'complexIdentifier' => 789 -// ] - -// 场景2:使用字符串作为 flexibleId -$data2 = FlexibleData::from([ - 'flexibleId' => 'ABC123', - 'userIdentifier' => [ - 'name' => '张三', - 'age' => 30 - ], - 'complexIdentifier' => [ - 'name' => '李四', - 'age' => 25 - ] -]); - -echo $data2->userIdentifier; // 输出 User 对象 -echo $data2->complexIdentifier; // 输出 User 对象 - -$data2Array = $data2->toArray(); -// $data2Array 的内容: -// [ -// 'flexibleId' => 'ABC123', -// 'userIdentifier' => User Object ( -// ['name' => '张三', 'age' => 30] -// ), -// 'complexIdentifier' => User Object ( -// ['name' => '李四', 'age' => 25] -// ) -// ] - -// 场景3:使用管理员用户 -$data3 = FlexibleData::from([ - 'flexibleId' => 'USER001', - 'userIdentifier' => [ - 'name' => '王五', - 'age' => 35, - 'role' => 'admin' - ], - 'complexIdentifier' => [ - 'name' => '赵六', - 'age' => 40, - 'role' => 'super_admin' - ] -]); - -echo $data2->userIdentifier; // 输出 User 对象 -echo $data2->complexIdentifier; // 输出 AdminUser 对象 - -$data3Array = $data3->toArray(); -// $data3Array 的内容: -// [ -// 'flexibleId' => 'USER001', -// 'userIdentifier' => User Object ( -// ['name' => '王五', 'age' => 35] -// ), -// 'complexIdentifier' => AdminUser Object ( -// ['name' => '赵六', 'age' => 40, 'role' => 'super_admin'] -// ) -// ] -``` - -#### 数组对象转换 - -##### phpDoc定义 - -```php -use Astral\Serialize\Serialize; - -// 定义基础数组类型 -class ArrayOne extends Serialize { - public string $type = 'one'; - public string $name; -} - -class ArrayTwo extends Serialize { - public string $type = 'two'; - public string $code; -} - -class MultiArraySerialize extends Serialize { - // 场景1:混合类型数组 - /** @var (ArrayOne|ArrayTwo)[] */ - public array $mixedTypeArray; - - // 场景2:多类型数组 - /** @var ArrayOne[]|ArrayTwo[] */ - public array $multiTypeArray; - - // 场景3:键值对混合类型 - /** @var array(string, ArrayOne|ArrayTwo) */ - public array $keyValueMixedArray; -} - -// 场景1:混合类型数组 -$data1 = MultiArraySerialize::from( - mixedTypeArray : [ - ['name' => '张三'], // 转化 ArrayOne 对象 - ['code' => 'ABC123'], // 转化 ArrayTwo 对象 - ['name' => '李四'], // 转化 ArrayOne 对象 - ['code' => 'DEF456'] // 转化 ArrayTwo 对象 - ] -); - -$data1Array = $data1->toArray(); -// $data1Array 的内容: -// [ -// 'mixedTypeArray' => [ -// [0] => ArrayOne Object -// ( -// ['name' => '张三', 'type' => 'one'], -// ) -// [1] => ArrayTwo Object -// ( -// ['code' => 'ABC123', 'type' => 'two'], -// ) -// [2] => ArrayOne Object -// ( -// ['name' => '李四', 'type' => 'one'], -// ) -// [3] => ArrayTwo Object -// ( -// ['code' => 'DEF456', 'type' => 'two'], -// ) -// ] -// ] - -// 场景2:多类型数组 -$data2 = MultiArraySerialize::from( - multiTypeArray:[ - ['name' => '王五'], // 转化 ArrayOne 对象 - ['name' => '赵六'], // 转化 ArrayOne 对象 - ['code' => 'GHI789'] // 转化 ArrayTwo 对象 - ] -); - -$data2Array = $data2->toArray(); -// $data2Array 的内容: -// [ -// 'multiTypeArray' => [ -// ArrayOne Object ( -// ['name' => '王五', 'type' => 'one'] -// ), -// ArrayOne Object ( -// ['name' => '赵六', 'type' => 'one'] -// ), -// ArrayTwo Object ( -// ['code' => 'GHI789', 'type' => 'two'] -// ) -// ] -// ] - -// 场景3:键值对混合类型 -$data3 = MultiArraySerialize::from( - keyValueMixedArray: [ - 'user1' => ['name' => '张三'], // 转化 ArrayOne 对象 - 'system1' => ['code' => 'ABC123'], // 转化 ArrayTwo 对象 - 'user2' => ['name' => '李四'] // 转化 ArrayOne 对象 - ] -); - -$data3Array = $data3->toArray(); -// $data3Array 的内容: -// [ -// 'keyValueMixedArray' => [ -// 'user1' => ArrayOne Object ( -// ['name' => '张三', 'type' => 'one'] -// ), -// 'system1' => ArrayTwo Object ( -// ['code' => 'ABC123', 'type' => 'two'] -// ), -// 'user2' => ArrayOne Object ( -// ['name' => '李四', 'type' => 'one'] -// ) -// ] -// ] - -// 场景4:无法匹配时的处理 -$data4 = MultiArraySerialize::from( - mixedTypeArray : [ - ['unknown' => 'data1'], - ['another' => 'data2'] - ] -); - -$data4Array = $data4->toArray(); -// $data4Array 的内容: -// [ -// 'mixedTypeArray' => [ -// ['unknown' => 'data1'], -// ['another' => 'data2'] -// ] -// ] -``` - -### 注解类使用 - -#### 属性分组 - -属性分组提供了一种灵活的方式来控制属性的输入和输出行为,允许在不同场景下精细地管理数据转换。 - -##### 基本用法 - -在属性上使用 `#[Groups]` 注解来指定属性所属的分组。 - -```php -use Astral\Serialize\Attributes\Groups; -use Astral\Serialize\Serialize; - -class User extends Serialize { - - #[Groups('update','detail')] - public string $id; - - #[Groups('create', 'update', 'detail')] - public string $name; - - #[Groups('create','detail')] - public string $username; - - #[Groups('other')] - public string $sensitiveData; - - // 没有指定Group 的属性将会被默认分组在default分组中 - public string $noGroupInfo; - - // 构造函数参数也支持分组 - public function __construct( - #[Groups('create','detail')] - public readonly string $email, - - #[Groups('update','detail')] - public readonly int $score - ) {} -} - - - -// 使用 默认分组展示所有信息 -$user1 = User::from( - id:1, - name: '李四', - score: 100, - username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' -); - -// 使用默认分组 toArray,展示所有信息 -$defaultArray = $user1->toArray(); -// $defaultArray 的内容: -// [ -// 'id' => '1', -// 'name' => '李四', -// 'username' => 'username', -// 'score' => 100, -// 'email' => 'zhangsan@example.com', -// 'sensitiveData' => '机密信息', -// 'noGroupInfo' => '默认分组信息' -// ] - -// 指定分组内容输入 -$defaultArray = $user1->withGroups('create')->toArray(); -// 输出内容 -// [ -// 'name' => '李四', -// 'username' => 'username', -// 'email' => 'zhangsan@example.com', -// ] - -$defaultArray = $user1->withGroups(['detail','other'])->toArray(); -// 输出内容 -// [ -// 'id' => '1', -// 'name' => '李四', -// 'username' => 'username', -// 'score' => 100, -// 'email' => 'zhangsan@example.com', -// 'sensitiveData' => '机密信息', -// ] - - -// 使用 create 分组创建用户 只会接受group为create的数据信息 -$user2 = User::setGroups(['create'])->from( - id:1, - name: '李四', - score: 100, - username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' -); - -// 使用 create 分组 toArray -$createArray = $user2->toArray(); -// $createArray 的内容: -// [ -// 'name' => '李四', -// 'username' => 'username', -// 'email' => 'zhangsan@example.com', -// ] - -// 使用 update 分组更新用户 只会接受group为update的数据信息 -$user3 = User::setGroups(['update'])->from( - id:1, - name: '李四', - score: 100, - username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' -); - -// 使用 update 分组 toArray -$updateArray = $user3->toArray(); -// $updateArray 的内容: -// [ -// 'id' => '1', -// 'name' => '李四', -// 'score' => 100, -// ] - -// 使用 detail 和 other 展示用户 会接受group为detail和other的数据信息 -$user4 = User::setGroups(['detail','other'])->from( - id:1, - name: '李四', - score: 100, - username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' -); - -// 使用多个分组 toArray -$multiGroupArray = $user4->toArray(); -// $multiGroupArray 的内容: -// [ -// 'id' => '1', -// 'name' => '李四', -// 'username' => 'username', -// 'score' => 100, -// 'email' => 'zhangsan@example.com', -// 'sensitiveData' => '机密信息', -// ] -``` - -##### 嵌套类指定Group类展示 - -```php -class ComplexUser extends Serialize { - - public string $name; - - public int $sex; - - public ComplexNestedInfo $info; -} - -class ComplexNestedInfo extends Serialize { - - #[Groups(ComplexAUser::class)] - public float $money; - - public string $currency; -} - -// ComplexNestedInfo 会自动隐藏currency -$adminUser = ComplexUser::from( - name: '张三', - sex: 1, - info: [ - 'money' => 100.00, - 'currency' => 'CNY' - ]; -); - -// 输出数据 -$adminUserArray = $adminUser->toArray(); -// $adminUserArray 的内容: -// [ -// 'name' => '张三', -// 'sex' => 1, -// 'info' => ComplexNestedInfo Object ([ -// 'money' => 100.00 -// ]) -// ] -``` - -#### 名称映射 - -##### 基础使用 - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Serialize; - -class User extends Serialize { - // 输入时使用不同的属性名 - #[InputName('user_name')] - public string $name; - - // 输出时使用不同的属性名 - #[OutputName('user_id')] - public int $id; - - // 同时支持输入和输出不同名称 - #[InputName('register_time')] - #[OutputName('registeredAt')] - public DateTime $createdAt; -} - -// 使用不同名称的输入数据 -$user = User::from([ - 'user_name' => '张三', // 映射到 $name - 'id' => 123, // 保持不变 - 'register_time' => '2023-01-01 10:00:00' // 映射到 $createdAt -]); - -// 输出数据 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'name' => '张三', -// 'user_id' => 123, -// 'registeredAt' => '2023-01-01 10:00:00' -// ] -``` - -##### 多输入/输出名称处理 - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Serialize; - -class MultiOutputUser extends Serialize { - // 多个输出名称 - #[OutputName('user_id')] - #[OutputName('id')] - #[OutputName('userId')] - public int $id; - - // 多个输出名称 按照声明顺序取地一个匹配的name - #[InputName('user_name')] - #[InputName('other_name')] - #[InputName('userName')] - public int $name; - -} - -// 场景1:使用第一个匹配的输入名称 -$user1 = MultiInputUser::from([ - 'user_name' => '张三' // 使用 'user_name' -]); -echo $user1->name; // 输出 '张三' - -// 场景2:使用第二个匹配的输入名称 -$user2 = MultiInputUser::from([ - 'other_name' => '李四' // 使用 'other_name' -]); -echo $user2->name; // 输出 '李四' - -// 场景3:使用最后的输入名称 -$user3 = MultiInputUser::from([ - 'userName' => '王五' // 使用 'userName' -]); -echo $user3->name; // 输出 '王五' - -// 场景4:传入多个的时候 按照声明顺序取地一个匹配的name -$user4 = MultiInputUser::from([ - 'userName' => '王五', - 'other_name' => '李四', - 'user_name' => '张三', -]); -echo $user4->name; // 输出 '张三' - -// 创建用户对象 -$user = MultiOutputUser::from([ - 'id' => 123, - 'name' => '张三' -]); - -// 转换为数组 -// tips: 因为id 有多个outputname 所以输出了 ['user_id','id','userId'] -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'user_id' => 123, -// 'id' => 123, -// 'userId' => 123, -// ] -``` - -##### 复杂映射场景 - -```php -use Astral\Serialize\Serialize; - -class ComplexUser extends Serialize { - // 嵌套对象的名称映射 - #[InputName('user_profile')] - public UserProfile $profile; - - // 数组元素的名称映射 - #[InputName('user_tags')] - public array $tags; -} - -// 处理复杂的输入结构 -$complexUser = ComplexUser::from([ - 'user_profile' => [ - 'nickname' => '小明', - 'age' => 25 - ], - 'user_tags' => ['developer', 'programmer'] -]); - -// 转换为标准数组 -$complexUserArray = $complexUser->toArray(); -// $complexUserArray 的内容: -// [ -// 'profile' => UserProfile Object ([ -// 'nickname' => '小明', -// 'age' => 25 -// ]), -// 'tags' => ['developer', 'programmer'] -// ] -``` - -##### Mapper映射 - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Support\Mappers\{ - CamelCaseMapper, - SnakeCaseMapper, - PascalCaseMapper, - KebabCaseMapper -}; -use Astral\Serialize\Serialize; - -#[Groups('profile','api')] -class User extends Serialize { - // 直接指定映射名称 - #[InputName('user_name', groups: ['profile','api'])] - #[OutputName('userName', groups: ['profile','api'])] - public string $name; - - // 使用映射器进行风格转换 - #[InputName(CamelCaseMapper::class, groups: ['profile','api'])] - #[OutputName(SnakeCaseMapper::class, groups: ['profile','api'])] - public int $userId; - - // 支持多个映射和分组 - #[InputName('profile-email', groups: 'profile')] - #[OutputName('userEmail', groups: 'profile')] - public string $email; -} - -// 使用不同的映射策略 -$user = User::setGroups('profile')::from([ - 'user_name' => '张三', // 映射到 $name - 'userId' => 123, // 使用 CamelCaseMapper 转换 - 'profile-email' => 'user@example.com' // 仅在 'profile' 分组生效 -]); - -// 输出时应用不同的映射 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'userName' => '张三', -// 'user_id' => '三', -// 'userEmail' => user@example.com, -// ] -``` - -##### 全局类映射 - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Support\Mappers\{ - CamelCaseMapper, - SnakeCaseMapper, - PascalCaseMapper, - KebabCaseMapper -}; -use Astral\Serialize\Serialize; - -#[InputName(SnakeCaseMapper::class)] -#[OutputName(CamelCaseMapper::class)] -class GlobalMappedUser extends Serialize { - // 类级别的映射会自动应用到所有属性 - public string $firstName; - public string $lastName; - public int $userId; - public DateTime $registeredAt; -} - -// 使用全局映射 -$user = GlobalMappedUser::from([ - 'first_name' => '张', // 从蛇形映射到 firstName - 'last_name' => '三', // 从蛇形映射到 lastName - 'user_id' => 123, // 从蛇形映射到 userId - 'registered_at' => '2023-01-01' // 从蛇形映射到 registeredAt -]); - -// 输出时会转换为驼峰命名 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'firstName' => '张', -// 'lastName' => '三', -// 'userId' => 123, -// 'registeredAt' => '2023-01-01' -// ] -``` - -###### 属性映射大于类级映射 - -```php - -#[InputName(SnakeCaseMapper::class)] -class PartialOverrideUser extends Serialize { - #[InputName(PascalCaseMapper::class)] - public string $userName; // 优先使用帕斯卡命名映射 - - public string $userEmail; // 继续使用类级别的全局映射 -} - -$partialUser = PartialOverrideUser::from([ - 'User_name' => '张三', // 使用蛇形映射 - 'UserName' => '李四', // 使用帕斯卡映射 - 'user_email' => 'user@example.com' // 使用蛇形映射 -]); - -$partialUser->toArray(); -// $partialUser 的内容: -// [ -// 'userName' => '李四', -// 'userEmail' => 'user@example.com', -// ] -``` - -###### 全局类映射的分组使用 - -需要搭配`Groups`注解一起使用 - -```php -use Astral\Serialize\Attributes\Groups; -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Support\Mappers\{ - CamelCaseMapper, - SnakeCaseMapper, - PascalCaseMapper, - KebabCaseMapper -}; -use Astral\Serialize\Serialize; - - -#[InputName(SnakeCaseMapper::class, groups: 'external')] -#[InputName(CamelCaseMapper::class, groups: 'api')] -#[OutputName(PascalCaseMapper::class, groups: ['external','api'])] -class ComplexMappedUser extends Serialize { - - #[Groups('external', 'api')] - public string $firstName; - - #[Groups('external', 'api')] - public string $lastName; - - - #[InputName('full_name', groups: 'special')] - #[OutputName('userEmail', groups: 'api')] - #[Groups('external', 'api')] - public string $fullName; -} - -// 使用admin分组 -$complexUser = ComplexMappedUser::setGroup('external')->from( - first_name :'张', - last_name :'三' - full_name: '张三' -); - -$complexUser = $complexUser->toArray(); -// $complexUser 的内容: -// [ -// 'FirstName' => '张', -// 'LastName' => '三', -// 'FullName' => 张三, -// ] - -// 如果熟悉指定了OutputName/InputName 则属性规则优先 -// 使用public分组 -$complexUser = ComplexMappedUser::setGroup('api')->from( - first_name :'张', - last_name :'三' - full_name: '张三' -); - -$complexUser = $complexUser->toArray(); -// $complexUser 的内容: -// [ -// 'FirstName' => '张', -// 'LastName' => '三', -// 'userEmail' => 张三, -// ] -``` - -#### 自定义映射器 - -```php -// 自定义映射器 需要继承NameMapper 并实现 resolve -class CustomMapper implements NameMapper { - public function resolve(string $name): string { - // 实现自定义的命名转换逻辑 - return str_replace('user', 'customer', $name); - } -} - -class AdvancedUser extends Serialize { - #[InputName(CustomMapper::class)] - public string $name; -} -``` - -#### 字段忽略 - -1. **安全性控制** - - 防止敏感信息的意外泄露 - - 精细控制数据的输入和输出 - -2. **数据过滤** - - 根据不同场景过滤字段 - - 为不同的 API 或用户角色定制数据视图 - -3. **性能优化** - - 减少不必要字段的序列化开销 - - 精简数据传输 - -##### 基础使用 - -```php -use Astral\Serialize\Attributes\InputIgnore; -use Astral\Serialize\Attributes\OutputIgnore; -use Astral\Serialize\Serialize; - - -class User extends Serialize { - - public string $name; - - // 输入时忽略的字段 - #[InputIgnore] - public string $internalId; - - // 输出时忽略的字段 - #[OutputIgnore] - public string $tempData; -} - -// 创建用户对象 -$user = User::from([ - 'name' => '张三', - 'internalId' => 'secret123', // 这个字段会被忽略 - 'tempData' => 'temporary' // 这个字段会被忽略 -]); - -echo $user->internalId; // 这里会输出 '' - -// 转换为数组 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'name' => '张三', -// 'internalId' => '', -// ] -``` - -##### 分组忽略 - -忽略分组需要搭配Groups注解一起使用 - -```php -use Astral\Serialize\Attributes\Input\InputIgnore; -use Astral\Serialize\Attributes\Output\OutputIgnore; -use Astral\Serialize\Serialize; -use Astral\Serialize\Attributes\Groups; - -class ComplexUser extends Serialize { - - #[Groups('admin','public')] - #[InputIgnore('admin')] - public string $name; - - #[Groups('admin','public')] - #[OutputIgnore('public')] - public string $secretKey; - - #[Groups('admin','public')] - #[InputIgnore('admin')] - #[OutputIgnore('public')] - public string $sensitiveInfo; - - #[InputIgnore] - public string $globalInputIgnore; - - #[OutputIgnore] - public string $globalOutputIgnore; -} - -// 默认分组 -$complexUser = ComplexUser::from([ - 'name' => '张三', - 'secretKey' => 'confidential', - 'sensitiveInfo' => '机密信息', - 'globalInputIgnore' => '全局输入忽略', - 'globalOutputIgnore' => '全局输出忽略' -]); - -echo $complexUser->globalInputIgnore; // 输出 ‘’ -echo $complexUser->globalOutputIgnore; // 输出 ‘全局输出忽略’ - -$complexUser = $complexUser->toArray(); -// $complexUser 的内容: -// [ -// 'name' => '张三', -// 'secretKey' => 'confidential', -// 'sensitiveInfo' => '机密信息', -// 'globalInputIgnore' => '', -// ] - - -// 使用admin分组 -$complexUser = ComplexUser::setGroups('admin')->from([ - 'name' => '张三', - 'secretKey' => 'confidential', - 'sensitiveInfo' => '机密信息' - 'globalInputIgnore' => '全局输入忽略', - 'globalOutputIgnore' => '全局输出忽略' -]); - -$complexUser = $complexUser->toArray(); -// $complexUser 的内容: -// [ -// 'name' => '', -// 'secretKey' => 'confidential', -// 'globalInputIgnore' => '', -// ] - -// 使用public分组 -$complexUser = ComplexUser::setGroups('public')->from([ - 'name' => '张三', - 'secretKey' => 'confidential', - 'sensitiveInfo' => '机密信息' - 'globalInputIgnore' => '全局输入忽略', - 'globalOutputIgnore' => '全局输出忽略' -]); - -$complexUser = $complexUser->toArray(); -// $complexUser 的内容: -// [ -// 'name' => '张三', -/// 'globalInputIgnore' => '', -// ] -``` - -#### 时间转换 - -1. 格式灵活性 - 支持多种输入和输出时间格式 - 可以轻松处理不同地区的日期表示 -2. 时区处理 - 支持在不同时区间转换 - 自动处理时间的时区偏移 -3. 类型安全 - 自动将字符串转换为 DateTime 对象 - 保证类型的一致性和正确性 - -##### 基础使用 - -```php -use Astral\Serialize\Attributes\Input\InputDateFormat; -use Astral\Serialize\Attributes\Output\OutputDateFormat; -use Astral\Serialize\Serialize; - -class TimeExample extends Serialize { - - // 输入时间格式转换 - #[InputDateFormat('Y-m-d')] - public DateTime $dateTime; - - // 输入时间格式转换 - #[InputDateFormat('Y-m-d')] - public string $dateDateString; - - // 输出时间格式转换 - #[OutputDateFormat('Y/m/d H:i')] - public DateTime|string $processedAt; - - - // 支持多种时间格式 - #[InputDateFormat('Y-m-d H:i:s')] - #[OutputDateFormat('Y-m-d')] - public string $createdAt; -} - -// 创建订单对象 -$order = TimeExample::from([ - 'dateTime' => new DateTime('2023-08-11'), // 输入格式:Y-m-d - 'dateDateString' => '2023-08-15', // 输入格式:Y-m-d - 'processedAt' => '2023-08-16 14:30', // 输入默认格式 也支持DateTime对象 - 'createdAt' => '2023-08-16 14:45:30' // 输入格式:Y-m-d H:i:s -]); - -// 转换为数组 -$orderArray = $order->toArray(); -// $orderArray 的内容: -// [ -// 'dateTime' => '2023-08-11', -// ’dateDateString' => '2023-08-15', -// 'processedAt' => '2023/08/16 14:30', -// 'createdAt' => '2023-08-16' -// ] -``` - -##### 带时区的时间转换 - -```php - -use Astral\Serialize\Attributes\Input\InputDateFormat; -use Astral\Serialize\Attributes\Output\OutputDateFormat; -use Astral\Serialize\Serialize; - -class AdvancedTimeUser extends Serialize { - // 支持时区转换 - #[InputDateFormat('Y-m-d H:i:s', timezone: 'UTC')] - #[OutputDateFormat('Y-m-d H:i:s', timezone: 'Asia/Shanghai')] - public DateTime $registeredAt; - - // 支持不同地区的时间格式 - #[InputDateFormat('d/m/Y')] // 英国格式 - #[OutputDateFormat('Y-m-d')] // 标准格式 - public DateTime $birthDate; -} - -// 使用高级时间转换 -$advancedUser = AdvancedTimeUser::from([ - 'registeredAt' => '2023-08-16 10:00:00', // UTC 时间 - 'birthDate' => '15/08/1990' // 英国日期格式 -]); - -$advancedUserArray = $advancedUser->toArray(); -// $advancedUserArray 的内容: -// [ -// 'registeredAt' => '2023-08-16 18:00:00', // 转换为上海时区 -// 'birthDate' => '1990-08-15' -// ] -``` - -## Faker - -### 简单属性模拟 - -```php -class UserFaker extends Serialize { - #[FakerValue('name')] - public string $name; - - #[FakerValue('email')] - public string $email; - - #[FakerValue('uuid')] - public string $userId; - - #[FakerValue('phoneNumber')] - public string $phone; - - #[FakerValue('age')] - public int $age; - - #[FakerValue('boolean')] - public bool $isActive; -} - -$user = UserFaker::faker(); - -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// "name" => "John Doe" -// "email" => "john.doe@example.com" -// "userId" => "550e8400-e29b-41d4-a716-446655440000" -// "phone" => "+1-555-123-4567" -// "age" => 35 -// "isActive" => true -// ] -``` - -### 集合模拟 - -```php - -class UserProfile extends Serialize { - public string $nickname; - public int $age; - public string $email; - public string $avatar; -} - -class UserListFaker extends Serialize { - #[FakerCollection(['name', 'email'], num: 3)] - public array $users; - - #[FakerCollection(UserProfile::class, num: 2)] - public array $profiles; -} - -$userList = UserListFaker::faker(); - -$complexUserListFaker = UserListFaker::faker(); - -$complexUserListFakerArray = $complexUserListFaker->toArray(); -// $complexUserListFakerArray 的内容: -// [ -// 'profile' => [ -// [0] => UserProfile Object ( -// [ -// 'nickname' => 'RandomNickname', -// 'age' => 28, 'email' => 'random.user@example.com', -// 'avatar' => 'https://example.com/avatars/random-avatar.jpg' -// ], -// ), -// [1] => UserProfile Object ( -// [ -// 'nickname' => 'RandomNickname', -// 'age' => 28, 'email' => 'random.user@example.com', -// 'avatar' => 'https://example.com/avatars/random-avatar.jpg' -// ], -// ) -// ], -// 'users' => [ -// ['name' => 'RandomNickname', 'email' => 'RandomEmail@example.com'] -// ['name' => 'RandomNickname', 'email' => 'RandomEmail@example.com'] -// ['name' => 'RandomNickname', 'email' => 'RandomEmail@example.com'] -// ] -// ] -``` - -### 嵌套对象模拟 - -#### 基本用法 - -```php -class ComplexUserFaker extends Serialize { - #[FakerObject(UserProfile::class)] - public UserProfile $profile; -} -``` - -#### 演示实例 - -```php -use Astral\Serialize\Serialize; -use Astral\Serialize\Attributes\FakerObject; -use Astral\Serialize\Attributes\FakerCollection; - -class UserProfile extends Serialize { - public string $nickname; - public int $age; - public string $email; - public string $avatar; -} - -class UserTag extends Serialize { - public string $name; - public string $color; -} - -class ComplexUserFaker extends Serialize { - #[FakerObject(UserProfile::class)] - public UserProfile $profile; - - #[FakerObject(UserTag::class)] - public UserTag|UserProfile $primaryTag; - -} - -$complexUserFaker = ComplexUserFaker::faker(); - -$complexUserFakerArray = $complexUserFaker->toArray(); -// $complexUserFakerArray 的内容: -// [ -// 'profile' => UserProfile Object ( -// ['nickname' => 'RandomNickname', 'age' => 28, 'email' => 'random.user@example.com', 'avatar' => 'https://example.com/avatars/random-avatar.jpg'] -// ), -// 'primaryTag' => UserTag Object ( -// ['name' => 'Developer', 'color' => '#007bff'] -// ) -// ] -``` - -### Faker类方法模拟 - -```php -class UserService { - public function generateUserData(): array { - return ['name' => 'Generated User']; - } -} - -class UserFaker extends Serialize { - #[FakerMethod(UserService::class, 'generateUserData')] - public array $userData; -} -``` - -#### 完整的示例 - -```php -use Astral\Serialize\Serialize; -use Astral\Serialize\Attributes\Faker\FakerMethod; -use Astral\Serialize\Attributes\Faker\FakerObject; -use Astral\Serialize\Attributes\Faker\FakerCollection; - -// 用户配置文件类 -class UserProfile extends Serialize { - public string $nickname; - public int $age; - public string $email; - public array $types = ['type1' => 'money', 'type2' => 'score']; -} - -// 用户服务类,提供数据生成方法 -class UserService { - public function generateUserData(): array { - return [ - 'name' => 'Generated User', - 'email' => 'generated.user@example.com', - 'age' => 30 - ]; - } - - public function generateUserProfile(UserProfile $user): UserProfile { - return $user; - } - - public function generateUserList(int $count): array { - $users = []; - for ($i = 0; $i < $count; $i++) { - $users[] = [ - 'name' => "User {$i}", - 'email' => "user{$i}@example.com" - ]; - } - return $users; - } -} - -// Faker 方法模拟示例 -class UserFaker extends Serialize { - // 使用方法生成简单数据 - #[FakerMethod(UserService::class, 'generateUserData')] - public array $userData; - - // 使用方法生成对象 - #[FakerMethod(UserService::class, 'generateUserProfile')] - public UserProfile $userProfile; - - // 获取指定属性 - #[FakerMethod(UserService::class, 'generateUserProfile',returnType:'age')] - public int $age; - - // 获取指定属性 多级可以使用[.]链接 - #[FakerMethod(UserService::class, 'generateUserProfile',returnType:'types.type2')] - public string $type2; - - // 传入参数 - #[FakerMethod(UserService::class, 'generateUserList',params:['count'=> 3])] - public array $userList; -} - -// 生成模拟数据 -$userFaker = UserFaker::faker(); - -// 转换为数组 -$userFakerArray = $userFaker->toArray(); -// $userFakerArray 的内容: -// [ -// 'userData' => [ -// 'name' => 'Generated User', -// 'email' => 'generated.user@example.com', -// 'age' => 30 -// ], -// 'userProfile' => UserProfile Object ( -// [ -// 'nickname' => 'GeneratedNickname', -// 'age' => 25, // 随机生成 -// 'email' => 'profile@example.com' -// 'types' => ['type1' => 'money', 'type2' => 'score'] -// ] -// ), -// 'age' => 99 , // 随机生成 -// 'type2' => 'score', -// 'userList' => [ -// ['name' => 'User 0', 'email' => 'user0@example.com'], -// ['name' => 'User 1', 'email' => 'user1@example.com'], -// ['name' => 'User 2', 'email' => 'user2@example.com'] -// ] -// ] -``` +- 🏷️ 属性别名映射 +- 🔄 自动类型转换(例如 `DateTime ↔ string`) +- 🔁 支持深度对象嵌套 +- ❌ 支持跳过/排除字段 +- 🧩 递归 DTO(数据传输对象)序列化 +- 🧬 **基于对象定义自动生成 OpenAPI schema** +- ⚙️ 与框架无关 — 兼容 Laravel、Symfony 等框架 \ No newline at end of file diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md new file mode 100644 index 0000000..45772e4 --- /dev/null +++ b/docs/zh/SUMMARY.md @@ -0,0 +1,32 @@ +# Summary + +* [介绍](README.md) +* [快速开始](getting-started.md) + +## 属性转换 + +* [基本类型转换](mapper/base-mapper.md) +* [Null值转换](mapper/null-mapper.md) +* [枚举转换](mapper/enum-mapper.md) +* [数组对象转换](mapper/array-mapper.md) +* [联合类型转换](mapper/union-mapper.md) + +## 注解类使用 + +* [属性分组](annotation/group-annotation.md) +* [输入/输出映射](annotation/alisa-annotation.md) +* [Mapper映射](annotation/mapper-annotation.md) +* [输入/输出忽略](annotation/ignore-annotation.md) +* [时间格式映射](annotation/date-annotation.md) +* [自定义注解](annotation/customer-annotation.md) + +## 参数快速模拟生成 + +* [简单属性Faker](faker/value-faker.md) +* [集合Faker](faker/collection-faker.md) +* [嵌套Faker](faker/nested-faker.md) +* [方法Faker](faker/method-faker.md) + +## 自动创建OpenApi文档 + +* [基础演示](openapi/base-openapi.md) \ No newline at end of file diff --git a/docs/zh/annotation/alisa-annotation.md b/docs/zh/annotation/alisa-annotation.md new file mode 100644 index 0000000..ff61fb7 --- /dev/null +++ b/docs/zh/annotation/alisa-annotation.md @@ -0,0 +1,141 @@ +## 名称映射 + +### 基础使用 + +```php +use Astral\Serialize\Attributes\InputName; +use Astral\Serialize\Attributes\OutputName; +use Astral\Serialize\Serialize; + +class User extends Serialize { + // 输入时使用不同的属性名 + #[InputName('user_name')] + public string $name; + + // 输出时使用不同的属性名 + #[OutputName('user_id')] + public int $id; + + // 同时支持输入和输出不同名称 + #[InputName('register_time')] + #[OutputName('registeredAt')] + public DateTime $createdAt; +} + +// 使用不同名称的输入数据 +$user = User::from([ + 'user_name' => '张三', // 映射到 $name + 'id' => 123, // 保持不变 + 'register_time' => '2023-01-01 10:00:00' // 映射到 $createdAt +]); + +// 输出数据 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'name' => '张三', +// 'user_id' => 123, +// 'registeredAt' => '2023-01-01 10:00:00' +// ] +``` + +### 多输入/输出名称处理 + +```php +use Astral\Serialize\Attributes\InputName; +use Astral\Serialize\Attributes\OutputName; +use Astral\Serialize\Serialize; + +class MultiOutputUser extends Serialize { + // 多个输出名称 + #[OutputName('user_id')] + #[OutputName('id')] + #[OutputName('userId')] + public int $id; + + // 多个输出名称 按照声明顺序取地一个匹配的name + #[InputName('user_name')] + #[InputName('other_name')] + #[InputName('userName')] + public int $name; + +} + +// 场景1:使用第一个匹配的输入名称 +$user1 = MultiInputUser::from([ + 'user_name' => '张三' // 使用 'user_name' +]); +echo $user1->name; // 输出 '张三' + +// 场景2:使用第二个匹配的输入名称 +$user2 = MultiInputUser::from([ + 'other_name' => '李四' // 使用 'other_name' +]); +echo $user2->name; // 输出 '李四' + +// 场景3:使用最后的输入名称 +$user3 = MultiInputUser::from([ + 'userName' => '王五' // 使用 'userName' +]); +echo $user3->name; // 输出 '王五' + +// 场景4:传入多个的时候 按照声明顺序取地一个匹配的name +$user4 = MultiInputUser::from([ + 'userName' => '王五', + 'other_name' => '李四', + 'user_name' => '张三', +]); +echo $user4->name; // 输出 '张三' + +// 创建用户对象 +$user = MultiOutputUser::from([ + 'id' => 123, + 'name' => '张三' +]); + +// 转换为数组 +// tips: 因为id 有多个outputname 所以输出了 ['user_id','id','userId'] +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'user_id' => 123, +// 'id' => 123, +// 'userId' => 123, +// ] +``` + +### 复杂映射场景 + +```php +use Astral\Serialize\Serialize; + +class ComplexUser extends Serialize { + // 嵌套对象的名称映射 + #[InputName('user_profile')] + public UserProfile $profile; + + // 数组元素的名称映射 + #[InputName('user_tags')] + public array $tags; +} + +// 处理复杂的输入结构 +$complexUser = ComplexUser::from([ + 'user_profile' => [ + 'nickname' => '小明', + 'age' => 25 + ], + 'user_tags' => ['developer', 'programmer'] +]); + +// 转换为标准数组 +$complexUserArray = $complexUser->toArray(); +// $complexUserArray 的内容: +// [ +// 'profile' => UserProfile Object ([ +// 'nickname' => '小明', +// 'age' => 25 +// ]), +// 'tags' => ['developer', 'programmer'] +// ] +``` \ No newline at end of file diff --git a/docs/zh/annotation/customer-annotation.md b/docs/zh/annotation/customer-annotation.md new file mode 100644 index 0000000..330c759 --- /dev/null +++ b/docs/zh/annotation/customer-annotation.md @@ -0,0 +1,73 @@ +### 自定义注解类实现 + +你可以通过自定义注解类,灵活地扩展序列化库的输入输出处理逻辑。 + +--- + +#### 入参处理注解类 + +实现 `InputValueCastInterface` 接口,重写其中的 `match` 和 `resolve` 方法,来自定义输入数据的转换和处理。 + +- **`match`**:用于判断是否对当前值进行处理,返回 `true` 表示进入 `resolve`。 +- **`resolve`**:对匹配的输入值进行转换,并返回转换后的结果。 + +示例:给输入值添加自定义前缀的注解类 + +```php +use Astral\Serialize\Contracts\Attribute\InputValueCastInterface; +use Attribute; + +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] +class CustomerInput implements InputValueCastInterface +{ + public function __construct( + public string $prefix = '', + ) { + } + + public function match(mixed $value, DataCollection $collection, InputValueContext $context): bool + { + // 对所有输入值都生效 + return true; + } + + public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): mixed + { + // 给输入值添加前缀 + return $this->prefix . $value; + } +} +```` + +### 输出处理注解类 + +输出处理注解与输入处理注解类似,只是实现的接口不同——需要实现 `OutputValueCastInterface`,用以对序列化输出的值进行自定义转换。 + +示例:给序列化输出的值添加自定义后缀的注解类 + +```php +use Astral\Serialize\Contracts\Attribute\OutputValueCastInterface; +use Attribute; + +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] +class CustomerOutput implements OutputValueCastInterface +{ + public function __construct( + public string $suffix = '', + ) { + } + + public function match(mixed $value, DataCollection $collection, OutputValueContext $context): bool + { + // 对所有输出值都生效 + return true; + } + + public function resolve(mixed $value, DataCollection $collection, OutputValueContext $context): mixed + { + // 给输出值添加后缀 + return $value . $this->suffix; + } +} +``` + diff --git a/docs/zh/annotation/date-annotation.md b/docs/zh/annotation/date-annotation.md new file mode 100644 index 0000000..1d37142 --- /dev/null +++ b/docs/zh/annotation/date-annotation.md @@ -0,0 +1,92 @@ +## 时间转换 + +1. 格式灵活性 + 支持多种输入和输出时间格式 + 可以轻松处理不同地区的日期表示 +2. 时区处理 + 支持在不同时区间转换 + 自动处理时间的时区偏移 +3. 类型安全 + 自动将字符串转换为 DateTime 对象 + 保证类型的一致性和正确性 + +### 基础使用 + +```php +use Astral\Serialize\Attributes\Input\InputDateFormat; +use Astral\Serialize\Attributes\Output\OutputDateFormat; +use Astral\Serialize\Serialize; + +class TimeExample extends Serialize { + + // 输入时间格式转换 + #[InputDateFormat('Y-m-d')] + public DateTime $dateTime; + + // 输入时间格式转换 + #[InputDateFormat('Y-m-d')] + public string $dateDateString; + + // 输出时间格式转换 + #[OutputDateFormat('Y/m/d H:i')] + public DateTime|string $processedAt; + + + // 支持多种时间格式 + #[InputDateFormat('Y-m-d H:i:s')] + #[OutputDateFormat('Y-m-d')] + public string $createdAt; +} + +// 创建订单对象 +$order = TimeExample::from([ + 'dateTime' => new DateTime('2023-08-11'), // 输入格式:Y-m-d + 'dateDateString' => '2023-08-15', // 输入格式:Y-m-d + 'processedAt' => '2023-08-16 14:30', // 输入默认格式 也支持DateTime对象 + 'createdAt' => '2023-08-16 14:45:30' // 输入格式:Y-m-d H:i:s +]); + +// 转换为数组 +$orderArray = $order->toArray(); +// $orderArray 的内容: +// [ +// 'dateTime' => '2023-08-11', +// ’dateDateString' => '2023-08-15', +// 'processedAt' => '2023/08/16 14:30', +// 'createdAt' => '2023-08-16' +// ] +``` + +### 带时区的时间转换 + +```php + +use Astral\Serialize\Attributes\Input\InputDateFormat; +use Astral\Serialize\Attributes\Output\OutputDateFormat; +use Astral\Serialize\Serialize; + +class AdvancedTimeUser extends Serialize { + // 支持时区转换 + #[InputDateFormat('Y-m-d H:i:s', timezone: 'UTC')] + #[OutputDateFormat('Y-m-d H:i:s', timezone: 'Asia/Shanghai')] + public DateTime $registeredAt; + + // 支持不同地区的时间格式 + #[InputDateFormat('d/m/Y')] // 英国格式 + #[OutputDateFormat('Y-m-d')] // 标准格式 + public DateTime $birthDate; +} + +// 使用高级时间转换 +$advancedUser = AdvancedTimeUser::from([ + 'registeredAt' => '2023-08-16 10:00:00', // UTC 时间 + 'birthDate' => '15/08/1990' // 英国日期格式 +]); + +$advancedUserArray = $advancedUser->toArray(); +// $advancedUserArray 的内容: +// [ +// 'registeredAt' => '2023-08-16 18:00:00', // 转换为上海时区 +// 'birthDate' => '1990-08-15' +// ] +``` \ No newline at end of file diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md new file mode 100644 index 0000000..e38b948 --- /dev/null +++ b/docs/zh/annotation/group-annotation.md @@ -0,0 +1,161 @@ +## 属性分组(Groups) + +属性分组提供了一种灵活的方式来控制属性的输入和输出行为,允许在不同场景下精细地管理数据转换。 + +--- + +### 分组原理说明 + +- 使用 `#[Groups(...)]` 注解可将属性归类到一个或多个分组中。 +- 支持: + - **输入时** 按分组过滤数据字段 + - **输出时** 按分组筛选输出字段 +- 未指定分组的属性将自动归入 `"default"` 分组。 + +--- + +### 基本示例 + +```php +use Astral\Serialize\Attributes\Groups; +use Astral\Serialize\Serialize; + +class User extends Serialize { + + #[Groups('update', 'detail')] + public string $id; + + #[Groups('create', 'update', 'detail')] + public string $name; + + #[Groups('create', 'detail')] + public string $username; + + #[Groups('other')] + public string $sensitiveData; + + // 未指定分组,默认为 default 分组 + public string $noGroupInfo; + + public function __construct( + #[Groups('create', 'detail')] + public readonly string $email, + + #[Groups('update', 'detail')] + public readonly int $score + ) {} +} +``` + +### 按分组接收 + +```php +// 使用 create 分组创建用户,只接受 group=create 的字段 +$user = User::setGroups(['create'])->from([ + 'id' => 1, + 'name' => '李四', + 'score' => 100, + 'username' => 'username', + 'email' => 'zhangsan@example.com', + 'sensitiveData' => '机密信息', + 'noGroupInfo' => '默认信息' +]); + +$user->toArray(); +/* +[ + 'name' => '李四', + 'username' => 'username', + 'email' => 'zhangsan@example.com', +] +*/ +``` + +### 按分组输出 + +```php +$user = User::from([ + 'id' => 1, + 'name' => '李四', + 'score' => 100, + 'username' => 'username', + 'email' => 'zhangsan@example.com', + 'sensitiveData' => '机密信息', + 'noGroupInfo' => '默认信息' +]); + +// 默认输出所有字段 +$user->toArray(); +/* +[ + 'id' => '1', + 'name' => '李四', + 'username' => 'username', + 'score' => 100, + 'email' => 'zhangsan@example.com', + 'sensitiveData' => '机密信息', + 'noGroupInfo' => '默认信息' +] +*/ + +// 指定输出分组 +$user->withGroups('create')->toArray(); +/* +[ + 'name' => '李四', + 'username' => 'username', + 'email' => 'zhangsan@example.com', +] +*/ + +$user->withGroups(['detail', 'other'])->toArray(); +/* +[ + 'id' => '1', + 'name' => '李四', + 'username' => 'username', + 'score' => 100, + 'email' => 'zhangsan@example.com', + 'sensitiveData' => '机密信息', +] +*/ +``` + +### 嵌套对象的分组 + +```php +class ComplexUser extends Serialize { + public string $name; + public int $sex; + public ComplexNestedInfo $info; +} + +class ComplexNestedInfo extends Serialize { + #[Groups(ComplexUser::class)] + public float $money; + + public string $currency; +} + +$adminUser = ComplexUser::from([ + 'name' => '张三', + 'sex' => 1, + 'info' => [ + 'money' => 100.00, + 'currency' => 'CNY' + ], +]); + +// info只会输出$money +// 因为 ComplexNestedInfo 绑定了 ComplexUser的类Group +$adminUser->toArray(); +/* +[ + 'name' => '张三', + 'sex' => 1, + 'info' => [ + 'money' => 100.00 + ] +] +*/ +``` \ No newline at end of file diff --git a/docs/zh/annotation/ignore-annotation.md b/docs/zh/annotation/ignore-annotation.md new file mode 100644 index 0000000..7bfec48 --- /dev/null +++ b/docs/zh/annotation/ignore-annotation.md @@ -0,0 +1,140 @@ +## 字段忽略 + +1. **安全性控制** + - 防止敏感信息的意外泄露 + - 精细控制数据的输入和输出 + +2. **数据过滤** + - 根据不同场景过滤字段 + - 为不同的 API 或用户角色定制数据视图 + +3. **性能优化** + - 减少不必要字段的序列化开销 + - 精简数据传输 + +### 基础使用 + +```php +use Astral\Serialize\Attributes\InputIgnore; +use Astral\Serialize\Attributes\OutputIgnore; +use Astral\Serialize\Serialize; + + +class User extends Serialize { + + public string $name; + + // 输入时忽略的字段 + #[InputIgnore] + public string $internalId; + + // 输出时忽略的字段 + #[OutputIgnore] + public string $tempData; +} + +// 创建用户对象 +$user = User::from([ + 'name' => '张三', + 'internalId' => 'secret123', // 这个字段会被忽略 + 'tempData' => 'temporary' // 这个字段会被忽略 +]); + +echo $user->internalId; // 这里会输出 '' + +// 转换为数组 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'name' => '张三', +// 'internalId' => '', +// ] +``` + +### 分组忽略 + +忽略分组需要搭配Groups注解一起使用 + +```php +use Astral\Serialize\Attributes\Input\InputIgnore; +use Astral\Serialize\Attributes\Output\OutputIgnore; +use Astral\Serialize\Serialize; +use Astral\Serialize\Attributes\Groups; + +class ComplexUser extends Serialize { + + #[Groups('admin','public')] + #[InputIgnore('admin')] + public string $name; + + #[Groups('admin','public')] + #[OutputIgnore('public')] + public string $secretKey; + + #[Groups('admin','public')] + #[InputIgnore('admin')] + #[OutputIgnore('public')] + public string $sensitiveInfo; + + #[InputIgnore] + public string $globalInputIgnore; + + #[OutputIgnore] + public string $globalOutputIgnore; +} + +// 默认分组 +$complexUser = ComplexUser::from([ + 'name' => '张三', + 'secretKey' => 'confidential', + 'sensitiveInfo' => '机密信息', + 'globalInputIgnore' => '全局输入忽略', + 'globalOutputIgnore' => '全局输出忽略' +]); + +echo $complexUser->globalInputIgnore; // 输出 ‘’ +echo $complexUser->globalOutputIgnore; // 输出 ‘全局输出忽略’ + +$complexUser = $complexUser->toArray(); +// $complexUser 的内容: +// [ +// 'name' => '张三', +// 'secretKey' => 'confidential', +// 'sensitiveInfo' => '机密信息', +// 'globalInputIgnore' => '', +// ] + + +// 使用admin分组 +$complexUser = ComplexUser::setGroups('admin')->from([ + 'name' => '张三', + 'secretKey' => 'confidential', + 'sensitiveInfo' => '机密信息' + 'globalInputIgnore' => '全局输入忽略', + 'globalOutputIgnore' => '全局输出忽略' +]); + +$complexUser = $complexUser->toArray(); +// $complexUser 的内容: +// [ +// 'name' => '', +// 'secretKey' => 'confidential', +// 'globalInputIgnore' => '', +// ] + +// 使用public分组 +$complexUser = ComplexUser::setGroups('public')->from([ + 'name' => '张三', + 'secretKey' => 'confidential', + 'sensitiveInfo' => '机密信息' + 'globalInputIgnore' => '全局输入忽略', + 'globalOutputIgnore' => '全局输出忽略' +]); + +$complexUser = $complexUser->toArray(); +// $complexUser 的内容: +// [ +// 'name' => '张三', +/// 'globalInputIgnore' => '', +// ] +``` \ No newline at end of file diff --git a/docs/zh/annotation/mapper-annotation.md b/docs/zh/annotation/mapper-annotation.md new file mode 100644 index 0000000..61257d7 --- /dev/null +++ b/docs/zh/annotation/mapper-annotation.md @@ -0,0 +1,199 @@ +## Mapper映射 + +### 属性映射 + +```php +use Astral\Serialize\Attributes\InputName; +use Astral\Serialize\Attributes\OutputName; +use Astral\Serialize\Support\Mappers\{ + CamelCaseMapper, + SnakeCaseMapper, + PascalCaseMapper, + KebabCaseMapper +}; +use Astral\Serialize\Serialize; + +#[Groups('profile','api')] +class User extends Serialize { + // 直接指定映射名称 + #[InputName('user_name', groups: ['profile','api'])] + #[OutputName('userName', groups: ['profile','api'])] + public string $name; + + // 使用映射器进行风格转换 + #[InputName(CamelCaseMapper::class, groups: ['profile','api'])] + #[OutputName(SnakeCaseMapper::class, groups: ['profile','api'])] + public int $userId; + + // 支持多个映射和分组 + #[InputName('profile-email', groups: 'profile')] + #[OutputName('userEmail', groups: 'profile')] + public string $email; +} + +// 使用不同的映射策略 +$user = User::setGroups('profile')::from([ + 'user_name' => '张三', // 映射到 $name + 'userId' => 123, // 使用 CamelCaseMapper 转换 + 'profile-email' => 'user@example.com' // 仅在 'profile' 分组生效 +]); + +// 输出时应用不同的映射 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'userName' => '张三', +// 'user_id' => '三', +// 'userEmail' => user@example.com, +// ] +``` + +### 全局类映射 + +```php +use Astral\Serialize\Attributes\InputName; +use Astral\Serialize\Attributes\OutputName; +use Astral\Serialize\Support\Mappers\{ + CamelCaseMapper, + SnakeCaseMapper, + PascalCaseMapper, + KebabCaseMapper +}; +use Astral\Serialize\Serialize; + +#[InputName(SnakeCaseMapper::class)] +#[OutputName(CamelCaseMapper::class)] +class GlobalMappedUser extends Serialize { + // 类级别的映射会自动应用到所有属性 + public string $firstName; + public string $lastName; + public int $userId; + public DateTime $registeredAt; +} + +// 使用全局映射 +$user = GlobalMappedUser::from([ + 'first_name' => '张', // 从蛇形映射到 firstName + 'last_name' => '三', // 从蛇形映射到 lastName + 'user_id' => 123, // 从蛇形映射到 userId + 'registered_at' => '2023-01-01' // 从蛇形映射到 registeredAt +]); + +// 输出时会转换为驼峰命名 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'firstName' => '张', +// 'lastName' => '三', +// 'userId' => 123, +// 'registeredAt' => '2023-01-01' +// ] +``` + +### 全局类映射的分组使用 + +需要搭配`Groups`注解一起使用 + +```php +use Astral\Serialize\Attributes\Groups; +use Astral\Serialize\Attributes\InputName; +use Astral\Serialize\Attributes\OutputName; +use Astral\Serialize\Support\Mappers\{ + CamelCaseMapper, + SnakeCaseMapper, + PascalCaseMapper, + KebabCaseMapper +}; +use Astral\Serialize\Serialize; + +#[InputName(SnakeCaseMapper::class, groups: 'external')] +#[InputName(CamelCaseMapper::class, groups: 'api')] +#[OutputName(PascalCaseMapper::class, groups: ['external','api'])] +class ComplexMappedUser extends Serialize { + + #[Groups('external', 'api')] + public string $firstName; + + #[Groups('external', 'api')] + public string $lastName; + + + #[InputName('full_name', groups: 'special')] + #[OutputName('userEmail', groups: 'api')] + #[Groups('external', 'api')] + public string $fullName; +} + +// 使用admin分组 +$complexUser = ComplexMappedUser::setGroup('external')->from( + first_name :'张', + last_name :'三' + full_name: '张三' +); + +$complexUser = $complexUser->toArray(); +// $complexUser 的内容: +// [ +// 'FirstName' => '张', +// 'LastName' => '三', +// 'FullName' => 张三, +// ] + +// 如果熟悉指定了OutputName/InputName 则属性规则优先 +// 使用public分组 +$complexUser = ComplexMappedUser::setGroup('api')->from( + first_name :'张', + last_name :'三' + full_name: '张三' +); + +$complexUser = $complexUser->toArray(); +// $complexUser 的内容: +// [ +// 'FirstName' => '张', +// 'LastName' => '三', +// 'userEmail' => 张三, +// ] +``` +### 自定义映射器 + +```php +// 自定义映射器 需要继承NameMapper 并实现 resolve +class CustomMapper implements NameMapper { + public function resolve(string $name): string { + // 实现自定义的命名转换逻辑 + return str_replace('user', 'customer', $name); + } +} + +class AdvancedUser extends Serialize { + #[InputName(CustomMapper::class)] + public string $name; +} +``` + +### Tips:属性映射优先于类级映射 + +```php + +#[InputName(SnakeCaseMapper::class)] +class PartialOverrideUser extends Serialize { + #[InputName(PascalCaseMapper::class)] + public string $userName; // 优先使用帕斯卡命名映射 + + public string $userEmail; // 继续使用类级别的全局映射 +} + +$partialUser = PartialOverrideUser::from([ + 'User_name' => '张三', // 使用蛇形映射 + 'UserName' => '李四', // 使用帕斯卡映射 + 'user_email' => 'user@example.com' // 使用蛇形映射 +]); + +$partialUser->toArray(); +// $partialUser 的内容: +// [ +// 'userName' => '李四', +// 'userEmail' => 'user@example.com', +// ] +``` \ No newline at end of file diff --git a/docs/zh/description.md b/docs/zh/description.md new file mode 100644 index 0000000..9b10cc7 --- /dev/null +++ b/docs/zh/description.md @@ -0,0 +1,16 @@ +# php-serialize + +**php-serialize** 是一个功能强大的基于属性(attribute)的 PHP 序列化库(需要 **PHP ≥ 8.1**)。 +它允许你将对象映射为数组或 JSON,并且可以基于相同的属性 **自动生成 OpenAPI 文档**。 + +> 🚀 统一解决方案,支持 API 数据序列化和文档生成。 + +## ✨ 功能特色 + +- 🏷️ 属性别名映射 +- 🔄 自动类型转换(例如 `DateTime ↔ string`) +- 🔁 支持深度对象嵌套 +- ❌ 支持跳过/排除字段 +- 🧩 递归 DTO(数据传输对象)序列化 +- 🧬 **基于对象定义自动生成 OpenAPI schema** +- ⚙️ 与框架无关 — 兼容 Laravel、Symfony 等框架 \ No newline at end of file diff --git a/docs/zh/faker/collection-faker.md b/docs/zh/faker/collection-faker.md new file mode 100644 index 0000000..a7b2126 --- /dev/null +++ b/docs/zh/faker/collection-faker.md @@ -0,0 +1,49 @@ +## 集合模拟 + +```php + +class UserProfile extends Serialize { + public string $nickname; + public int $age; + public string $email; + public string $avatar; +} + +class UserListFaker extends Serialize { + #[FakerCollection(['name', 'email'], num: 3)] + public array $users; + + #[FakerCollection(UserProfile::class, num: 2)] + public array $profiles; +} + +$userList = UserListFaker::faker(); + +$complexUserListFaker = UserListFaker::faker(); + +$complexUserListFakerArray = $complexUserListFaker->toArray(); +// $complexUserListFakerArray 的内容: +// [ +// 'profile' => [ +// [0] => UserProfile Object ( +// [ +// 'nickname' => 'RandomNickname', +// 'age' => 28, 'email' => 'random.user@example.com', +// 'avatar' => 'https://example.com/avatars/random-avatar.jpg' +// ], +// ), +// [1] => UserProfile Object ( +// [ +// 'nickname' => 'RandomNickname', +// 'age' => 28, 'email' => 'random.user@example.com', +// 'avatar' => 'https://example.com/avatars/random-avatar.jpg' +// ], +// ) +// ], +// 'users' => [ +// ['name' => 'RandomNickname', 'email' => 'RandomEmail@example.com'] +// ['name' => 'RandomNickname', 'email' => 'RandomEmail@example.com'] +// ['name' => 'RandomNickname', 'email' => 'RandomEmail@example.com'] +// ] +// ] +``` diff --git a/docs/zh/faker/method-faker.md b/docs/zh/faker/method-faker.md new file mode 100644 index 0000000..5d7de3e --- /dev/null +++ b/docs/zh/faker/method-faker.md @@ -0,0 +1,111 @@ +## Faker类方法模拟 + +### 基本用法 + +```php +class UserService { + public function generateUserData(): array { + return ['name' => 'Generated User']; + } +} + +class UserFaker extends Serialize { + #[FakerMethod(UserService::class, 'generateUserData')] + public array $userData; +} +``` + +### 完整的示例 + +```php +use Astral\Serialize\Serialize; +use Astral\Serialize\Attributes\Faker\FakerMethod; +use Astral\Serialize\Attributes\Faker\FakerObject; +use Astral\Serialize\Attributes\Faker\FakerCollection; + +// 用户配置文件类 +class UserProfile extends Serialize { + public string $nickname; + public int $age; + public string $email; + public array $types = ['type1' => 'money', 'type2' => 'score']; +} + +// 用户服务类,提供数据生成方法 +class UserService { + public function generateUserData(): array { + return [ + 'name' => 'Generated User', + 'email' => 'generated.user@example.com', + 'age' => 30 + ]; + } + + public function generateUserProfile(UserProfile $user): UserProfile { + return $user; + } + + public function generateUserList(int $count): array { + $users = []; + for ($i = 0; $i < $count; $i++) { + $users[] = [ + 'name' => "User {$i}", + 'email' => "user{$i}@example.com" + ]; + } + return $users; + } +} + +// Faker 方法模拟示例 +class UserFaker extends Serialize { + // 使用方法生成简单数据 + #[FakerMethod(UserService::class, 'generateUserData')] + public array $userData; + + // 使用方法生成对象 + #[FakerMethod(UserService::class, 'generateUserProfile')] + public UserProfile $userProfile; + + // 获取指定属性 + #[FakerMethod(UserService::class, 'generateUserProfile',returnType:'age')] + public int $age; + + // 获取指定属性 多级可以使用[.]链接 + #[FakerMethod(UserService::class, 'generateUserProfile',returnType:'types.type2')] + public string $type2; + + // 传入参数 + #[FakerMethod(UserService::class, 'generateUserList',params:['count'=> 3])] + public array $userList; +} + +// 生成模拟数据 +$userFaker = UserFaker::faker(); + +// 转换为数组 +$userFakerArray = $userFaker->toArray(); +// $userFakerArray 的内容: +// [ +// 'userData' => [ +// 'name' => 'Generated User', +// 'email' => 'generated.user@example.com', +// 'age' => 30 +// ], +// 'userProfile' => UserProfile Object ( +// [ +// 'nickname' => 'GeneratedNickname', +// 'age' => 25, // 随机生成 +// 'email' => 'profile@example.com' +// 'types' => ['type1' => 'money', 'type2' => 'score'] +// ] +// ), +// 'age' => 99 , // 随机生成 +// 'type2' => 'score', +// 'userList' => [ +// ['name' => 'User 0', 'email' => 'user0@example.com'], +// ['name' => 'User 1', 'email' => 'user1@example.com'], +// ['name' => 'User 2', 'email' => 'user2@example.com'] +// ] +// ] +``` \ No newline at end of file diff --git a/docs/zh/faker/nested-faker.md b/docs/zh/faker/nested-faker.md new file mode 100644 index 0000000..b479a52 --- /dev/null +++ b/docs/zh/faker/nested-faker.md @@ -0,0 +1,52 @@ +## 嵌套对象模拟 + +### 基本用法 + +```php +class ComplexUserFaker extends Serialize { + #[FakerObject(UserProfile::class)] + public UserProfile $profile; +} +``` + +### 演示实例 + +```php +use Astral\Serialize\Serialize; +use Astral\Serialize\Attributes\FakerObject; +use Astral\Serialize\Attributes\FakerCollection; + +class UserProfile extends Serialize { + public string $nickname; + public int $age; + public string $email; + public string $avatar; +} + +class UserTag extends Serialize { + public string $name; + public string $color; +} + +class ComplexUserFaker extends Serialize { + #[FakerObject(UserProfile::class)] + public UserProfile $profile; + + #[FakerObject(UserTag::class)] + public UserTag|UserProfile $primaryTag; + +} + +$complexUserFaker = ComplexUserFaker::faker(); + +$complexUserFakerArray = $complexUserFaker->toArray(); +// $complexUserFakerArray 的内容: +// [ +// 'profile' => UserProfile Object ( +// ['nickname' => 'RandomNickname', 'age' => 28, 'email' => 'random.user@example.com', 'avatar' => 'https://example.com/avatars/random-avatar.jpg'] +// ), +// 'primaryTag' => UserTag Object ( +// ['name' => 'Developer', 'color' => '#007bff'] +// ) +// ] +``` diff --git a/docs/zh/faker/value-faker.md b/docs/zh/faker/value-faker.md new file mode 100644 index 0000000..8645904 --- /dev/null +++ b/docs/zh/faker/value-faker.md @@ -0,0 +1,36 @@ +## 简单属性模拟 + +```php +class UserFaker extends Serialize { + #[FakerValue('name')] + public string $name; + + #[FakerValue('email')] + public string $email; + + #[FakerValue('uuid')] + public string $userId; + + #[FakerValue('phoneNumber')] + public string $phone; + + #[FakerValue('age')] + public int $age; + + #[FakerValue('boolean')] + public bool $isActive; +} + +$user = UserFaker::faker(); + +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// "name" => "John Doe" +// "email" => "john.doe@example.com" +// "userId" => "550e8400-e29b-41d4-a716-446655440000" +// "phone" => "+1-555-123-4567" +// "age" => 35 +// "isActive" => true +// ] +``` \ No newline at end of file diff --git a/docs/zh/getting-started.md b/docs/zh/getting-started.md new file mode 100644 index 0000000..9a2108e --- /dev/null +++ b/docs/zh/getting-started.md @@ -0,0 +1,94 @@ +## 快速开始 + +### 安装 + +使用 Composer 安装: + +```bash +composer require astral/serialize +``` + +### 基本用法 + +```php +use Astral\Serialize\Serialize; + +class User extends Serialize { + public string $name, + public int $age +} + +// 从数组创建对象 +$user = User::from([ + 'name' => '张三', + 'age' => 30 +]); + +// 访问对象属性 +echo $user->name; // 输出: 张三 +echo $user->age; // 输出: 30 + +// 转换为数组 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'name' => '张三', +// 'age' => 30 +// ] +``` + +### 其他特性 + +#### **不可变性**:只读属性在构造后无法修改 + +```php +use Astral\Serialize\Serialize; + +class User extends Serialize { + public function __construct( + public readonly string $name, + public readonly int $age + ) {} +} + +$user = User::from([ + 'name' => '张三', + 'age' => 30 +]); + +try { + $user->name = '李四'; // 编译时错误:无法修改只读属性 +} catch (Error $e) { + echo "只读属性不能被重新赋值"; +} +``` + +#### **类型安全的初始化** + +```php +$user = User::from([ + 'name' => 123, // 整数会被转换为字符串 + 'age' => '35' // 字符串会被转换为整数 +]); + +echo $user->name; // 输出: "123" +echo $user->age; // 输出: 35 +``` + +#### **构造函数初始化** + +```php +use Astral\Serialize\Serialize; + +class User extends Serialize { + public function __construct( + public readonly string $name, + public readonly int $age + ) { + // 可以在构造函数中添加额外的验证或处理逻辑 + if (strlen($name) < 2) { + throw new \InvalidArgumentException('名称太短'); + } + } +} +``` \ No newline at end of file diff --git a/docs/zh/mapper/array-mapper.md b/docs/zh/mapper/array-mapper.md new file mode 100644 index 0000000..17434cc --- /dev/null +++ b/docs/zh/mapper/array-mapper.md @@ -0,0 +1,132 @@ +## 数组对象转换 + +### phpDoc定义 + +```php +use Astral\Serialize\Serialize; + +// 定义基础数组类型 +class ArrayOne extends Serialize { + public string $type = 'one'; + public string $name; +} + +class ArrayTwo extends Serialize { + public string $type = 'two'; + public string $code; +} + +class MultiArraySerialize extends Serialize { + // 场景1:混合类型数组 + /** @var (ArrayOne|ArrayTwo)[] */ + public array $mixedTypeArray; + + // 场景2:多类型数组 + /** @var ArrayOne[]|ArrayTwo[] */ + public array $multiTypeArray; + + // 场景3:键值对混合类型 + /** @var array(string, ArrayOne|ArrayTwo) */ + public array $keyValueMixedArray; +} + +// 场景1:混合类型数组 +$data1 = MultiArraySerialize::from( + mixedTypeArray : [ + ['name' => '张三'], // 转化 ArrayOne 对象 + ['code' => 'ABC123'], // 转化 ArrayTwo 对象 + ['name' => '李四'], // 转化 ArrayOne 对象 + ['code' => 'DEF456'] // 转化 ArrayTwo 对象 + ] +); + +$data1Array = $data1->toArray(); +// $data1Array 的内容: +// [ +// 'mixedTypeArray' => [ +// [0] => ArrayOne Object +// ( +// ['name' => '张三', 'type' => 'one'], +// ) +// [1] => ArrayTwo Object +// ( +// ['code' => 'ABC123', 'type' => 'two'], +// ) +// [2] => ArrayOne Object +// ( +// ['name' => '李四', 'type' => 'one'], +// ) +// [3] => ArrayTwo Object +// ( +// ['code' => 'DEF456', 'type' => 'two'], +// ) +// ] +// ] + +// 场景2:多类型数组 +$data2 = MultiArraySerialize::from( + multiTypeArray:[ + ['name' => '王五'], // 转化 ArrayOne 对象 + ['name' => '赵六'], // 转化 ArrayOne 对象 + ['code' => 'GHI789'] // 转化 ArrayTwo 对象 + ] +); + +$data2Array = $data2->toArray(); +// $data2Array 的内容: +// [ +// 'multiTypeArray' => [ +// ArrayOne Object ( +// ['name' => '王五', 'type' => 'one'] +// ), +// ArrayOne Object ( +// ['name' => '赵六', 'type' => 'one'] +// ), +// ArrayTwo Object ( +// ['code' => 'GHI789', 'type' => 'two'] +// ) +// ] +// ] + +// 场景3:键值对混合类型 +$data3 = MultiArraySerialize::from( + keyValueMixedArray: [ + 'user1' => ['name' => '张三'], // 转化 ArrayOne 对象 + 'system1' => ['code' => 'ABC123'], // 转化 ArrayTwo 对象 + 'user2' => ['name' => '李四'] // 转化 ArrayOne 对象 + ] +); + +$data3Array = $data3->toArray(); +// $data3Array 的内容: +// [ +// 'keyValueMixedArray' => [ +// 'user1' => ArrayOne Object ( +// ['name' => '张三', 'type' => 'one'] +// ), +// 'system1' => ArrayTwo Object ( +// ['code' => 'ABC123', 'type' => 'two'] +// ), +// 'user2' => ArrayOne Object ( +// ['name' => '李四', 'type' => 'one'] +// ) +// ] +// ] + +// 场景4:无法匹配时的处理 +$data4 = MultiArraySerialize::from( + mixedTypeArray : [ + ['unknown' => 'data1'], + ['another' => 'data2'] + ] +); + +$data4Array = $data4->toArray(); +// $data4Array 的内容: +// [ +// 'mixedTypeArray' => [ +// ['unknown' => 'data1'], +// ['another' => 'data2'] +// ] +// ] +``` \ No newline at end of file diff --git a/docs/zh/mapper/base-mapper.md b/docs/zh/mapper/base-mapper.md new file mode 100644 index 0000000..4dfc598 --- /dev/null +++ b/docs/zh/mapper/base-mapper.md @@ -0,0 +1,70 @@ +## 类型转换 + +### 基本类型转换 + +#### 方式一:构造函数属性提升 + +```php +use Astral\Serialize\Serialize; + +class Profile extends Serialize { + public function __construct( + public string $username, + public int $score, + public float $balance, + public bool $isActive + ) {} +} +``` + +#### 方式二:传统属性定义 + +```php +use Astral\Serialize\Serialize; + +class Profile extends Serialize { + public string $username; + public int $score; + public float $balance; + public bool $isActive; +} + +// 两种方式都支持相同的类型转换 +$profile = Profile::from([ + 'username' => 123, // 整数转换为字符串 + 'score' => '100', // 字符串转换为整数 + 'balance' => '99.99', // 字符串转换为浮点数 + 'isActive' => 1 // 数字转换为布尔值 +]); + +// 转换为数组 +$profileArray = $profile->toArray(); +``` + +#### 方式三:只读属性 + +```php +use Astral\Serialize\Serialize; + +class Profile extends Serialize { + public readonly string $username; + public readonly int $score; + public readonly float $balance; + public readonly bool $isActive; + + // 手动初始化 + public function __construct( + string $username, + int $score, + float $balance, + bool $isActive + ) { + $this->username = $username; + $this->score = $score; + $this->balance = $balance; + $this->isActive = $isActive; + } +} +``` + +无论使用哪种方式,`Serialize` 类都能正常工作,并提供相同的类型转换和序列化功能。 \ No newline at end of file diff --git a/docs/zh/mapper/enum-mapper.md b/docs/zh/mapper/enum-mapper.md new file mode 100644 index 0000000..f2b2b2b --- /dev/null +++ b/docs/zh/mapper/enum-mapper.md @@ -0,0 +1,82 @@ +## 枚举转换 + +枚举转换提供了强大且灵活的枚举处理机制,支持多种枚举类型和转换场景。 + +- 支持 `tryFrom()` 和 `cases()` 方法的枚举类型 +- 输入时自动将字符串转换为枚举实例 +- 输出时自动将枚举转换为字符串(枚举名称) +- 提供灵活且安全的枚举处理机制 + +### 普通枚举 + +```php +enum UserRole { + case ADMIN; + case EDITOR; + case VIEWER; +} + +class ComplexUser extends Serialize { + + public UserRole $role; + + // 支持多种枚举类型 + public UserStatus|UserRole $mixedStatus; +} + +$complexUser = ComplexUser::from([ + 'role' => 'ADMIN', // 自动转换为 UserRole::ADMIN + 'mixedStatus' => 'ACTIVE' // 可以是 UserStatus 或 UserRole +]); + +echo $complexUser->role; // 返回 UserRole枚举实例 + +$complexUserArray = $complexUser->toArray(); +// $complexUserArray 的内容: +// [ +// 'role' => 'ADMIN', +// 'mixedStatus' => 'ACTIVE' +// ] +``` + +### 回退枚举 + +```php +use Astral\Serialize\Serialize; + +// BackedEnum +enum UserStatus: string { + case ACTIVE = 'active'; + case INACTIVE = 'inactive'; + case SUSPENDED = 'suspended'; +} + +// 定义带有枚举的用户类 +class User extends Serialize { + public string $name; + + // 支持 UnitEnum 和 BackedEnum + public UserStatus $status; + + // 支持多枚举类型 + public UserStatus|string $alternateStatus; +} + +// 创建用户对象 +$user = User::from([ + 'name' => '张三', + 'status' => 'active', // 自动转换为 UserStatus::ACTIVE + 'alternateStatus' => 'inactive' // 支持字符串或枚举值 +]); + +var_dump($user->status); // 输出: UserStatus::ACTIVE + +// 转换为数组 +$userArray = $user->toArray(); +// $userArray 的内容: +// [ +// 'name' => '张三', +// 'status' => 'ACTIVE', // 输出枚举名称 +// 'alternateStatus' => 'INACTIVE' +// ] +``` \ No newline at end of file diff --git a/docs/zh/mapper/null-mapper.md b/docs/zh/mapper/null-mapper.md new file mode 100644 index 0000000..728e5c9 --- /dev/null +++ b/docs/zh/mapper/null-mapper.md @@ -0,0 +1,78 @@ +## Null值转换规则详细示例 + +当属性不是可空类型(`?type`)时,`null` 值会根据目标类型自动转换: + +```php +use Astral\Serialize\Serialize; + +class NullConversionProfile extends Serialize { + public string $username; + public int $score; + public float $balance; + public array $tags; + public object $metadata; +} + +// Null 值转换示例 +$profile = NullConversionProfile::from([ + 'username' => null, // 转换为空字符串 '' + 'score' => null, // 转换为 0 + 'balance' => null, // 转换为 0.0 + 'tags' => null, // 转换为空数组 [] + 'metadata' => null // 转换为空对象 new stdClass() +]); + +// 验证转换结果 +echo $profile->username; // 输出: ""(空字符串) +echo $profile->score; // 输出: 0 +echo $profile->balance; // 输出: 0.0 +var_dump($profile->tags); // 输出: array(0) {} +var_dump($profile->metadata); // 输出: object(stdClass)#123 (0) {} + +// 布尔值的特殊处理 +try { + NullConversionProfile::from([ + 'isActive' => null // 这将抛出类型错误 + ]); +} catch (\TypeError $e) { + echo "布尔类型不支持 null 值:" . $e->getMessage(); +} +``` + +## 可空类型的方案 + +对于需要接受 `null` 的场景,使用可空类型: + +```php +use Astral\Serialize\Serialize; + +class FlexibleProfile extends Serialize { + public function __construct( + public ?string $username, + public ?int $score, + public ?object $metadata, + public ?array $tags + ) {} +} + +// 创建包含 null 值的对象 +$profile = FlexibleProfile::from([ + 'username' => null, // 允许 null + 'score' => null, // 允许 null + 'metadata' => null, // 允许 null + 'tags' => null // 允许 null +]); + +// 转换为数组 +$profileArray = $profile->toArray(); +// $profileArray 的内容: +// [ +// 'username' => null, +// 'score' => null, +// 'metadata' => null, +// 'tags' => null +// ] + +// 验证可空类型的行为 +echo $profile->username; // 输出 null +``` \ No newline at end of file diff --git a/docs/zh/mapper/union-mapper.md b/docs/zh/mapper/union-mapper.md new file mode 100644 index 0000000..08d838e --- /dev/null +++ b/docs/zh/mapper/union-mapper.md @@ -0,0 +1,104 @@ +## 联合类型 + +1. 可以混合使用基本类型和对象类型 +2. 对象层级匹配。对于多个对象类型,会选择最匹配的类型。支持继承层级的智能匹配 +3. 动态类型处理,自动处理不同类型的,输入提供更加灵活的数据建模方式 + +```php +use Astral\Serialize\Serialize; + +// 定义一个基础用户类 +class User extends Serialize { + public string $name; + public int $age; +} + +// 定义一个管理员用户类 +class AdminUser extends User { + public string $role; +} + +class FlexibleData extends Serialize { + // 支持整数或字符串类型的标识符 + public int|string $flexibleId; + + // 支持用户对象或整数标识符 + public User|int $userIdentifier; + + // 支持多种复杂的联合类型 + public AdminUser|User|int $complexIdentifier; +} + +// 场景1:使用整数作为 flexibleId +$data1 = FlexibleData::from([ + 'flexibleId' => 123, + 'userIdentifier' => 456, + 'complexIdentifier' => 789 +]); + +$data1Array = $data1->toArray(); +// $data1Array 的内容: +// [ +// 'flexibleId' => 123, +// 'userIdentifier' => 456, +// 'complexIdentifier' => 789 +// ] + +// 场景2:使用字符串作为 flexibleId +$data2 = FlexibleData::from([ + 'flexibleId' => 'ABC123', + 'userIdentifier' => [ + 'name' => '张三', + 'age' => 30 + ], + 'complexIdentifier' => [ + 'name' => '李四', + 'age' => 25 + ] +]); + +echo $data2->userIdentifier; // 输出 User 对象 +echo $data2->complexIdentifier; // 输出 User 对象 + +$data2Array = $data2->toArray(); +// $data2Array 的内容: +// [ +// 'flexibleId' => 'ABC123', +// 'userIdentifier' => User Object ( +// ['name' => '张三', 'age' => 30] +// ), +// 'complexIdentifier' => User Object ( +// ['name' => '李四', 'age' => 25] +// ) +// ] + +// 场景3:使用管理员用户 +$data3 = FlexibleData::from([ + 'flexibleId' => 'USER001', + 'userIdentifier' => [ + 'name' => '王五', + 'age' => 35, + 'role' => 'admin' + ], + 'complexIdentifier' => [ + 'name' => '赵六', + 'age' => 40, + 'role' => 'super_admin' + ] +]); + +echo $data2->userIdentifier; // 输出 User 对象 +echo $data2->complexIdentifier; // 输出 AdminUser 对象 + +$data3Array = $data3->toArray(); +// $data3Array 的内容: +// [ +// 'flexibleId' => 'USER001', +// 'userIdentifier' => User Object ( +// ['name' => '王五', 'age' => 35] +// ), +// 'complexIdentifier' => AdminUser Object ( +// ['name' => '赵六', 'age' => 40, 'role' => 'super_admin'] +// ) +// ] +``` \ No newline at end of file diff --git a/docs/zh/openapi/base-openapi.md b/docs/zh/openapi/base-openapi.md new file mode 100644 index 0000000..d4b4c4e --- /dev/null +++ b/docs/zh/openapi/base-openapi.md @@ -0,0 +1,51 @@ +## 创建Request + +```php +use Astral\Serialize\Serialize; + +class UserAddRequest extends Serialize { + public string $name; + public int $id; +} + +class UserDetailRequest extends Serialize { + public int $id; +} +``` + +## 创建Repose +```php +use Astral\Serialize\Serialize; + +class UserDto extends Serialize { + public string $name, + public int $id; +} +``` + +## 创建Controller +```php +use Astral\Serialize\Serialize; +use Astral\Serialize\OpenApi\Enum\MethodEnum; + +#[\Astral\Serialize\OpenApi\Annotations\Tag('用户模块管理')] +class UserController { + + #[\Astral\Serialize\OpenApi\Annotations\Summary('创建用户')] + #[\Astral\Serialize\OpenApi\Annotations\Route('/user/create')] + #[\Astral\Serialize\OpenApi\Annotations\RequestBody(UserAddRequest::class)] + #[\Astral\Serialize\OpenApi\Annotations\Response(UserDto::class)] + public function create() + { + return new UserDto(); + } + + #[\Astral\Serialize\OpenApi\Annotations\Summary('用户详情')] + #[\Astral\Serialize\OpenApi\Annotations\Route(route:'/user/detail', method: MethodEnum::GET)] + public function detail(UserDetailRequest $request): UserDto + { + return new UserDto(); + } +} +``` +## 启动服务 \ No newline at end of file diff --git a/src/Annotations/Input/InputDateFormat.php b/src/Annotations/Input/InputDateFormat.php index 1a2b326..9d8b046 100755 --- a/src/Annotations/Input/InputDateFormat.php +++ b/src/Annotations/Input/InputDateFormat.php @@ -8,9 +8,7 @@ use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Context\InputValueContext; use Attribute; -use DateInvalidTimeZoneException; use DateTime; -use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; @@ -30,9 +28,7 @@ public function match(mixed $value, DataCollection $collection, InputValueContex return is_string($value) || is_numeric($value); } - /** - * @throws DateInvalidTimeZoneException - */ + public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): string|DateTime { diff --git a/src/Annotations/Output/OutputDateFormat.php b/src/Annotations/Output/OutputDateFormat.php index a727247..965bdf9 100755 --- a/src/Annotations/Output/OutputDateFormat.php +++ b/src/Annotations/Output/OutputDateFormat.php @@ -8,11 +8,10 @@ use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Context\OutContext; use Attribute; -use DateInvalidTimeZoneException; -use DateMalformedStringException; use DateTime; use DateTimeInterface; use DateTimeZone; +use Exception; /** * toArray 输出值为 固定日期格式 默认 YYYY-MM-DD HH:ii:ss的日期格式 @@ -31,19 +30,12 @@ public function match(mixed $value, DataCollection $collection, OutContext $cont return is_string($value) || is_numeric($value) || is_subclass_of($value, DateTimeInterface::class); } - /** - * @throws DateMalformedStringException - * @throws DateInvalidTimeZoneException - */ + public function resolve(mixed $value, DataCollection $collection, OutContext $context): string|DateTime|null { return $this->formatValue($value); } - /** - * @throws DateMalformedStringException - * @throws DateInvalidTimeZoneException - */ private function formatValue(mixed $value): ?string { $timezone = $this->timezone ? new DateTimeZone($this->timezone) : null; @@ -57,7 +49,7 @@ private function formatValue(mixed $value): ?string } /** - * @throws DateMalformedStringException + * @throws Exception */ private function formatDateTime(DateTimeInterface $dateTime, ?DateTimeZone $timezone): string { @@ -69,7 +61,7 @@ private function formatDateTime(DateTimeInterface $dateTime, ?DateTimeZone $time } /** - * @throws DateMalformedStringException + * @throws Exception */ private function formatTimestamp(int $timestamp, ?DateTimeZone $timezone): string { @@ -82,7 +74,7 @@ private function formatTimestamp(int $timestamp, ?DateTimeZone $timezone): strin } /** - * @throws DateMalformedStringException + * @throws Exception */ private function formatStringDate(string $value, ?DateTimeZone $timezone): string { diff --git a/src/Enums/TypeKindEnum.php b/src/Enums/TypeKindEnum.php index b56752f..438a013 100644 --- a/src/Enums/TypeKindEnum.php +++ b/src/Enums/TypeKindEnum.php @@ -2,7 +2,8 @@ namespace Astral\Serialize\Enums; -use http\Exception\RuntimeException; + +use RuntimeException; enum TypeKindEnum { diff --git a/src/OpenApi.php b/src/OpenApi.php new file mode 100755 index 0000000..1fa4e92 --- /dev/null +++ b/src/OpenApi.php @@ -0,0 +1,85 @@ +getAttributes(Tag::class); + /** @var Tag $tagDoc */ + $tagDoc = isset($tagDoc[0]) ? $tagDoc[0]->newInstance() : null; + + if ($tagDoc) { + self::$OpenAPI->addTag(new TagStorage($tagDoc->value, $tagDoc->description)); + } + + foreach ($classRefection->getMethods() as $item) { + + $methodAttributes = $item->getAttributes(); + + if (! $methodAttributes) { + continue; + } + + $instances = [ + Route::class => null, + Summary::class => null, + RequestBody::class => null, + Response::class => null, + Headers::class => null, + ]; + + foreach ($methodAttributes as $methodAttribute) { + $inst = $methodAttribute->newInstance(); + foreach (array_keys($instances) as $anchorClass) { + if ($inst instanceof $anchorClass) { + $instances[$anchorClass] = $inst; + } + } + } + + if ($instances[Route::class] === null || $instances[Summary::class] === null) { + continue; + } + + $openApiCollection = new OpenApiCollection( + controllerClass: $className, + methodName: $item->getName(), + reflectionMethod: $item, + tag: $tagDoc, + summary: $instances[Summary::class], + route: $instances[Route::class], + headers: $instances[Headers::class], + attributes: $methodAttributes, + requestBody: $instances[RequestBody::class], + response: $instances[Response::class], + ); + + self::$OpenAPI->addPath($openApiCollection); + } + } +} diff --git a/src/OpenApi/Annotations/Headers.php b/src/OpenApi/Annotations/Headers.php new file mode 100755 index 0000000..d5255f0 --- /dev/null +++ b/src/OpenApi/Annotations/Headers.php @@ -0,0 +1,24 @@ +'1'],['test-header'=>'test']] + * withOutHeaders: ['test-header','company-id'] + */ + public function __construct( + public array $headers = [], + public array $withOutHeaders = [] + ) { + } +} diff --git a/src/OpenApi/Annotations/OpenApi.php b/src/OpenApi/Annotations/OpenApi.php new file mode 100755 index 0000000..d10c6a3 --- /dev/null +++ b/src/OpenApi/Annotations/OpenApi.php @@ -0,0 +1,18 @@ +run(); \ No newline at end of file diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php new file mode 100644 index 0000000..d33e3c9 --- /dev/null +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -0,0 +1,158 @@ +route->method->value; + /** @var Method $openAPIMethod */ + $openAPIMethod = new $methodClass( + tags:[$this->tag->value ?: ''], + summary:$this->summary->value, + description:$this->summary->description ?: '' + ); + + $openAPIMethod->withRequestBody($this->requestBody !== null ? $this->buildRequestBodyByAttribute() : $this->buildRequestBodyByParameters()); + $openAPIMethod->addResponse(200, $this->buildResponse()); + return $openAPIMethod; + } + + /** + * @throws InvalidArgumentException + */ + public function buildRequestBodyByAttribute(): RequestBodyStorage + { + $openAPIRequestBody = new RequestBodyStorage($this->requestBody->contentType); + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($this->requestBody->className,$this->requestBody->groups)); + $openAPIRequestBody->withParameter($schemaStorage); + return $openAPIRequestBody; + } + + /** + * @throws InvalidArgumentException + */ + public function buildRequestBodyByParameters(): RequestBodyStorage + { + $openAPIRequestBody = new RequestBodyStorage(ContentTypeEnum::JSON); + $methodParam = $this->reflectionMethod->getParameters()[0] ?? null; + $type = $methodParam?->getType(); + $requestBodyClass = $type instanceof ReflectionNamedType ? $type->getName() : ''; + if (is_subclass_of($requestBodyClass, Serialize::class)) { + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($requestBodyClass)); + $openAPIRequestBody->withParameter($schemaStorage); + } + + return $openAPIRequestBody; + } + + /** + * @throws InvalidArgumentException + */ + public function buildResponse(): ResponseStorage + { + $returnClass = $this->reflectionMethod->getReturnType(); + $returnClass = $returnClass instanceof ReflectionNamedType ? $returnClass->getName() : null; + $responseClass = match(true){ + $this->response !== null => $this->response->className, + $returnClass && is_subclass_of($returnClass,Serialize::class) => $returnClass, + default => null, + }; + + $responseStorage = new ResponseStorage(); + + + if($responseClass) { + $groups = $this->response && is_array($this->response->groups) ? $this->response->groups : ['default']; + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($responseClass, $groups)); + $responseStorage->withParameter($schemaStorage); + } + + return $responseStorage; + } + + /** + * @param string $className + * @param array $groups + * @return array + * @throws InvalidArgumentException + */ + public function buildParameterCollections(string $className, array $groups = ['default']): array + { + $serializeContext = ContextFactory::build($className); + $serializeContext->from(); + $properties = $serializeContext->getGroupCollection()->getProperties(); + + $vols = []; + foreach ($properties as $property){ + $vol = new ParameterCollection( + className: $className, + name: current($property->getInputNamesByGroups($groups,$className)), + types: $property->getTypes(), + type: ParameterTypeEnum::getByTypes($property->getTypes()), + openApiAnnotation: $this->getOpenApiAnnotation($property->getAttributes()), + required: !$property->isNullable(), + ignore: false, + ); + + if($property->getChildren()){ + foreach ($property->getChildren() as $children){ + $className = $children->getClassName(); + $vol->children[$className] = $this->buildParameterCollections($className); + } + } + + $vols[] = $vol; + } + + return $vols; + } + + public function getOpenApiAnnotation(array $attributes): OpenApi|null + { + foreach ($attributes as $attribute){ + if($attribute->getName() === OpenApi::class){ + return $attribute->newInstance(); + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/OpenApi/Collections/ParameterCollection.php b/src/OpenApi/Collections/ParameterCollection.php new file mode 100755 index 0000000..2e2b901 --- /dev/null +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -0,0 +1,27 @@ + $children */ + public array $children = [], + ){ + } +} + diff --git a/src/OpenApi/Enum/ContentTypeEnum.php b/src/OpenApi/Enum/ContentTypeEnum.php new file mode 100755 index 0000000..cada37e --- /dev/null +++ b/src/OpenApi/Enum/ContentTypeEnum.php @@ -0,0 +1,10 @@ +kind === TypeKindEnum::STRING, $collection->kind === TypeKindEnum::ENUM => self::STRING, + $collection->kind === TypeKindEnum::INT => self::INTEGER, + $collection->kind === TypeKindEnum::FLOAT => self::NUMBER, + $collection->kind === TypeKindEnum::BOOLEAN => self::BOOLEAN, + default => null, + }; + } + + /** + * @param TypeCollection[] $types + * @param string $className + * @return ParameterTypeEnum|null + */ + + public static function getArrayAndObjectEnumBy(array $types, string $className): ?ParameterTypeEnum + { + + foreach ($types as $collection){ + if($className === $collection->className && in_array($collection->kind, [TypeKindEnum::CLASS_OBJECT, TypeKindEnum::OBJECT], true)){ + return self::OBJECT; + } + + if( $className === $collection->className && in_array($collection->kind, [TypeKindEnum::ARRAY, TypeKindEnum::COLLECT_SINGLE_OBJECT, TypeKindEnum::COLLECT_UNION_OBJECT], true)){ + return self::ARRAY; + } + } + + return null; + } + + public static function hasEnum(array $types): bool + { + foreach ($types as $type){ + if($type->kind === TypeKindEnum::ENUM){ + return true; + } + } + + return false; + } + + /** + * @param TypeCollection[] $types + */ + public static function getByTypes(array $types): ParameterTypeEnum + { + + $count = count($types); + + if($count === 1){ + $type = current($types)->kind; + return match (true){ + $type === TypeKindEnum::INT => self::INTEGER, + $type === TypeKindEnum::FLOAT => self::NUMBER, + $type === TypeKindEnum::BOOLEAN => self::BOOLEAN, + $type === TypeKindEnum::OBJECT, $type === TypeKindEnum::CLASS_OBJECT => self::OBJECT, + $type === TypeKindEnum::ARRAY, $type === TypeKindEnum::COLLECT_SINGLE_OBJECT , $type === TypeKindEnum::COLLECT_UNION_OBJECT => self::ARRAY, + default => self::STRING, + }; + } + + $hasUnion = false; + $unionTypes = []; + foreach ($types as $type){ + + if($type->kind === TypeKindEnum::ENUM){ + $unionTypes[TypeKindEnum::STRING->name] = $type; + }else{ + $unionTypes[$type->kind->name] = $type; + } + + if($type->kind === TypeKindEnum::COLLECT_UNION_OBJECT){ + $hasUnion = true; + } + } + + if(!$hasUnion && count($unionTypes) === 1){ + $type = current($types)->kind; + return match (true){ + $type === TypeKindEnum::INT => self::INTEGER, + $type === TypeKindEnum::FLOAT => self::NUMBER, + $type === TypeKindEnum::BOOLEAN => self::BOOLEAN, + in_array($type, [TypeKindEnum::OBJECT, TypeKindEnum::CLASS_OBJECT, TypeKindEnum::ARRAY, TypeKindEnum::COLLECT_SINGLE_OBJECT, TypeKindEnum::COLLECT_UNION_OBJECT], true) => self::ONE_OF, + default => self::STRING, + }; + } + + return $hasUnion ? self::ANY_OF : self::ONE_OF; + + } +} diff --git a/src/OpenApi/Formatter/FormatterInterface.php b/src/OpenApi/Formatter/FormatterInterface.php new file mode 100755 index 0000000..7846ee1 --- /dev/null +++ b/src/OpenApi/Formatter/FormatterInterface.php @@ -0,0 +1,12 @@ +withApiInfo(new ApiInfo(Config::get('title'), Config::get('description'))) + ->withServers(Config::get('service')); + + if(Config::has('headers')) { + foreach (Config::get('headers') as $header) { + $this->headerParameterStorages->addHeaderProperties($header['name'], $header['description'], $header['example']); + } + } + } + + /** + * 构建OpenApi结构文档 + * + * @param class-string $className + * @throws ReflectionException + * @throws Exception + */ + abstract public function buildByClass(string $className): void; + + /** + * 向全局头部参数存储中添加一个新的头部参数。 + * + * @param string $name 参数的名称。 + * @param string $example 参数的示例值,默认为空字符串。 + * @param string $description 参数的描述,默认为空字符串。 + * @return self 返回对象自身,支持链式调用。 + */ + public function addGlobalHeader(string $name, string $example = '', string $description = ''): self + { + $this->headerParameterStorages->addHeaderProperties($name, $description, $example); + return $this; + } + + /** + * 遍历整个项目根目录,自动扫描所有 PHP 文件, + * 如果文件内容中包含 "Astral\Serialize\OpenApi\Annotations", + * 则认为它是需要处理的 Controller,进而调用 buildByClass。 + * + * @return $this + * @throws ReflectionException + */ + public function handleByFolders(): self + { + // 1. 取项目根目录 + $projectRoot = getcwd(); + + if ($projectRoot === false) { + return $this; + } + + // 2. 默认根命名空间留空(根据项目实际情况可改为 "App"、"App\\Http\\Controllers" 等) + // 如果你希望扫描时拼接命名空间前缀,可以在这里做修改。 + $rootNamespace = ''; + + // 3. 调用内部递归方法开始扫描 + $this->scanFolderRecursively($projectRoot, $rootNamespace); + + return $this; + } + + /** + * 递归扫描指定目录下的所有子目录和文件。 + * @param string $folder 要扫描的文件夹路径 + * @param string $namespace 该文件夹对应的 PHP 命名空间前缀 + * + * @throws ReflectionException + */ + protected function scanFolderRecursively(string $folder, string $namespace): void + { + // 如果不是目录,跳过 + if (! is_dir($folder)) { + return; + } + + // 遍历当前文件夹下的所有内容 + foreach (scandir($folder) as $file) { + // 跳过 . 和 .. ,以及隐藏文件夹/文件 + if ($file === '.' || $file === '..' || str_starts_with($file, '.')) { + continue; + } + + $path = $folder . DIRECTORY_SEPARATOR . $file; + + // 如果是子目录,则递归,并拼接命名空间 + if (is_dir($path)) { + // 例如,如果当前命名空间是 "App": + // 子目录 "Http" 则新的命名空间为 "App\Http" + $newNamespace = $namespace !== '' ? ($namespace . '\\' . $file) : $file; + $this->scanFolderRecursively($path, $newNamespace); + continue; + } + + // 只处理 .php 文件 + if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') { + continue; + } + + // 读取文件内容,检查是否包含 Annotations 关键字 + $fileContent = @file_get_contents($path); + if ($fileContent === false) { + continue; + } + + // 如果文件中没有引入 Astral\Serialize\OpenApi\Annotations,就跳过 + if (!str_contains($fileContent, 'Astral\\Serialize\\OpenApi\\Annotations')) { + continue; + } + + // 计算类名:去掉 .php 之后,将命名空间前缀 + 文件名 组成完整类名 + $baseName = substr($file, 0, -4); // 去掉 ".php" + $className = $namespace !== '' ? ($namespace . '\\' . $baseName) : $baseName; + + // 如果类尚未加载,则尝试 include + if (! class_exists($className)) { + continue; + } + + // 调用子类实现的 buildByClass + $this->buildByClass($className); + } + } + + /** + * @throws JsonException + */ + public function toString(): string + { + return json_encode(self::$OpenAPI, JSON_THROW_ON_ERROR); + } +} diff --git a/src/OpenApi/Storage/OpenAPI/ApiInfo.php b/src/OpenApi/Storage/OpenAPI/ApiInfo.php new file mode 100755 index 0000000..f30a72e --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/ApiInfo.php @@ -0,0 +1,20 @@ + $responses */ + public array|stdClass $responses = new stdClass(), + ) { + } + + public function withTags(array $tags): Method + { + $this->tags = $tags; + + return $this; + } + + public function withParameters(array $parameters): static + { + $this->parameters = $parameters; + + return $this; + } + + public function addParameters(array $parameters): void + { + $this->parameters[] = $parameters; + } + + /** + * @return $this + */ + public function withRequestBody(RequestBodyStorage $body): static + { + $this->requestBody = $body->getData(); + return $this; + } + + /** + * @param int $code + * @param ResponseStorage $response + * @return $this + */ + public function addResponse(int $code, ResponseStorage $response): static + { + + $this->responses = []; + $this->responses[$code] = $response->getData(); + + return $this; + } +} diff --git a/src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php b/src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php new file mode 100755 index 0000000..1d4d7c6 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php @@ -0,0 +1,9 @@ + */ + public array $tags; + + /** @var array */ + public array $paths = []; + + public function withApiInfo(ApiInfo $apiInfo): self + { + $this->info = $apiInfo; + return $this; + } + + + public function withServers(array $servers): self + { + $this->servers = $servers; + return $this; + } + + public function withTags(array $tags): self + { + $this->tags = $tags; + return $this; + } + + public function addTag(TagStorage $tag): void + { + $this->tags[] = $tag; + } + + public function withPaths(array $paths): self + { + $this->paths = $paths; + return $this; + } + + public function addPath(OpenApiCollection $openApiCollection): self + { + $this->paths[$openApiCollection->route->route][strtolower($openApiCollection->route->method->name)] = $openApiCollection->build(); + return $this; + } + + +} diff --git a/src/OpenApi/Storage/OpenAPI/ParameterStorage.php b/src/OpenApi/Storage/OpenAPI/ParameterStorage.php new file mode 100755 index 0000000..6479298 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/ParameterStorage.php @@ -0,0 +1,73 @@ +data[] = [ + 'in' => 'query', + 'name' => $name, + 'description' => $description, + 'schema' => ['type' => $type], + 'example' => $example, + 'required' => $required, + ]; + } + + public function addHeaderProperties(string $name, string $description, string $example): void + { + $this->data[] = [ + 'in' => 'header', + 'name' => $name, + 'description' => $description, + 'schema' => ['type' => 'string'], + 'example' => $example, + 'required' => false, + ]; + } + + public function deleteHeadersProperties(array $names = []): void + { + if (! $names) { + return; + } + + foreach ($this->data as $key => ['in' => $type,'name' => $headerName]) { + if ($type === 'header' && in_array($headerName, $names, true)) { + unset($this->data[$key]); + break; + } + } + } + + public function getData(): array + { + return $this->data; + } +} diff --git a/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php new file mode 100755 index 0000000..1444711 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php @@ -0,0 +1,39 @@ +parameters = $schema->getData(); + } + + public function getData(): array + { + return [ + 'required' => true, + 'content' => [ + $this->contentType->value => [ + 'schema' => $this->parameters + ] + ] + ]; + } +} diff --git a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php new file mode 100755 index 0000000..578edd4 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php @@ -0,0 +1,37 @@ +parameter = $schema->getData(); + return $this; + } + + public function getData(): array + { + return [ + 'description' => $this->description, + 'content' => [ + $this->contentType => [ + 'schema' => $this->parameter + ] + ] + ]; + } +} diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php new file mode 100755 index 0000000..ea2ff5a --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -0,0 +1,184 @@ + 'object', + 'properties' => [], + ]; + + /** + * 获取构建的 Schema 数据 + */ + public function getData(): array + { + return $this->data; + } + + /** + * 构建 OpenAPI Schema 数据结构 + * + * @param array $parameterTree 参数集合树 + * @param array|null $currentNode 当前构建节点的引用 + * @param-out array $currentNode 当前构建节点的引用 + * @return static + */ + public function build(array $parameterTree, array &$currentNode = null): static + { + if ($currentNode === null) { + $currentNode = &$this->data; + } + + foreach ($parameterTree as $parameter) { + + + // 跳过被标记为忽略的参数 + if ($parameter->ignore) { + continue; + } + + // 构建基础属性 Schema + $this->buildBasicPropertySchema($parameter, $currentNode); + + // oneOf/anyOf/allOf 格式 + if($parameter->type->isOf()){ + $this->buildOfProperties($parameter, $currentNode); + } + // 处理嵌套子属性 + else if ($parameter->children) { + $this->buildNestedProperties($parameter, $currentNode); + } + } + + return $this; + } + + /** + * 构建基础属性的 Schema 结构 + */ + private function buildBasicPropertySchema(ParameterCollection $parameter, array &$currentNode): void + { + $propertyName = $parameter->name; + + $currentNode['properties'][$propertyName] = [ + 'type' => $parameter->type->value, + 'description' => $this->getDescriptions($parameter), + 'example' => $parameter->openApiAnnotation?->example, + ]; + + // 添加必填字段标记 + if ($parameter->required) { + $currentNode['required'][] = $propertyName; + } + } + + /** + * 构建 oneOf/anyOf/allOf 属性结构 + */ + public function buildOfProperties(ParameterCollection $topParameter, array &$currentNode): void + { + $propertyName = $topParameter->name; + // 重构属性结构为 oneOf/anyOf/allOf 格式 + $node = &$currentNode['properties'][$propertyName][$topParameter->type->value]; + + $i = 0; + $addedTypes = []; + foreach ($topParameter->types as $kindType){ + $type = ParameterTypeEnum::getBaseEnumByTypeKindEnum($kindType); + if ($type && !in_array($type->value, $addedTypes, true)) { + $node[$i] = ['type' => $type->value]; + $addedTypes[] = $type->value; + $i++; + } + } + + if($topParameter->children){ + foreach ($topParameter->children as $className => $child){ + $type = ParameterTypeEnum::getArrayAndObjectEnumBy($topParameter->types,$className); + if($type->isObject()){ + $node[$i] = ['type'=>'object','properties' => []]; + $childNode = &$node[$i]; + $i++; + }else if($type->isArray()){ + $node[$i] = ['type'=>'array','items'=> ['type'=>'object','properties' => []]]; + $childNode = &$node[$i]['items']; + $i++; + } + + $this->build($child,$childNode); + } + } + + } + + /** + * 构建嵌套属性结构 + */ + private function buildNestedProperties(ParameterCollection $topParameter, array &$currentNode): void + { + $propertyName = $topParameter->name; + $nestedNode = null; + + if ($topParameter->type->isArray()) { + // 数组类型:创建 items 结构 + $currentNode['properties'][$propertyName]['items'] = [ + 'type' => 'object', + 'properties' => [], + ]; + $nestedNode = &$currentNode['properties'][$propertyName]['items']; + } elseif ($topParameter->type->isObject()) { + // 对象类型:重构为嵌套对象结构 + $currentNode['properties'][$propertyName] = [ + 'type' => 'object', + 'properties' => [], + 'description' => $this->getDescriptions($topParameter), + ]; + $nestedNode = &$currentNode['properties'][$propertyName]; + } + + // 递归构建子属性 + if ($nestedNode !== null) { + foreach ($topParameter->children as $childParameter) { + $this->build($childParameter, $nestedNode); + } + } + } + + public function getDescriptions(ParameterCollection $parameter):string + { + $description = $parameter->openApiAnnotation->description ?? ''; + if(!ParameterTypeEnum::hasEnum($parameter->types)){ + return $description; + } + + $names = []; + foreach ($parameter->types as $type) { + if (TypeKindEnum::ENUM === $type->kind && enum_exists($type->className)) { + foreach ($type->className::cases() as $case) { + $names[$case->name] = $case->name; + } + } + } + + $description .= ($description ? ' ' : '').'optional values:' . implode('、', $names); + + return $description; + } +} \ No newline at end of file diff --git a/src/OpenApi/Storage/OpenAPI/ServersStorage.php b/src/OpenApi/Storage/OpenAPI/ServersStorage.php new file mode 100755 index 0000000..cfab890 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/ServersStorage.php @@ -0,0 +1,28 @@ +variables = $this->variables instanceof stdClass ? [] : $this->variables; + + $this->variables[$name] = ['default' => $default, 'description'=> $description]; + + return $this; + } +} diff --git a/src/OpenApi/Storage/OpenAPI/TagStorage.php b/src/OpenApi/Storage/OpenAPI/TagStorage.php new file mode 100755 index 0000000..3651691 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/TagStorage.php @@ -0,0 +1,16 @@ +setValue($value); + } + + public function addChildren(TreeNode $child): void + { + $this->children[] = $child; + } + + public function setValue($value): void + { + $this->value = $value; + } + + public function getValue(): mixed + { + return $this->value; + } + + /** + * @return TreeNode[] + */ + public function getChildren(): array + { + return $this->children; + } +} diff --git a/src/OpenApi/Web/Index.php b/src/OpenApi/Web/Index.php new file mode 100644 index 0000000..eecbb44 --- /dev/null +++ b/src/OpenApi/Web/Index.php @@ -0,0 +1,12 @@ +handleByFolders()->toString(); +} catch (JsonException|ReflectionException $e) { + throw new RuntimeException($e->getMessage(),$e->getCode(),$e); +} \ No newline at end of file diff --git a/src/Resolvers/PropertyTypeDocResolver.php b/src/Resolvers/PropertyTypeDocResolver.php index 2cdd054..bdf59cd 100644 --- a/src/Resolvers/PropertyTypeDocResolver.php +++ b/src/Resolvers/PropertyTypeDocResolver.php @@ -22,19 +22,19 @@ public function resolve(Type $type): array protected function resolveArrayType(Array_ $type): array { - $valueType = $type->getValueType(); + $valueTypes = $type->getValueType(); - if ($valueType instanceof Object_) { - $className = ltrim($valueType->getFqsen()?->__toString(), '\\'); - } elseif ($valueType instanceof AggregatedType) { + if ($valueTypes instanceof Object_) { + $className = ltrim($valueTypes->getFqsen()?->__toString(), '\\'); + } elseif ($valueTypes instanceof AggregatedType) { $className = []; - foreach ($valueType as $type) { - $className[] = ltrim($type instanceof Object_ - ? $type->getFqsen()?->__toString() - : (string)$valueType, '\\'); + foreach ($valueTypes as $valueType) { + $className[] = ltrim($valueType instanceof Object_ + ? $valueType->getFqsen()?->__toString() + : (string)$valueTypes, '\\'); } } else { - $className = ltrim((string)$valueType, '\\'); + $className = ltrim((string)$valueTypes, '\\'); } return [ diff --git a/src/Support/Collections/ConstructDataCollection.php b/src/Support/Collections/ConstructDataCollection.php index f428ba2..de1e4c8 100644 --- a/src/Support/Collections/ConstructDataCollection.php +++ b/src/Support/Collections/ConstructDataCollection.php @@ -4,8 +4,6 @@ class ConstructDataCollection { - // private array $tranFromResolvers = []; - public function __construct( public readonly string $name, public readonly bool $isPromoted, diff --git a/src/Support/Collections/DataCollection.php b/src/Support/Collections/DataCollection.php index 2f074e6..ff089b2 100644 --- a/src/Support/Collections/DataCollection.php +++ b/src/Support/Collections/DataCollection.php @@ -3,6 +3,7 @@ namespace Astral\Serialize\Support\Collections; use InvalidArgumentException; +use ReflectionAttribute; use ReflectionProperty; class DataCollection @@ -13,6 +14,7 @@ public function __construct( private readonly string $name, private readonly bool $isNullable, private readonly bool $isReadonly, + /** @var ReflectionAttribute[] */ private readonly array $attributes, private readonly mixed $defaultValue, private readonly ReflectionProperty $property, diff --git a/tests/Openapi/AnnotationOpenApiTest.php b/tests/Openapi/AnnotationOpenApiTest.php new file mode 100644 index 0000000..13993cd --- /dev/null +++ b/tests/Openapi/AnnotationOpenApiTest.php @@ -0,0 +1,75 @@ +buildByClass(AnnotationOpenApiController::class); + + $openApi = $api::$OpenAPI; + $paths = $openApi->paths; + $post = $paths['/test/description-action']['post']; + $requestBody = $post->requestBody; + $schema = $requestBody['content']['application/json']['schema']; + + expect(array_keys($schema['properties']))->toMatchArray([ + 'test_enum', + 'test_object', + 'test_example', + ]); + + $enumProp = $schema['properties']['test_enum']; + expect($enumProp['type'])->toBe('string') + ->and($enumProp['description'])->toBe('description test enum optional values:ENUM_1、ENUM_2') + ->and($enumProp['example'])->toBe(''); + + $objProp = $schema['properties']['test_object']; + expect($objProp['type'])->toBe('object') + ->and($objProp['description'])->toBe('this is object description') + ->and($objProp['example'])->toBe('1'); + + $exProp = $schema['properties']['test_example']; + expect($exProp['type'])->toBe('string') + ->and($exProp['description'])->toBe('') + ->and($exProp['example'])->toBe('abc') + ->and($schema['required'])->toMatchArray([ + 'test_enum', + 'test_object', + 'test_example', + ]); +}); diff --git a/tests/Openapi/EnumOpenApiTest.php b/tests/Openapi/EnumOpenApiTest.php new file mode 100644 index 0000000..706986a --- /dev/null +++ b/tests/Openapi/EnumOpenApiTest.php @@ -0,0 +1,99 @@ +buildByClass(OpenapiEnumController::class); + + $openApi = $api::$OpenAPI; + + + $paths = $openApi->paths; + $post = $paths['/test/enum-action']['post']; + $requestBody = $post->requestBody; + $schema = $requestBody['content']['application/json']['schema']; + + expect($schema['properties']) + ->toHaveKeys([ + 'test_enum', + 'test_string_enum', + 'test_string_2_enum', + 'test_one_of_enum', + ]) + ->and($schema['properties']['test_enum'])->toMatchArray([ + 'type' => 'string', + 'description' => 'optional values:ENUM_1、ENUM_2', + 'example' => '', + ]) + ->and($schema['properties']['test_string_enum'])->toMatchArray([ + 'type' => 'string', + 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', + 'example' => '', + ]) + ->and($schema['properties']['test_string_2_enum'])->toMatchArray([ + 'type' => 'string', + 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', + 'example' => '', + ]) + ->and($schema['properties']['test_one_of_enum'])->toMatchArray([ + 'type' => 'oneOf', + 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', + 'example' => '', + ]) + ->and($schema['properties']['test_one_of_enum']['oneOf'])->toBeArray()->toHaveCount(2) + ->and($schema['properties']['test_one_of_enum']['oneOf'][0])->toMatchArray(['type' => 'string']) + ->and($schema['properties']['test_one_of_enum']['oneOf'][1])->toMatchArray(['type' => 'integer']) + ->and($schema['required'])->toBeArray()->toEqualCanonicalizing([ + 'test_enum', + 'test_string_enum', + 'test_string_2_enum', + 'test_one_of_enum', + ]); + +}); \ No newline at end of file diff --git a/tests/Openapi/OpenApiTest.php b/tests/Openapi/OpenApiTest.php new file mode 100644 index 0000000..15c6db9 --- /dev/null +++ b/tests/Openapi/OpenApiTest.php @@ -0,0 +1,102 @@ +buildByClass(TestOpenApiController::class); + + $openApi = $api::$OpenAPI; + + // 顶层结构断言 + expect($openApi->openapi)->toBe('3.1.1') + ->and($openApi->info->version)->toBe('1.0.0') + ->and($openApi->tags[0]->name)->toBe('接口测试'); + + // 路径是否存在 + $paths = $openApi->paths; + expect($paths)->toHaveKey('/test/one-action'); + + // 方法与标签断言 + $post = $paths['/test/one-action']['post']; + expect($post->summary)->toBe('测试方法一') + ->and($post->tags)->toContain('接口测试'); + + // 请求体断言 + $requestBody = $post->requestBody; + expect($requestBody['required'])->toBeTrue(); + $schema = $requestBody['content']['application/json']['schema']; + + // 请求字段存在 + expect($schema['properties'])->toHaveKeys(['name', 'id', 'any_array']); + + // id 字段是 oneOf 并包含 string, integer, number + $idOneOf = $schema['properties']['id']['oneOf']; + $types = array_map(static fn($item) => $item['type'], $idOneOf); + expect($types)->toMatchArray(['string', 'integer', 'number']); + + // any_array 是 oneOf 并包含至少一个 array 类型 + $anyArray = $schema['properties']['any_array']; + expect($anyArray['type'])->toBe('oneOf') + ->and($anyArray['oneOf'])->toBeArray() + ->and($anyArray['oneOf'][0]['type'])->toBe('array'); + + // 响应体 200 是否定义成功 + $response200 = $post->responses[200]; + expect($response200['description'])->toBe('成功'); + + $schema = $response200['content']['application/json']['schema']; + expect($schema['properties'])->toHaveKeys(['name', 'id']) + ->and($schema['required'])->toHaveCount(1) + ->and($schema['required'][0])->toBeString('id'); + +}); \ No newline at end of file diff --git a/tests/Openapi/RouteOrewriteOpenApiTest.php b/tests/Openapi/RouteOrewriteOpenApiTest.php new file mode 100644 index 0000000..cffae5a --- /dev/null +++ b/tests/Openapi/RouteOrewriteOpenApiTest.php @@ -0,0 +1,46 @@ +buildByClass(TestCustomerRouteController::class); + + $openApi = $api::$OpenAPI; + + // 路径是否存在 + $paths = $openApi->paths; + expect($paths)->toHaveKey('/test/customer-route'); + +}); \ No newline at end of file