From 71c4a93a4d09ec7155974126437b2db1a54856e5 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 8 May 2025 09:27:08 +0800 Subject: [PATCH 01/43] init --- composer.lock | 6812 +++++++++++++++++ src/OpenApi/Annotations/Headers.php | 24 + src/OpenApi/Annotations/OpenApi.php | 16 + src/OpenApi/Annotations/RequestBody.php | 20 + src/OpenApi/Annotations/Response.php | 19 + src/OpenApi/Annotations/Route.php | 24 + src/OpenApi/Annotations/Summary.php | 21 + src/OpenApi/Annotations/Tag.php | 26 + src/OpenApi/Collections/OpenApiCollection.php | 28 + .../Collections/ParameterCollection.php | 28 + src/OpenApi/Enum/ContentTypeEnum.php | 10 + src/OpenApi/Enum/MethodEnum.php | 9 + src/OpenApi/Enum/ParameterTypeEnum.php | 10 + src/OpenApi/Formatter/FormatterInterface.php | 12 + src/OpenApi/Formatter/Json.php | 18 + src/OpenApi/Formatter/Yam.php | 0 src/OpenApi/Handler/HandleInterface.php | 12 + src/OpenApi/Handler/Handler.php | 178 + src/OpenApi/Handler/ParserPartaker.php | 191 + src/OpenApi/Handler/SerializeHandler.php | 152 + src/OpenApi/Storage/OpenAPI/ApiInfo.php | 20 + src/OpenApi/Storage/OpenAPI/Contact.php | 20 + .../Storage/OpenAPI/ContentProperties.php | 27 + src/OpenApi/Storage/OpenAPI/Method/GET.php | 13 + src/OpenApi/Storage/OpenAPI/Method/Method.php | 71 + .../OpenAPI/Method/MethodInterface.php | 10 + src/OpenApi/Storage/OpenAPI/Method/POST.php | 13 + src/OpenApi/Storage/OpenAPI/OpenAPI.php | 67 + .../Storage/OpenAPI/ParameterStorage.php | 73 + .../Storage/OpenAPI/RequestBodyStorage.php | 27 + .../Storage/OpenAPI/ResponseStorage.php | 29 + src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 233 + .../Storage/OpenAPI/ServersStorage.php | 19 + src/OpenApi/Storage/OpenAPI/TagStorage.php | 16 + src/OpenApi/Storage/StorageInterface.php | 9 + src/OpenApi/Storage/TreeNode.php | 47 + src/Resolvers/PropertyTypeDocResolver.php | 18 +- .../Collections/ConstructDataCollection.php | 2 - 38 files changed, 8313 insertions(+), 11 deletions(-) create mode 100644 composer.lock create mode 100755 src/OpenApi/Annotations/Headers.php create mode 100755 src/OpenApi/Annotations/OpenApi.php create mode 100755 src/OpenApi/Annotations/RequestBody.php create mode 100644 src/OpenApi/Annotations/Response.php create mode 100755 src/OpenApi/Annotations/Route.php create mode 100755 src/OpenApi/Annotations/Summary.php create mode 100755 src/OpenApi/Annotations/Tag.php create mode 100644 src/OpenApi/Collections/OpenApiCollection.php create mode 100755 src/OpenApi/Collections/ParameterCollection.php create mode 100755 src/OpenApi/Enum/ContentTypeEnum.php create mode 100755 src/OpenApi/Enum/MethodEnum.php create mode 100755 src/OpenApi/Enum/ParameterTypeEnum.php create mode 100755 src/OpenApi/Formatter/FormatterInterface.php create mode 100755 src/OpenApi/Formatter/Json.php create mode 100755 src/OpenApi/Formatter/Yam.php create mode 100755 src/OpenApi/Handler/HandleInterface.php create mode 100755 src/OpenApi/Handler/Handler.php create mode 100755 src/OpenApi/Handler/ParserPartaker.php create mode 100755 src/OpenApi/Handler/SerializeHandler.php create mode 100755 src/OpenApi/Storage/OpenAPI/ApiInfo.php create mode 100755 src/OpenApi/Storage/OpenAPI/Contact.php create mode 100755 src/OpenApi/Storage/OpenAPI/ContentProperties.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/GET.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/Method.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/POST.php create mode 100755 src/OpenApi/Storage/OpenAPI/OpenAPI.php create mode 100755 src/OpenApi/Storage/OpenAPI/ParameterStorage.php create mode 100755 src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php create mode 100755 src/OpenApi/Storage/OpenAPI/ResponseStorage.php create mode 100755 src/OpenApi/Storage/OpenAPI/SchemaStorage.php create mode 100755 src/OpenApi/Storage/OpenAPI/ServersStorage.php create mode 100755 src/OpenApi/Storage/OpenAPI/TagStorage.php create mode 100755 src/OpenApi/Storage/StorageInterface.php create mode 100755 src/OpenApi/Storage/TreeNode.php diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..1825245 --- /dev/null +++ b/composer.lock @@ -0,0 +1,6812 @@ +{ + "_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": "e2fb9f91440aed5f8990e559a3d1b4be", + "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.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "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.4" + }, + "time": "2024-12-07T21:18:45+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.4.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "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.4.0" + }, + "time": "2024-12-30T11:07:19+00:00" + }, + { + "name": "phpdocumentor/reflection", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/Reflection.git", + "reference": "bb4dea805a645553d6d989b23dad9f8041f39502" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/bb4dea805a645553d6d989b23dad9f8041f39502", + "reference": "bb4dea805a645553d6d989b23dad9f8041f39502", + "shasum": "" + }, + "require": { + "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": "^12.0", + "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": "^5.24", + "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": { + "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.1.0" + }, + "time": "2024-11-22T15:11:54+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.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "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.1" + }, + "time": "2024-12-07T09:39:29+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/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-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.5.1" + }, + "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:20:29+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "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.31.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-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "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.31.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/translation", + "version": "v6.4.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "3b9bf9f33997c064885a7bfc126c14b9daa0e00e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/3b9bf9f33997c064885a7bfc126c14b9daa0e00e", + "reference": "3b9bf9f33997c064885a7bfc126c14b9daa0e00e", + "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.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-13T10:18:43+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-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.5.1" + }, + "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:20:29+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.4.8", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "cf16fcbb9b8107a7df6b97e497fc91e819774d8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/cf16fcbb9b8107a7df6b97e497fc91e819774d8b", + "reference": "cf16fcbb9b8107a7df6b97e497fc91e819774d8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.2.0", + "jean85/pretty-package-versions": "^2.0.6", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-timer": "^6.0.0", + "phpunit/phpunit": "^10.5.36", + "sebastian/environment": "^6.1.0", + "symfony/console": "^6.4.7 || ^7.1.5", + "symfony/process": "^6.4.7 || ^7.1.5" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "squizlabs/php_codesniffer": "^3.10.3", + "symfony/filesystem": "^6.4.3 || ^7.1.5" + }, + "bin": [ + "bin/paratest", + "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.4.8" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2024-10-15T12:45:19+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.17.0", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", + "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "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.17.0" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-01-25T12:00:00+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.70.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "1ca468270efbb75ce0c7566a79cca8ea2888584d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1ca468270efbb75ce0c7566a79cca8ea2888584d", + "reference": "1ca468270efbb75ce0c7566a79cca8ea2888584d", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "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.5", + "infection/infection": "^0.29.10", + "justinrainbow/json-schema": "^5.3 || ^6.0", + "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.7", + "symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.0", + "symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.0" + }, + "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.70.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-03-03T21:07:23+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.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.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "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": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "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.0" + }, + "time": "2024-11-18T16:19:46+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.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "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.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-02-12T12:17:51+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.5.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", + "php": "^8.2.0", + "symfony/console": "^7.1.5" + }, + "conflict": { + "laravel/framework": "<11.0.0 || >=12.0.0", + "phpunit/phpunit": "<10.5.1 || >=12.0.0" + }, + "require-dev": { + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "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": "2024-10-15T16:06:32+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.1.8" + }, + "require-dev": { + "illuminate/console": "^11.33.2", + "laravel/pint": "^1.18.2", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^7.1.8", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "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/v2.3.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:39:51+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.0", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "4248817222514421cba466bfa7adc7d8932345d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4", + "reference": "4248817222514421cba466bfa7adc7d8932345d4", + "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.0" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2025-01-26T19:54:45+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.6", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "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-02-19T15:46:42+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/console", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^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/v7.2.1" + }, + "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-11T03:49:26+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.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": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^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/v7.2.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/event-dispatcher-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "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.5-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.5.1" + }, + "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:20:29+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^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/v7.2.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-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^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/v7.2.2" + }, + "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-30T19:00:17+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "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/v7.2.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-11-20T11:17:29+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.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.31.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.31.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.31.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.31.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.31.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-php81", + "version": "v1.31.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.31.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": "v7.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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/v7.2.4" + }, + "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-05T08:33:46+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "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.5-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.5.1" + }, + "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:20:29+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "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/v7.2.4" + }, + "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-24T10:49:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "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/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^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/v7.2.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-11-13T13:31:26+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.4", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "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", + "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.4" + }, + "time": "2024-01-05T14:10:56+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": {}, + "plugin-api-version": "2.6.0" +} 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..20f77e2 --- /dev/null +++ b/src/OpenApi/Annotations/OpenApi.php @@ -0,0 +1,16 @@ +method->name; + } +} diff --git a/src/OpenApi/Annotations/Summary.php b/src/OpenApi/Annotations/Summary.php new file mode 100755 index 0000000..ca893ce --- /dev/null +++ b/src/OpenApi/Annotations/Summary.php @@ -0,0 +1,21 @@ +value, $this->description); + } +} diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php new file mode 100644 index 0000000..82a119d --- /dev/null +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -0,0 +1,28 @@ + $parameters, */ + public array $parameters = [], + /** @var array $requestBody, */ + public array $requestBody = [], + /** @var array $responses, */ + public array $response = [], + + ){ + } + +} \ 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..3625566 --- /dev/null +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -0,0 +1,28 @@ + $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 @@ +headerParameterStorages->addHeaderProperties($name, $description, $example); + return $this; + } + + /** + * Undocumented function + */ + public function getOpenAPI(): OpenAPI + { + return self::$OpenAPI; + } + + /** + * 增加类前缀标识 + * + * @return $this + */ + public function withControllerPrefix(string $value): self + { + $this->controllerPrefix = $value; + return $this; + } + + /** + * 增加类后缀标识 + * + * @return $this + */ + public function withControllerSuffix(string $value): self + { + $this->controllerSuffix = $value; + return $this; + } + + public function handleByAutoLoad(): void + { + $classMap = require base_path('vendor/composer/autoload_classmap.php'); + $appClassMap = array_keys(array_filter($classMap, function ($key) { + return (str_starts_with($key, 'App\\') && str_ends_with($key, 'Controller')) + || (str_starts_with($key, 'April\\') && str_ends_with($key, 'Controller')); + }, ARRAY_FILTER_USE_KEY)); + + foreach ($appClassMap as $className) { + $this->createOpenAPIByClass($className); + } + } + + /** + * 解析Controller文件 + * + * @param array $folders 文件路径 => 命名空间 + * @return $this + */ + public function handleByFolders(array $folders): self + { + + foreach ($folders as $folder => $namespace) { + + if (! is_dir($folder)) { + continue; + } + + foreach (scandir($folder) as $file) { + + $path = $folder . '/' . $file; + if ($file == '.' || $file == '..' || strpos($file, '.') === 0) { + continue; + } + + if (is_dir($path)) { + $this->handleByFolders([$path => $namespace . '\\' . $file]); + + continue; + } + + if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') { + continue; + } + + $fileName = $this->controllerPrefix . trim(substr($file, 0, strpos($file, '.'))) . $this->controllerSuffix; + $className = $namespace ? $namespace . '\\' . $fileName : $fileName; + + if (! class_exists($className)) { + include_once $folder . '/' . $file; + ob_clean(); // 清除一些引入进来的莫名其妙输出文件 + if (! class_exists($className)) { + continue; + } + } + + $this->createOpenAPIByClass($className); + } + } + + return $this; + } + + /** + * 根据类信息构建Schema + * @throws Exception + */ + public function buildSchemaByClass(string $className, ?string $group = null): SchemaStorage + { + $schema = new SchemaStorage(); + + if (! $className) { + return $schema; + } + + $ParserPartaker = new ParserPartaker(); + $ParserPartaker->addNode($className, $group, null); + + $tree = $ParserPartaker->getTree(); + + $schema->createTree($tree->getChildren()); + + return $schema; + } + + public function output(string $path): bool + { + return true; + } + + /** + * @throws JsonException + */ + public function toString(): string + { + return json_encode(self::$OpenAPI, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); + } +} diff --git a/src/OpenApi/Handler/ParserPartaker.php b/src/OpenApi/Handler/ParserPartaker.php new file mode 100755 index 0000000..3efdd85 --- /dev/null +++ b/src/OpenApi/Handler/ParserPartaker.php @@ -0,0 +1,191 @@ +tree = new TreeNode(); + } + + public function addNode(string $className, ?string $group = null, ?TreeNode $childTree = null) + { + + $classRefection = new ReflectionClass($className); + + foreach ($classRefection->getProperties() as $property) { + + if ($property->isProtected()) { + continue; + } + + if ($group && class_exists(Group::class)) { + $attributes = $property->getAttributes(Group::class); + if (! $attributes) { + continue; + } + + $groups = $attributes[0]->newInstance(); + if (! in_array($group, $groups->names)) { + continue; + } + } + + $tree = new TreeNode($property); + if ($childTree) { + $childTree->addChildren($tree); + } else { + $this->tree->addChildren($tree); + } + + // // debug + // $docComment = <<getDocComment(); + $varMatch = getVarByDocComment($docComment); + if (! $varMatch) { + continue; + } + + /** + * var array<*,{string-class}> + * var {string-class}[] + * var {string-class} + */ + if ( + ! preg_match('#array<\S+,(\S+)>#', $varMatch[1], $arrayMatch) + && ! preg_match('#(\S+)\[\]#', $varMatch[1], $arrayMatch) + && ! preg_match('#(\S+)#', $varMatch[1], $arrayMatch) + ) { + continue; + } + + $listClass = trim($arrayMatch[1]); + if (in_array(strtolower($listClass), $this->ignoreConst)) { + continue; + } + + try { + $listClass = $this->getFullClassName($property, $listClass); + } catch (\Throwable $th) { + continue; + } + + // 添加子级类信息 + if ($listClass != $className) { + $this->addNode($listClass, $group, $tree); + } + } + } + + public function getFullClassName(ReflectionProperty $property, string $className): string + { + // 当前类命名空间 + $selfNamespaceName = $property->getDeclaringClass()->getNamespaceName(); + // 直接匹配类 + if (class_exists($className)) { + return $className; + } + // 判断是否是同一命名空间下的类 + elseif (class_exists($selfNamespaceName . '\\' . $className)) { + return $selfNamespaceName . '\\' . $className; + } + // 判断是否是引用类 + else { + // 获取引入文件 + $importClass = $this->parseUseStatements($property->getDeclaringClass()); + if (isset($importClass[$className])) { + return $importClass[$className]; + } + } + + throw new Exception('not find class ' . $className); + } + + /** + * 获取引入文件 + */ + private function parseUseStatements(ReflectionClass $reflectionClass): array + { + + $content = file_get_contents($reflectionClass->getFileName()); + preg_match_all('/^\s*use[\s+](.*);$/m', $content, $matches); + $classNames = []; + foreach ($matches[1] as $fullClassName) { + $parts = explode('\\', $fullClassName); + $classNames[end($parts)] = $fullClassName; + } + + return $classNames; + } + + /** + * Undocumented function + * + * @return TreeNode + */ + public function getTree() + { + return $this->tree; + } +} diff --git a/src/OpenApi/Handler/SerializeHandler.php b/src/OpenApi/Handler/SerializeHandler.php new file mode 100755 index 0000000..396fbba --- /dev/null +++ b/src/OpenApi/Handler/SerializeHandler.php @@ -0,0 +1,152 @@ +getAttributes(Tag::class); + /** @var Tag $tagDoc */ + $tagDoc = isset($tagDoc[0]) ? $tagDoc[0]->newInstance() : null; + if ($tagDoc) { + self::$OpenAPI->addTag($tagDoc->buildTagStorage()); + } + + foreach ($classRefection->getMethods() as $item) { + + $methodAttributes = $item->getAttributes(); + + if (! $methodAttributes) { + continue; + } + + $routeDoc = $summaryDoc = $requestBodyDoc = $responseDoc = $headersDoc = null; + $instances = [ + Route::class => &$routeDoc, + Summary::class => &$summaryDoc, + RequestBody::class => &$requestBodyDoc, + Response::class => &$responseDoc, + Headers::class => &$headersDoc, + ]; + foreach ($methodAttributes as $methodAttribute) { + $name = $methodAttribute->getName(); + if (isset($instances[$name])) { + $instances[$name] = $methodAttribute->newInstance(); + } + } + + if (! $routeDoc || ! $summaryDoc) { + continue; + } + + $returnClass = $classRefection->getMethod($item->name)->getReturnType(); + $responseClass = match(true){ + $responseDoc => $responseDoc->className, + $returnClass && $returnClass instanceof Serialize::class =>$returnClass, + default => null, + }; + + new OpenApiCollection( + controllerClass: $className, + methodName: $item->getName(), + summary:$summaryDoc, + route: $routeDoc, + headers: $headersDoc, + attributes: $methodAttributes, + requestBody: $this->buildRequestBodyParameterCollections($requestBodyDoc->className), + response: $requestBodyDoc ? $this->buildRequestBodyParameterCollections($responseClass) : [], + ); + } + } + + /** + * @param string $className + * @return array + */ + public function buildRequestBodyParameterCollections(string $className): array + { + $serializeContext = ContextFactory::build($className); + $serializeContext->from(); + $properties = $serializeContext->getChooseSerializeContext()->getProperties(); + + $vols = []; + foreach ($properties as $property){ + $vol = new ParameterCollection( + name:$property->getInputName(), + descriptions: '', + type: $property->getType()?->kind->name ?: 'STRING', + required: !$property->getDataCollection()->isNullable(), + ); + + if($property->getChildren()){ + $vol->children = $this->buildRequestBodyParameterCollections($property->getChildren()->get) + } + } + + return $vols; + } + + /** + * @param string $className + * @return array + */ + public function buildResponseParameterCollections(string $className): array + { + $serializeContext = ContextFactory::build($className); + $serializeContext->from(); + $properties = $serializeContext->getChooseSerializeContext()->getProperties(); + + $vols = []; + foreach ($properties as $property){ + + if(!$property->getInputName()){ + continue; + } + + $vols[] = new ParameterCollection( + name:$property->getInputName(), + descriptions: '', + type: $property->getType()?->kind->name ?: 'STRING', + required: !$property->getDataCollection()->isNullable(), + ); + } + + return $vols; + } +} 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 @@ + $parameters */ + public array|stdClass $parameters = new stdClass(), + public RequestBodyStorage|stdClass $requestBody = new stdClass(), + /** @var array 返回信息 */ + 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; + + return $this; + } + + /** + * @param int $code + * @param ResponseStorage $response + * @return $this + */ + public function addResponse(int $code, ResponseStorage $response): static + { + + $this->responses = new stdClass(); // 去除初始化 + $this->responses[$code] = $response; + + 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..d04a7b5 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php @@ -0,0 +1,10 @@ + 请求api的标识符 */ + public array $tags; + + /** @var array 具体api配置 */ + 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(string $url, MethodInterface $method): self + { + $this->paths[$url][$method->getName()] = $method; + 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..689a7a0 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php @@ -0,0 +1,27 @@ + 参数类型 */ + public array $content = [] + ) { + $this->content[$this->contentType->value]['schema'] = []; + } + + public function withParameter(SchemaStorage $schema): void + { + $this->content[$this->contentType->value]['schema'] = $schema->getData(); + } +} diff --git a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php new file mode 100755 index 0000000..565d23e --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php @@ -0,0 +1,29 @@ +content[$this->contentType]['schema'] = []; + } + + public function withParameter(SchemaStorage $schema): void + { + $this->content[$this->contentType]['schema'] = $schema->getData(); + } +} diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php new file mode 100755 index 0000000..0dd80b5 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -0,0 +1,233 @@ + 'object', + 'properties' => [], + // 'required' => [], + ]; + + public function getData(): SchemaStorage|array + { + return $this->data; + } + + /** + * Undocumented function + * + * @param array $tree + * @param mixed|null $node + * @return void + * @throws Exception + */ + public function createTree(array $tree, mixed &$node = null): void + { + if ($node === null) { + $node = &$this->data; + } + + foreach ($tree as $item) { + + $parameter = $this->parserParameter($item->getValue()); + if ($parameter->ignore) { + continue; + } + + $node['properties'][$parameter->name] = [ + 'type' => $parameter->type, + 'description' => $parameter->value, + 'example' => $parameter->example, + ]; + + if ($parameter->required) { + $node['required'][] = $parameter->name; + } + + if ($item->getChildren()) { + // list对象 + if ($parameter->type === 'array') { + $node['properties'][$parameter->name]['items'] = [ + 'type' => 'object', + 'properties' => [], + // 'required' => [], + ]; + $tree = &$node['properties'][$parameter->name]['items']; + } + // 单个对象 + elseif ($parameter->type === 'object') { + $node['properties'][$parameter->name] = [ + 'type' => 'object', + 'properties' => [], + // 'required' => [], + ]; + $tree = &$node['properties'][$parameter->name]; + } + + $this->createTree($item->getChildren(), $tree); + } + } + } + + public function addProperties(string $name, string $type, string $description, string $example, bool $required = false): void + { + $this->data['properties'][$name] = [ + 'type' => $type, + 'description' => $description, + 'example' => $example, + ]; + + if ($required) { + $this->data['required'][] = $name; + } + } + + private function parserParameter(ReflectionProperty $prop): Parameter + { + + try { + + $parameterAttributes = $prop->getAttributes(Parameter::class); + + /** @var Parameter $parameter */ + $parameter = isset($parameterAttributes[0]) ? $parameterAttributes[0]->newInstance() : new Parameter(); + + // 转换输入名称 + if (class_exists(GetAlias::class) && $prop->getAttributes(GetAlias::class)) { + $getAsName = $prop->getAttributes(GetAlias::class)[0]->newInstance(); + $parameter->name = $getAsName->name; + } elseif (class_exists(SetAlisa::class) && $prop->getAttributes(SetAlisa::class)) { + $setAsName = $prop->getAttributes(SetAlisa::class)[0]->newInstance(); + $parameter->name = $setAsName->name; + } else { + $parameter->name = $prop->name; + } + + $docComment = $prop->getDocComment(); + + if (! $docComment) { + return $parameter; + } + + // 获取 类型/说明 @var {type} {name} or @var {type} + $varMatch = getVarByDocComment($docComment); + + // 获取 例值 @example {value} + preg_match('/@example\s*(\S+)/', $docComment, $exampleMatch); + + // 获取 是否必填 @requires true / @required true + preg_match('/@(?:requires|required)\s*(\S+)/i', $docComment, $requiredMatch); + + if ($requiredMatch) { + $parameter->required = $requiredMatch[1] == 'true' ? true : false; + } elseif (class_exists(NotBlank::class) && ($prop->getAttributes(NotBlank::class) ?? [])) { + $parameter->required = true; + } + + if ($varMatch) { + $parameter->type = $this->getType($varMatch[1]); + $parameter->value = $varMatch[2] ?? ''; + + $className = str_replace(['[', ']'], '', $varMatch[1]); + if (($parameter->type == 'object' || $parameter->type == 'array') + && ! in_array($className, ['int', 'float', 'bool', 'array', 'string', 'boolean', 'integer']) + ) { + try { + $fullClassName = (new ParserPartaker())->getFullClassName($prop, $className); + } catch (\Throwable $th) { + throw new Exception( + sprintf('%s not find from %s', $className, $prop->getDeclaringClass()->getName()), + $th->getCode(), + $th + ); + } + $reflectionClass = new ReflectionClass($fullClassName); + if ($reflectionClass->isEnum()) { + // object转string + $parameter->type = $parameter->type == 'object' ? 'string' : $parameter->type; + foreach ($reflectionClass->getConstants() as $enum) { + $parameter->value .= isset($enum->name) ? ' ' . $enum->name . ',' : ''; + } + $parameter->value = rtrim($parameter->value, ','); + } + } + } + // 获取忽略 + if (str_contains($docComment, '@ignore')) { + $parameter->ignore = true; + } + + if ($exampleMatch) { + $parameter->example = $exampleMatch[1]; + if (strpos($exampleMatch['1'], '[') === 0 || strpos($exampleMatch['1'], '{') === 0) { + $example = json_decode($exampleMatch[1], true); + if ($exampleMatch[1] && json_last_error()) { + throw new Exception('example JSON 错误 from ' . $prop->getName() . ' in ' . $prop->getDeclaringClass()->getName()); + } + $parameter->example = $example; + } + } + + return $parameter; + } catch (\Throwable $th) { + + echo '参数解析失败了' . PHP_EOL; + throw new Exception($th->getMessage(), $th->getCode(), $th); + } + } + + private function getType(string $type): string + { + + /** + * 不包含一下信息的 + * var array<*,string-class> + * var string-class[] + * var string-class + */ + if ( + ! preg_match('/array<\S+,(\S+)>/', $type, $arrayMatch) + && ! preg_match('/(\S+)\[\]/', $type, $arrayMatch) + && ! preg_match('/(\S+)/', $type, $arrayMatch) + ) { + return 'string'; + } + + // 取出对应值 + $stringClass = strtolower(trim($arrayMatch[1])); + + if (stripos($type, '[]') !== false) { + return 'array'; + } + + if (in_array($stringClass, ['int', 'float', 'bool', 'array', 'string', 'boolean', 'integer'])) { + + $stringClass = $stringClass == 'array' ? 'string' : $stringClass; + $stringClass = $stringClass == 'int' ? 'integer' : $stringClass; + $stringClass = $stringClass == 'bool' ? 'boolean' : $stringClass; + + return $stringClass; + } + + if (stripos($type, '<') === false && stripos($type, '[') === false) { + return 'object'; + } + + return 'array'; + } +} diff --git a/src/OpenApi/Storage/OpenAPI/ServersStorage.php b/src/OpenApi/Storage/OpenAPI/ServersStorage.php new file mode 100755 index 0000000..db2f062 --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/ServersStorage.php @@ -0,0 +1,19 @@ +variables = $this->variables ?: new stdClass(); + } +} 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/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, From 578e3be407b14f4f57f6d7ef45fcb80db52bd580 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 6 Jun 2025 16:12:39 +0800 Subject: [PATCH 02/43] ref v2 --- src/Enums/TypeKindEnum.php | 15 +- src/OpenApi/Annotations/RequestBody.php | 2 +- src/OpenApi/Annotations/Response.php | 6 +- src/OpenApi/Annotations/Route.php | 5 - src/OpenApi/Annotations/Tag.php | 4 - src/OpenApi/Collections/OpenApiCollection.php | 160 ++++++++- .../Collections/ParameterCollection.php | 16 +- src/OpenApi/Enum/MethodEnum.php | 14 +- src/OpenApi/Handler/Handler.php | 41 +-- src/OpenApi/Handler/SerializeHandler.php | 152 --------- src/OpenApi/Handler/api.php | 314 ++++++++++++++++++ src/OpenApi/OpenApi.php | 89 +++++ src/OpenApi/Storage/OpenAPI/Method/Delete.php | 9 + src/OpenApi/Storage/OpenAPI/Method/GET.php | 13 - src/OpenApi/Storage/OpenAPI/Method/Get.php | 9 + src/OpenApi/Storage/OpenAPI/Method/Method.php | 20 +- .../OpenAPI/Method/MethodInterface.php | 1 - src/OpenApi/Storage/OpenAPI/Method/POST.php | 13 - src/OpenApi/Storage/OpenAPI/Method/Post.php | 9 + src/OpenApi/Storage/OpenAPI/Method/Put.php | 9 + src/OpenApi/Storage/OpenAPI/OpenAPI.php | 20 +- .../Storage/OpenAPI/RequestBodyStorage.php | 19 +- .../Storage/OpenAPI/ResponseStorage.php | 28 +- src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 185 ++--------- tests/Openapi/ArrayOpenApi.php | 66 ++++ 25 files changed, 782 insertions(+), 437 deletions(-) delete mode 100755 src/OpenApi/Handler/SerializeHandler.php create mode 100644 src/OpenApi/Handler/api.php create mode 100755 src/OpenApi/OpenApi.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/Delete.php delete mode 100755 src/OpenApi/Storage/OpenAPI/Method/GET.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/Get.php delete mode 100755 src/OpenApi/Storage/OpenAPI/Method/POST.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/Post.php create mode 100755 src/OpenApi/Storage/OpenAPI/Method/Put.php create mode 100644 tests/Openapi/ArrayOpenApi.php diff --git a/src/Enums/TypeKindEnum.php b/src/Enums/TypeKindEnum.php index b56752f..ef13933 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 { @@ -68,4 +69,16 @@ public static function getNameTo(string $type, ?string $className = null): self default => throw new RuntimeException("not found type $type"), }; } + + public function getOpenApiName(): string + { + return match ($this) { + self::INT => 'integer', + self::FLOAT => 'number', + self::BOOLEAN => 'boolean', + self::OBJECT, self::CLASS_OBJECT => 'object', + self::ARRAY, self::COLLECT_SINGLE_OBJECT , self::COLLECT_UNION_OBJECT => 'array', + default => 'string', + }; + } } diff --git a/src/OpenApi/Annotations/RequestBody.php b/src/OpenApi/Annotations/RequestBody.php index 5b5dd4d..71e5a96 100755 --- a/src/OpenApi/Annotations/RequestBody.php +++ b/src/OpenApi/Annotations/RequestBody.php @@ -14,7 +14,7 @@ public function __construct( /** @var class-string $className */ public string $className = '', public ContentTypeEnum $contentType = ContentTypeEnum::JSON, - public ?string $group = null + public array|null $group = null ){ } } diff --git a/src/OpenApi/Annotations/Response.php b/src/OpenApi/Annotations/Response.php index 03830b2..6f9376a 100644 --- a/src/OpenApi/Annotations/Response.php +++ b/src/OpenApi/Annotations/Response.php @@ -11,9 +11,9 @@ class Response { public function __construct( /** @var class-string $className */ - public string $className, - public ?string $group = null, - public ?int $code = 200 + public string $className, + public ?int $code = 200, + public ?array $groups = null, ){ } } diff --git a/src/OpenApi/Annotations/Route.php b/src/OpenApi/Annotations/Route.php index 80cc709..3d5ec9c 100755 --- a/src/OpenApi/Annotations/Route.php +++ b/src/OpenApi/Annotations/Route.php @@ -16,9 +16,4 @@ public function __construct( public array $attributes = [], ) { } - - public function getMethod(): string - { - return 'April\\ApiDoc\\Storage\\OpenAPI\\Method\\' . $this->method->name; - } } diff --git a/src/OpenApi/Annotations/Tag.php b/src/OpenApi/Annotations/Tag.php index 7ff4165..79769b1 100755 --- a/src/OpenApi/Annotations/Tag.php +++ b/src/OpenApi/Annotations/Tag.php @@ -19,8 +19,4 @@ public function __construct( ) { } - public function buildTagStorage(): TagStorage - { - return new TagStorage($this->value, $this->description); - } } diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 82a119d..c09b698 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -2,27 +2,171 @@ namespace Astral\Serialize\OpenApi\Collections; +use Astral\Serialize\Enums\TypeKindEnum; use Astral\Serialize\OpenApi\Annotations\Headers; +use Astral\Serialize\OpenApi\Annotations\RequestBody; +use Astral\Serialize\OpenApi\Annotations\Response; use Astral\Serialize\OpenApi\Annotations\Route; use Astral\Serialize\OpenApi\Annotations\Summary; +use Astral\Serialize\OpenApi\Annotations\Tag; +use Astral\Serialize\OpenApi\Enum\ContentTypeEnum; +use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Method; +use Astral\Serialize\OpenApi\Storage\OpenAPI\RequestBodyStorage; +use Astral\Serialize\OpenApi\Storage\OpenAPI\ResponseStorage; +use Astral\Serialize\OpenApi\Storage\OpenAPI\SchemaStorage; +use Astral\Serialize\Serialize; +use Astral\Serialize\Support\Factories\ContextFactory; +use Psr\SimpleCache\InvalidArgumentException; +use ReflectionMethod; class OpenApiCollection { public function __construct( public string $controllerClass, public string $methodName, + public reflectionMethod $reflectionMethod, + public Tag $tag, public Summary $summary, public Route $route, - public Headers $headers, + public Headers|null $headers, public array $attributes, - /** @var array $parameters, */ - public array $parameters = [], - /** @var array $requestBody, */ - public array $requestBody = [], - /** @var array $responses, */ - public array $response = [], - + public RequestBody|null $requestBody, + public Response|null $response, ){ } + /** + * @throws InvalidArgumentException + */ + public function build() : Method + { + $methodClass = $this->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->buildRequestBodyParameterCollections($this->requestBody->className,$this->requestBody->group),$n); + $openAPIRequestBody->withParameter($schemaStorage); + return $openAPIRequestBody; + } + + /** + * @throws InvalidArgumentException + */ + public function buildRequestBodyByParameters(): RequestBodyStorage + { + $openAPIRequestBody = new RequestBodyStorage(ContentTypeEnum::JSON); + $methodParam = $this->reflectionMethod->getParameters()[0] ?? null; + $requestBodyClass = $methodParam ? $methodParam->getType()?->getName() : ''; + if (is_subclass_of($requestBodyClass, Serialize::class)) { + $schemaStorage = (new SchemaStorage())->build($this->buildRequestBodyParameterCollections($requestBodyClass),$node); + $openAPIRequestBody->withParameter($schemaStorage); + } + + return $openAPIRequestBody; + } + + /** + * @throws InvalidArgumentException + */ + public function buildResponse(): ResponseStorage + { + $responseStorage = new ResponseStorage(); + $schemaStorage = (new SchemaStorage())->build($this->buildResponseParameterCollections()); + $responseStorage->withParameter($schemaStorage); + return $responseStorage; + } + + /** + * @param string $className + * @param array $groups + * @return array + * @throws InvalidArgumentException + */ + public function buildRequestBodyParameterCollections(string $className, array $groups = ['default']): array + { + $serializeContext = ContextFactory::build($className); + $serializeContext->from(); + $properties = $serializeContext->getGroupCollection()->getProperties(); + + $vols = []; + foreach ($properties as $property){ + $vol = new ParameterCollection( + name: current($property->getInputNamesByGroups($groups,$className)), + descriptions: '', + type: current($property->getTypes())->kind ?: TypeKindEnum::STRING, + required: !$property->isNullable(), + ignore: false, + ); + + if($property->getChildren()){ + foreach ($property->getChildren() as $children){ + $vol->children[] = $this->buildRequestBodyParameterCollections($children->getClassName()); + } + } + + $vols[] = $vol; + } + + return $vols; + } + + /** + * @return array + * @throws InvalidArgumentException + */ + public function buildResponseParameterCollections(): array + { + $returnClass = $this->reflectionMethod->getReturnType(); + $responseClass = match(true){ + $this->response !== null => $this->response->className, + $returnClass && is_subclass_of($returnClass,Serialize::class) => $returnClass, + default => null, + }; + + if(!$responseClass){ + return []; + } + + $groups = $this->response ? $this->response->groups : ['default']; + $serializeContext = ContextFactory::build($responseClass); + $serializeContext->from(); + $properties = $serializeContext->getGroupCollection()->getProperties(); + + $vols = []; + foreach ($properties as $property){ + $vol = new ParameterCollection( + name:current($property->getOutNamesByGroups($groups,$responseClass)), + descriptions: '', + type: current($property->getTypes())->kind ?: TypeKindEnum::STRING, + required: !$property->isNullable(), + ignore: false, + ); + + if($property->getChildren()){ + foreach ($property->getChildren() as $children){ + $vol->children[] = $this->buildRequestBodyParameterCollections($children->getClassName()); + } + } + + $vols[] = $vol; + } + + return $vols; + } } \ No newline at end of file diff --git a/src/OpenApi/Collections/ParameterCollection.php b/src/OpenApi/Collections/ParameterCollection.php index 3625566..f161b6d 100755 --- a/src/OpenApi/Collections/ParameterCollection.php +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -4,25 +4,25 @@ namespace Astral\Serialize\OpenApi\Collections; +use Astral\Serialize\Enums\TypeKindEnum; use Attribute; class ParameterCollection { public function __construct( /** @var string 元素变量名 */ - public string $name, + public string $name, /** @var string descriptions */ - public string $descriptions = '', - /** @var string 元素类型 */ - public string $type = 'string', + public string $descriptions = '', + public TypeKindEnum $type = TypeKindEnum::STRING, /** @var mixed 示例值 */ - public mixed $example = '', + public mixed $example = '', /** @var bool 是否必填 */ - public bool $required = false, + public bool $required = false, /** @var bool 是否忽略显示 */ - public bool $ignore = false, + public bool $ignore = false, /** @var array $children */ - public array $children = [], + public array $children = [], ){ } } diff --git a/src/OpenApi/Enum/MethodEnum.php b/src/OpenApi/Enum/MethodEnum.php index 8dd5471..e5a2156 100755 --- a/src/OpenApi/Enum/MethodEnum.php +++ b/src/OpenApi/Enum/MethodEnum.php @@ -2,8 +2,16 @@ namespace Astral\Serialize\OpenApi\Enum; -enum MethodEnum +use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Delete; +use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Get; +use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Post; +use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Put; + +enum MethodEnum:string { - case POST; - case GET; + case POST = Post::class; + case GET = Get::class; + case PUT = Put::class; + case DELETE = Delete::class; + } diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index 2b6f380..f547914 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -5,6 +5,7 @@ namespace Astral\Serialize\OpenApi\Handler; use Astral\Serialize\OpenApi\Collections\OpenApiCollection; +use Astral\Serialize\OpenApi\Storage\OpenAPI\ApiInfo; use Astral\Serialize\OpenApi\Storage\OpenAPI\OpenAPI; use Astral\Serialize\OpenApi\Storage\OpenAPI\ParameterStorage; use Astral\Serialize\OpenApi\Storage\OpenAPI\SchemaStorage; @@ -22,7 +23,7 @@ abstract class Handler implements HandleInterface public function __construct( protected readonly ParameterStorage $headerParameterStorages = new ParameterStorage() ) { - self::$OpenAPI ?: self::$OpenAPI = new OpenAPI(); + self::$OpenAPI ??= (new OpenAPI())->withApiInfo(new ApiInfo('API 接口','')); } /** @@ -32,7 +33,7 @@ public function __construct( * @throws ReflectionException * @throws Exception */ - abstract public function createOpenAPIByClass(string $className): void; + abstract public function buildByClass(string $className): void; /** * 向全局头部参数存储中添加一个新的头部参数。 @@ -78,24 +79,28 @@ public function withControllerSuffix(string $value): self return $this; } + /** + * @throws ReflectionException + */ public function handleByAutoLoad(): void { - $classMap = require base_path('vendor/composer/autoload_classmap.php'); - $appClassMap = array_keys(array_filter($classMap, function ($key) { + $classMap = require dir('vendor/composer/autoload_classmap.php'); + $appClassMap = array_keys(array_filter($classMap, static function ($key) { return (str_starts_with($key, 'App\\') && str_ends_with($key, 'Controller')) || (str_starts_with($key, 'April\\') && str_ends_with($key, 'Controller')); }, ARRAY_FILTER_USE_KEY)); foreach ($appClassMap as $className) { - $this->createOpenAPIByClass($className); + $this->buildByClass($className); } } /** * 解析Controller文件 * - * @param array $folders 文件路径 => 命名空间 + * @param array $folders 文件路径 => 命名空间 * @return $this + * @throws ReflectionException */ public function handleByFolders(array $folders): self { @@ -109,7 +114,7 @@ public function handleByFolders(array $folders): self foreach (scandir($folder) as $file) { $path = $folder . '/' . $file; - if ($file == '.' || $file == '..' || strpos($file, '.') === 0) { + if ($file === '.' || $file === '..' || str_starts_with($file, '.')) { continue; } @@ -134,34 +139,14 @@ public function handleByFolders(array $folders): self } } - $this->createOpenAPIByClass($className); + $this->buildByClass($className); } } return $this; } - /** - * 根据类信息构建Schema - * @throws Exception - */ - public function buildSchemaByClass(string $className, ?string $group = null): SchemaStorage - { - $schema = new SchemaStorage(); - - if (! $className) { - return $schema; - } - - $ParserPartaker = new ParserPartaker(); - $ParserPartaker->addNode($className, $group, null); - $tree = $ParserPartaker->getTree(); - - $schema->createTree($tree->getChildren()); - - return $schema; - } public function output(string $path): bool { diff --git a/src/OpenApi/Handler/SerializeHandler.php b/src/OpenApi/Handler/SerializeHandler.php deleted file mode 100755 index 396fbba..0000000 --- a/src/OpenApi/Handler/SerializeHandler.php +++ /dev/null @@ -1,152 +0,0 @@ -getAttributes(Tag::class); - /** @var Tag $tagDoc */ - $tagDoc = isset($tagDoc[0]) ? $tagDoc[0]->newInstance() : null; - if ($tagDoc) { - self::$OpenAPI->addTag($tagDoc->buildTagStorage()); - } - - foreach ($classRefection->getMethods() as $item) { - - $methodAttributes = $item->getAttributes(); - - if (! $methodAttributes) { - continue; - } - - $routeDoc = $summaryDoc = $requestBodyDoc = $responseDoc = $headersDoc = null; - $instances = [ - Route::class => &$routeDoc, - Summary::class => &$summaryDoc, - RequestBody::class => &$requestBodyDoc, - Response::class => &$responseDoc, - Headers::class => &$headersDoc, - ]; - foreach ($methodAttributes as $methodAttribute) { - $name = $methodAttribute->getName(); - if (isset($instances[$name])) { - $instances[$name] = $methodAttribute->newInstance(); - } - } - - if (! $routeDoc || ! $summaryDoc) { - continue; - } - - $returnClass = $classRefection->getMethod($item->name)->getReturnType(); - $responseClass = match(true){ - $responseDoc => $responseDoc->className, - $returnClass && $returnClass instanceof Serialize::class =>$returnClass, - default => null, - }; - - new OpenApiCollection( - controllerClass: $className, - methodName: $item->getName(), - summary:$summaryDoc, - route: $routeDoc, - headers: $headersDoc, - attributes: $methodAttributes, - requestBody: $this->buildRequestBodyParameterCollections($requestBodyDoc->className), - response: $requestBodyDoc ? $this->buildRequestBodyParameterCollections($responseClass) : [], - ); - } - } - - /** - * @param string $className - * @return array - */ - public function buildRequestBodyParameterCollections(string $className): array - { - $serializeContext = ContextFactory::build($className); - $serializeContext->from(); - $properties = $serializeContext->getChooseSerializeContext()->getProperties(); - - $vols = []; - foreach ($properties as $property){ - $vol = new ParameterCollection( - name:$property->getInputName(), - descriptions: '', - type: $property->getType()?->kind->name ?: 'STRING', - required: !$property->getDataCollection()->isNullable(), - ); - - if($property->getChildren()){ - $vol->children = $this->buildRequestBodyParameterCollections($property->getChildren()->get) - } - } - - return $vols; - } - - /** - * @param string $className - * @return array - */ - public function buildResponseParameterCollections(string $className): array - { - $serializeContext = ContextFactory::build($className); - $serializeContext->from(); - $properties = $serializeContext->getChooseSerializeContext()->getProperties(); - - $vols = []; - foreach ($properties as $property){ - - if(!$property->getInputName()){ - continue; - } - - $vols[] = new ParameterCollection( - name:$property->getInputName(), - descriptions: '', - type: $property->getType()?->kind->name ?: 'STRING', - required: !$property->getDataCollection()->isNullable(), - ); - } - - return $vols; - } -} diff --git a/src/OpenApi/Handler/api.php b/src/OpenApi/Handler/api.php new file mode 100644 index 0000000..db0c773 --- /dev/null +++ b/src/OpenApi/Handler/api.php @@ -0,0 +1,314 @@ +headerParameterStorages) { + $this->headerParameterStorages = new ParameterStorage(); + } + + // 添加头部参数的属性 + $this->headerParameterStorages->addHeaderProperties($name, $description, $example); + + return $this; + } + + /** + * Undocumented function + */ + public function getOpenAPI(): OpenAPI + { + return self::$OpenAPI; + } + + /** + * 增加类前缀标识 + * + * @return $this + */ + public function withControllerPrefix(string $value): self + { + $this->controllerPrefix = $value; + + return $this; + } + + /** + * 增加类后缀标识 + * + * @return $this + */ + public function withControllerSuffix(string $value): self + { + $this->controllerSuffix = $value; + + return $this; + } + + /** + * 是否开启注解异常信息 + * + * @return $this + */ + public function enableException(bool $bool = true): self + { + $this->_isIgnoreException = $bool; + + return $this; + } + + /** + * 解析Controller文件 + * + * @param array $folders 文件路径 => 命名空间 + * @return $this + */ + public function handleByFolders(array $folders): self + { + + foreach ($folders as $folder => $namespace) { + + if (! is_dir($folder)) { + continue; + } + + foreach (scandir($folder) as $file) { + + $path = $folder.'/'.$file; + if ($file == '.' || $file == '..' || strpos($file, '.') === 0) { + continue; + } + + if (is_dir($path)) { + $this->handleByFolders([$path => $namespace.'\\'.$file]); + + continue; + } + + if (pathinfo($file, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $fileName = $this->controllerPrefix.trim(substr($file, 0, strpos($file, '.'))).$this->controllerSuffix; + $className = $namespace ? $namespace.'\\'.$fileName : $fileName; + + if (! class_exists($className)) { + include_once $folder.'/'.$file; + ob_clean(); // 清除一些引入进来的莫名其妙输出文件 + if (! class_exists($className)) { + continue; + } + } + + $this->createOpenAPIByClass($className); + } + } + + return $this; + } + + /** + * 构建OpenApi结构文档 + * + * @param class-string $className + */ + public function createOpenAPIByClass($className): void + { + // try { + + $classRefection = new ReflectionClass($className); + + $tagDoc = $classRefection->getAttributes(Tag::class); + /** @var Tag */ + $tagDoc = isset($tagDoc[0]) ? $tagDoc[0]->newInstance() : null; + if ($tagDoc) { + self::$OpenAPI->addTag($tagDoc->buildTagStorage()); + } + + foreach ($classRefection->getMethods() as $item) { + + /** @var ReflectionMethod */ + $reflectionMethod = $classRefection->getMethod($item->name); + $methodAttributes = $reflectionMethod->getAttributes(); + + if (! $methodAttributes) { + continue; + } + + $routeDoc = null; + $summaryDoc = null; + $requestBodyDoc = null; + $responseDoc = null; + foreach ($methodAttributes as $methodAttribute) { + switch ($methodAttribute) { + case $methodAttribute->getName() == Route::class: + /** @var Route */ + $routeDoc = $methodAttribute->newInstance(); + break; + case $methodAttribute->getName() == Summary::class: + /** @var Summary */ + $summaryDoc = $methodAttribute->newInstance(); + break; + case $methodAttribute->getName() == RequestBody::class: + /** @var RequestBody */ + $requestBodyDoc = $methodAttribute->newInstance(); + break; + case $methodAttribute->getName() == Response::class: + /** @var Response */ + $responseDoc = $methodAttribute->newInstance(); + break; + } + } + + if (! $routeDoc || ! $summaryDoc) { + continue; + } + + $methodClass = $routeDoc->getMethod(); + /** @var MethodInterface|Method| */ + $openAPIMethod = new $methodClass($summaryDoc->value, $summaryDoc->description ?: '', [$tagDoc->value ?: '']); + + // 统一header头 + if ($this->headerParameterStorages) { + $openAPIMethod->withParameters($this->headerParameterStorages->getData()); + } + + if ($requestBodyDoc) { + $openAPIRequestBody = new OpenAPIRequestBody($requestBodyDoc->contentType); + $requestBodySchema = $this->buildSchemaByClass($requestBodyDoc->className); + $openAPIRequestBody->withParameter($requestBodySchema); + $openAPIMethod->withRequestBody($openAPIRequestBody); + } else { + $methodParam = $reflectionMethod->getParameters(); + if (isset($methodParam[0]) && ($requestBodyClass = $methodParam[0]->gettype()->getName()) !== Request::class) { + $openAPIRequestBody = new OpenAPIRequestBody(ContentTypeEnum::JSON); + $requestBodySchema = $this->buildSchemaByClass($requestBodyClass); + $openAPIRequestBody->withParameter($requestBodySchema); + $openAPIMethod->withRequestBody($openAPIRequestBody); + } + } + + if ($responseDoc) { + if (! class_exists($responseDoc->className)) { + throw new Exception( + sprintf('Class "%s" does not exist in "%s" from action "%s"', + $responseDoc->className, + $reflectionMethod->getFileName(), + $reflectionMethod->getName()) + ); + } + + $openApiResponse = new OpenAPIResponse(); + $openApiResponse->description = '成功'; + $openApiResponse->withParameter($this->buildSchemaByClass($responseDoc->className)); + $openAPIMethod->addResponse($responseDoc->code, $openApiResponse); + + } else { + /** @var ReflectionType */ + $returnClass = $classRefection->getMethod($item->name)->getReturnType(); + if ($returnClass && class_exists($returnClass->getName())) { + $openApiResponse = new OpenAPIResponse(); + $openApiResponse->description = '成功'; + $openApiResponse->withParameter($this->buildSchemaByClass($returnClass->getName())); + $openAPIMethod->addResponse(200, $openApiResponse); + } + } + + // /** @var Params[] */ + // $paramsDoc = $reflectionMethod->getAttributes(Params::class); + // if ($paramsDoc) { + // $Parameter = new Parameter(); + // foreach ($paramsDoc as $v) { + // $Parameter->addProperties($v->name, $v->type, $v->value, $v->example, $v->required); + // } + + // $openAPIMethod->withParameters($Parameter->getData()); + // } + + // /** @var RequestValue[] */ + // $requestValuesDoc = $reflectionMethod->getAttributes(RequestValue::class); + // if ($requestValuesDoc && $openAPIRequestBody) { + // foreach ($requestValuesDoc as $v) { + // $requestBodySchema->addProperties($v->name, $v->type, $v->value, $v->example, $v->required); + // } + + // $openAPIRequestBody->withParameter($requestBodySchema); + // $openAPIMethod->withRequestBody($openAPIRequestBody); + // } + + self::$OpenAPI->addPath($routeDoc->route, $openAPIMethod); + } + // } catch (Throwable $th) { + // if ($this->_isIgnoreException) { + // echo '解析参数异常:'.PHP_EOL; + // exit(highlight_string(var_export($th, true))); + // } + + // } + + } + + /** + * 根据类信息构建Schema + */ + public function buildSchemaByClass(string $className): SchemaStorage + { + $schema = new SchemaStorage(); + + if (! $className) { + return $schema; + } + + $ParserPartaker = new ParserPartaker(); + $ParserPartaker->addNode($className, null); + + $tree = $ParserPartaker->getTree(); + + $schema->createTree($tree->getChildren()); + + return $schema; + } + + public function output(string $path): bool + { + return true; + } + + public function toString(): string + { + return json_encode(self::$OpenAPI, JSON_UNESCAPED_UNICODE); + } +} diff --git a/src/OpenApi/OpenApi.php b/src/OpenApi/OpenApi.php new file mode 100755 index 0000000..bd15630 --- /dev/null +++ b/src/OpenApi/OpenApi.php @@ -0,0 +1,89 @@ +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; + } + + $routeDoc = $summaryDoc = $requestBodyDoc = $responseDoc = $headersDoc = null; + $instances = [ + Route::class => &$routeDoc, + Summary::class => &$summaryDoc, + RequestBody::class => &$requestBodyDoc, + Response::class => &$responseDoc, + Headers::class => &$headersDoc, + ]; + + foreach ($methodAttributes as $methodAttribute) { + $name = $methodAttribute->getName(); + if (array_key_exists($name,$instances)) { + $instances[$name] = $methodAttribute->newInstance(); + } + } + + if (! $routeDoc || ! $summaryDoc) { + continue; + } + + $openApiCollection = new OpenApiCollection( + controllerClass: $className, + methodName: $item->getName(), + reflectionMethod: $item, + tag: $tagDoc, + summary: $summaryDoc, + route: $routeDoc, + headers: $headersDoc, + attributes: $methodAttributes, + requestBody: $requestBodyDoc, + response: $responseDoc, + ); + + + self::$OpenAPI->addPath($openApiCollection); + } + } + + + + +} diff --git a/src/OpenApi/Storage/OpenAPI/Method/Delete.php b/src/OpenApi/Storage/OpenAPI/Method/Delete.php new file mode 100755 index 0000000..c3995fa --- /dev/null +++ b/src/OpenApi/Storage/OpenAPI/Method/Delete.php @@ -0,0 +1,9 @@ + $parameters */ - public array|stdClass $parameters = new stdClass(), - public RequestBodyStorage|stdClass $requestBody = new stdClass(), - /** @var array 返回信息 */ + /** @var array $parameters */ + public array $parameters = [], + public array|stdClass $requestBody = new stdClass(), + /** @var array $responses */ public array|stdClass $responses = new stdClass(), ) { } @@ -50,8 +47,7 @@ public function addParameters(array $parameters): void */ public function withRequestBody(RequestBodyStorage $body): static { - $this->requestBody = $body; - + $this->requestBody = $body->getData(); return $this; } @@ -63,8 +59,8 @@ public function withRequestBody(RequestBodyStorage $body): static public function addResponse(int $code, ResponseStorage $response): static { - $this->responses = new stdClass(); // 去除初始化 - $this->responses[$code] = $response; + $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 index d04a7b5..1d4d7c6 100755 --- a/src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php +++ b/src/OpenApi/Storage/OpenAPI/Method/MethodInterface.php @@ -6,5 +6,4 @@ interface MethodInterface { - public function getName(): string; } diff --git a/src/OpenApi/Storage/OpenAPI/Method/POST.php b/src/OpenApi/Storage/OpenAPI/Method/POST.php deleted file mode 100755 index 7a3cc44..0000000 --- a/src/OpenApi/Storage/OpenAPI/Method/POST.php +++ /dev/null @@ -1,13 +0,0 @@ - 请求api的标识符 */ + /** @var array */ public array $tags; - /** @var array 具体api配置 */ + /** @var array */ public array $paths = []; public function withApiInfo(ApiInfo $apiInfo): self @@ -59,9 +57,11 @@ public function withPaths(array $paths): self return $this; } - public function addPath(string $url, MethodInterface $method): self + public function addPath(OpenApiCollection $openApiCollection): self { - $this->paths[$url][$method->getName()] = $method; + $this->paths[$openApiCollection->route->route][strtolower($openApiCollection->route->method->name)] = $openApiCollection->build(); return $this; } + + } diff --git a/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php index 689a7a0..5b74f85 100755 --- a/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php +++ b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php @@ -12,16 +12,27 @@ */ class RequestBodyStorage implements StorageInterface { + public array $parameters; + public function __construct( public ContentTypeEnum $contentType = ContentTypeEnum::JSON, - /** @var array 参数类型 */ - public array $content = [] ) { - $this->content[$this->contentType->value]['schema'] = []; } public function withParameter(SchemaStorage $schema): void { - $this->content[$this->contentType->value]['schema'] = $schema->getData(); + $this->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 index 565d23e..d74c959 100755 --- a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php +++ b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php @@ -6,24 +6,32 @@ use Astral\Serialize\OpenApi\Storage\StorageInterface; -/** - * 参数配置 - */ class ResponseStorage implements StorageInterface { - /** @var RequestBodyStorage 参数类型 */ - public RequestBodyStorage $content; + public array $parameter; public function __construct( public string $contentType = 'application/json', - public string $description = '', - public ?string $group = null + public string $description = '成功', + public string|null $groups = null, ) { - $this->content[$this->contentType]['schema'] = []; } - public function withParameter(SchemaStorage $schema): void + public function withParameter(SchemaStorage $schema): static { - $this->content[$this->contentType]['schema'] = $schema->getData(); + $this->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 index 0dd80b5..135dc5f 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -4,13 +4,9 @@ namespace Astral\Serialize\OpenApi\Storage\OpenAPI; -use Astral\Serialize\OpenApi\Annotations\Parameter; -use Astral\Serialize\OpenApi\Handler\ParserPartaker; +use Astral\Serialize\OpenApi\Collections\ParameterCollection; use Astral\Serialize\OpenApi\Storage\StorageInterface; -use Astral\Serialize\OpenApi\Storage\TreeNode; use Exception; -use ReflectionClass; -use ReflectionProperty; /** * 文档开发者介绍 @@ -20,7 +16,6 @@ class SchemaStorage implements StorageInterface private array|SchemaStorage $data = [ 'type' => 'object', 'properties' => [], - // 'required' => [], ]; public function getData(): SchemaStorage|array @@ -31,12 +26,11 @@ public function getData(): SchemaStorage|array /** * Undocumented function * - * @param array $tree + * @param array $tree * @param mixed|null $node - * @return void - * @throws Exception + * @return SchemaStorage */ - public function createTree(array $tree, mixed &$node = null): void + public function build(array $tree, mixed &$node = null): static { if ($node === null) { $node = &$this->data; @@ -44,44 +38,46 @@ public function createTree(array $tree, mixed &$node = null): void foreach ($tree as $item) { - $parameter = $this->parserParameter($item->getValue()); - if ($parameter->ignore) { + if ($item->ignore) { continue; } - $node['properties'][$parameter->name] = [ - 'type' => $parameter->type, - 'description' => $parameter->value, - 'example' => $parameter->example, + $node['properties'][$item->name] = [ + 'type' => strtolower($item->type->getOpenApiName()), + 'description' => $item->descriptions, + 'example' => $item->example, ]; - if ($parameter->required) { - $node['required'][] = $parameter->name; + if ($item->required) { + $node['required'][] = $item->name; } - if ($item->getChildren()) { + if ($item->children) { // list对象 - if ($parameter->type === 'array') { - $node['properties'][$parameter->name]['items'] = [ + if ($item->type->isCollect()) { + $node['properties'][$item->name]['items'] = [ 'type' => 'object', 'properties' => [], - // 'required' => [], ]; - $tree = &$node['properties'][$parameter->name]['items']; + $tree = &$node['properties'][$item->name]['items']; } // 单个对象 - elseif ($parameter->type === 'object') { - $node['properties'][$parameter->name] = [ + elseif ($item->type->existsCollectClass()) { + $node['properties'][$item->name] = [ 'type' => 'object', 'properties' => [], - // 'required' => [], ]; - $tree = &$node['properties'][$parameter->name]; + $tree = &$node['properties'][$item->name]; + } + + foreach ($item->children as $v){ + $this->build($v, $tree); } - $this->createTree($item->getChildren(), $tree); } } + + return $this; } public function addProperties(string $name, string $type, string $description, string $example, bool $required = false): void @@ -97,137 +93,4 @@ public function addProperties(string $name, string $type, string $description, s } } - private function parserParameter(ReflectionProperty $prop): Parameter - { - - try { - - $parameterAttributes = $prop->getAttributes(Parameter::class); - - /** @var Parameter $parameter */ - $parameter = isset($parameterAttributes[0]) ? $parameterAttributes[0]->newInstance() : new Parameter(); - - // 转换输入名称 - if (class_exists(GetAlias::class) && $prop->getAttributes(GetAlias::class)) { - $getAsName = $prop->getAttributes(GetAlias::class)[0]->newInstance(); - $parameter->name = $getAsName->name; - } elseif (class_exists(SetAlisa::class) && $prop->getAttributes(SetAlisa::class)) { - $setAsName = $prop->getAttributes(SetAlisa::class)[0]->newInstance(); - $parameter->name = $setAsName->name; - } else { - $parameter->name = $prop->name; - } - - $docComment = $prop->getDocComment(); - - if (! $docComment) { - return $parameter; - } - - // 获取 类型/说明 @var {type} {name} or @var {type} - $varMatch = getVarByDocComment($docComment); - - // 获取 例值 @example {value} - preg_match('/@example\s*(\S+)/', $docComment, $exampleMatch); - - // 获取 是否必填 @requires true / @required true - preg_match('/@(?:requires|required)\s*(\S+)/i', $docComment, $requiredMatch); - - if ($requiredMatch) { - $parameter->required = $requiredMatch[1] == 'true' ? true : false; - } elseif (class_exists(NotBlank::class) && ($prop->getAttributes(NotBlank::class) ?? [])) { - $parameter->required = true; - } - - if ($varMatch) { - $parameter->type = $this->getType($varMatch[1]); - $parameter->value = $varMatch[2] ?? ''; - - $className = str_replace(['[', ']'], '', $varMatch[1]); - if (($parameter->type == 'object' || $parameter->type == 'array') - && ! in_array($className, ['int', 'float', 'bool', 'array', 'string', 'boolean', 'integer']) - ) { - try { - $fullClassName = (new ParserPartaker())->getFullClassName($prop, $className); - } catch (\Throwable $th) { - throw new Exception( - sprintf('%s not find from %s', $className, $prop->getDeclaringClass()->getName()), - $th->getCode(), - $th - ); - } - $reflectionClass = new ReflectionClass($fullClassName); - if ($reflectionClass->isEnum()) { - // object转string - $parameter->type = $parameter->type == 'object' ? 'string' : $parameter->type; - foreach ($reflectionClass->getConstants() as $enum) { - $parameter->value .= isset($enum->name) ? ' ' . $enum->name . ',' : ''; - } - $parameter->value = rtrim($parameter->value, ','); - } - } - } - // 获取忽略 - if (str_contains($docComment, '@ignore')) { - $parameter->ignore = true; - } - - if ($exampleMatch) { - $parameter->example = $exampleMatch[1]; - if (strpos($exampleMatch['1'], '[') === 0 || strpos($exampleMatch['1'], '{') === 0) { - $example = json_decode($exampleMatch[1], true); - if ($exampleMatch[1] && json_last_error()) { - throw new Exception('example JSON 错误 from ' . $prop->getName() . ' in ' . $prop->getDeclaringClass()->getName()); - } - $parameter->example = $example; - } - } - - return $parameter; - } catch (\Throwable $th) { - - echo '参数解析失败了' . PHP_EOL; - throw new Exception($th->getMessage(), $th->getCode(), $th); - } - } - - private function getType(string $type): string - { - - /** - * 不包含一下信息的 - * var array<*,string-class> - * var string-class[] - * var string-class - */ - if ( - ! preg_match('/array<\S+,(\S+)>/', $type, $arrayMatch) - && ! preg_match('/(\S+)\[\]/', $type, $arrayMatch) - && ! preg_match('/(\S+)/', $type, $arrayMatch) - ) { - return 'string'; - } - - // 取出对应值 - $stringClass = strtolower(trim($arrayMatch[1])); - - if (stripos($type, '[]') !== false) { - return 'array'; - } - - if (in_array($stringClass, ['int', 'float', 'bool', 'array', 'string', 'boolean', 'integer'])) { - - $stringClass = $stringClass == 'array' ? 'string' : $stringClass; - $stringClass = $stringClass == 'int' ? 'integer' : $stringClass; - $stringClass = $stringClass == 'bool' ? 'boolean' : $stringClass; - - return $stringClass; - } - - if (stripos($type, '<') === false && stripos($type, '[') === false) { - return 'object'; - } - - return 'array'; - } } diff --git a/tests/Openapi/ArrayOpenApi.php b/tests/Openapi/ArrayOpenApi.php new file mode 100644 index 0000000..b903429 --- /dev/null +++ b/tests/Openapi/ArrayOpenApi.php @@ -0,0 +1,66 @@ +buildByClass(TestOpenApiController::class); + $res = $api->toString(); + print_r($res); + +}); From 3e0d541f880a6d9b3f99a4f7239b1d67b9a84f5b Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 6 Jun 2025 18:02:16 +0800 Subject: [PATCH 03/43] ref v2 --- src/Enums/TypeKindEnum.php | 12 -- src/OpenApi/Collections/OpenApiCollection.php | 5 +- .../Collections/ParameterCollection.php | 3 +- src/OpenApi/Enum/ParameterTypeEnum.php | 41 +++++ src/OpenApi/Handler/Handler.php | 140 +++++++++--------- tests/Openapi/ArrayOpenApi.php | 9 +- 6 files changed, 117 insertions(+), 93 deletions(-) diff --git a/src/Enums/TypeKindEnum.php b/src/Enums/TypeKindEnum.php index ef13933..438a013 100644 --- a/src/Enums/TypeKindEnum.php +++ b/src/Enums/TypeKindEnum.php @@ -69,16 +69,4 @@ public static function getNameTo(string $type, ?string $className = null): self default => throw new RuntimeException("not found type $type"), }; } - - public function getOpenApiName(): string - { - return match ($this) { - self::INT => 'integer', - self::FLOAT => 'number', - self::BOOLEAN => 'boolean', - self::OBJECT, self::CLASS_OBJECT => 'object', - self::ARRAY, self::COLLECT_SINGLE_OBJECT , self::COLLECT_UNION_OBJECT => 'array', - default => 'string', - }; - } } diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index c09b698..c08787c 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -10,6 +10,7 @@ use Astral\Serialize\OpenApi\Annotations\Summary; use Astral\Serialize\OpenApi\Annotations\Tag; use Astral\Serialize\OpenApi\Enum\ContentTypeEnum; +use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Method; use Astral\Serialize\OpenApi\Storage\OpenAPI\RequestBodyStorage; use Astral\Serialize\OpenApi\Storage\OpenAPI\ResponseStorage; @@ -109,7 +110,7 @@ public function buildRequestBodyParameterCollections(string $className, array $g $vol = new ParameterCollection( name: current($property->getInputNamesByGroups($groups,$className)), descriptions: '', - type: current($property->getTypes())->kind ?: TypeKindEnum::STRING, + type: ParameterTypeEnum::getByTypes($property->getTypes()), required: !$property->isNullable(), ignore: false, ); @@ -153,7 +154,7 @@ public function buildResponseParameterCollections(): array $vol = new ParameterCollection( name:current($property->getOutNamesByGroups($groups,$responseClass)), descriptions: '', - type: current($property->getTypes())->kind ?: TypeKindEnum::STRING, + type: ParameterTypeEnum::getByTypes($property->getTypes()), required: !$property->isNullable(), ignore: false, ); diff --git a/src/OpenApi/Collections/ParameterCollection.php b/src/OpenApi/Collections/ParameterCollection.php index f161b6d..e9049c5 100755 --- a/src/OpenApi/Collections/ParameterCollection.php +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -5,6 +5,7 @@ namespace Astral\Serialize\OpenApi\Collections; use Astral\Serialize\Enums\TypeKindEnum; +use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Attribute; class ParameterCollection @@ -14,7 +15,7 @@ public function __construct( public string $name, /** @var string descriptions */ public string $descriptions = '', - public TypeKindEnum $type = TypeKindEnum::STRING, + public ParameterTypeEnum $type = ParameterTypeEnum::STRING, /** @var mixed 示例值 */ public mixed $example = '', /** @var bool 是否必填 */ diff --git a/src/OpenApi/Enum/ParameterTypeEnum.php b/src/OpenApi/Enum/ParameterTypeEnum.php index a055d28..b45e74a 100755 --- a/src/OpenApi/Enum/ParameterTypeEnum.php +++ b/src/OpenApi/Enum/ParameterTypeEnum.php @@ -2,9 +2,50 @@ namespace Astral\Serialize\OpenApi\Enum; +use Astral\Serialize\Enums\TypeKindEnum; +use Astral\Serialize\Support\Collections\TypeCollection; + enum ParameterTypeEnum: string { case ARRAY = 'array'; case STRING = 'string'; case OBJECT = 'object'; + case BOOLEAN = 'boolean'; + case INTEGER = 'integer'; + case NUMBER = 'number'; + case ONE_OF = 'oneOf'; + case ANY_OF = 'anyOf'; + case ALL_OF = 'allOf'; + + /** + * @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; + foreach ($types as $type){ + if($type->kind === TypeKindEnum::COLLECT_UNION_OBJECT){ + $hasUnion = true; + } + } + + return $hasUnion ? self::ANY_OF : self::ONE_OF; + + + } } diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index f547914..5c5e155 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -16,9 +16,7 @@ abstract class Handler implements HandleInterface { /** @var OpenAPI */ - protected static OpenAPI $OpenAPI; - private string $controllerPrefix = ''; - private string $controllerSuffix = ''; + public static OpenAPI $OpenAPI; public function __construct( protected readonly ParameterStorage $headerParameterStorages = new ParameterStorage() @@ -50,100 +48,98 @@ public function addGlobalHeader(string $name, string $example = '', string $desc } /** - * Undocumented function - */ - public function getOpenAPI(): OpenAPI + * 遍历整个项目根目录,自动扫描所有 PHP 文件, + * 如果文件内容中包含 "Astral\Serialize\OpenApi\Annotations", + * 则认为它是需要处理的 Controller,进而调用 buildByClass。 + * + * @return $this + * @throws ReflectionException + */ + public function handleByFolders(): self { - return self::$OpenAPI; - } + // 1. 取项目根目录 + $projectRoot = getcwd(); - /** - * 增加类前缀标识 - * - * @return $this - */ - public function withControllerPrefix(string $value): self - { - $this->controllerPrefix = $value; - return $this; - } + if ($projectRoot === false) { + return $this; + } - /** - * 增加类后缀标识 - * - * @return $this - */ - public function withControllerSuffix(string $value): self - { - $this->controllerSuffix = $value; - return $this; - } + // 2. 默认根命名空间留空(根据项目实际情况可改为 "App"、"App\\Http\\Controllers" 等) + // 如果你希望扫描时拼接命名空间前缀,可以在这里做修改。 + $rootNamespace = ''; - /** - * @throws ReflectionException - */ - public function handleByAutoLoad(): void - { - $classMap = require dir('vendor/composer/autoload_classmap.php'); - $appClassMap = array_keys(array_filter($classMap, static function ($key) { - return (str_starts_with($key, 'App\\') && str_ends_with($key, 'Controller')) - || (str_starts_with($key, 'April\\') && str_ends_with($key, 'Controller')); - }, ARRAY_FILTER_USE_KEY)); + // 3. 调用内部递归方法开始扫描 + $this->scanFolderRecursively($projectRoot, $rootNamespace); - foreach ($appClassMap as $className) { - $this->buildByClass($className); - } + return $this; } /** - * 解析Controller文件 + * 递归扫描指定目录下的所有子目录和文件。 + * @param string $folder 要扫描的文件夹路径 + * @param string $namespace 该文件夹对应的 PHP 命名空间前缀 * - * @param array $folders 文件路径 => 命名空间 - * @return $this * @throws ReflectionException */ - public function handleByFolders(array $folders): self + protected function scanFolderRecursively(string $folder, string $namespace): void { + // 如果不是目录,跳过 + if (! is_dir($folder)) { + return; + } - foreach ($folders as $folder => $namespace) { - - if (! is_dir($folder)) { + // 遍历当前文件夹下的所有内容 + foreach (scandir($folder) as $file) { + // 跳过 . 和 .. ,以及隐藏文件夹/文件 + if ($file === '.' || $file === '..' || str_starts_with($file, '.')) { continue; } - foreach (scandir($folder) as $file) { + $path = $folder . DIRECTORY_SEPARATOR . $file; - $path = $folder . '/' . $file; - if ($file === '.' || $file === '..' || str_starts_with($file, '.')) { - continue; - } + // 如果是子目录,则递归,并拼接命名空间 + if (is_dir($path)) { - if (is_dir($path)) { - $this->handleByFolders([$path => $namespace . '\\' . $file]); + // 例如,如果当前命名空间是 "App": + // 子目录 "Http" 则新的命名空间为 "App\Http" + $newNamespace = $namespace !== '' ? ($namespace . '\\' . $file) : $file; + $this->scanFolderRecursively($path, $newNamespace); + continue; + } - continue; - } + // 只处理 .php 文件 + if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') { + continue; + } - 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; + } - $fileName = $this->controllerPrefix . trim(substr($file, 0, strpos($file, '.'))) . $this->controllerSuffix; - $className = $namespace ? $namespace . '\\' . $fileName : $fileName; + // 计算类名:去掉 .php 之后,将命名空间前缀 + 文件名 组成完整类名 + $baseName = substr($file, 0, -4); // 去掉 ".php" + $className = $namespace !== '' ? ($namespace . '\\' . $baseName) : $baseName; + // 如果类尚未加载,则尝试 include + if (! class_exists($className)) { + include_once $path; + @ob_clean(); if (! class_exists($className)) { - include_once $folder . '/' . $file; - ob_clean(); // 清除一些引入进来的莫名其妙输出文件 - if (! class_exists($className)) { - continue; - } + // 如果 include 后仍然不存在该类,跳过 + continue; } - - $this->buildByClass($className); } - } - return $this; + // 调用子类实现的 buildByClass + $this->buildByClass($className); + } } @@ -158,6 +154,6 @@ public function output(string $path): bool */ public function toString(): string { - return json_encode(self::$OpenAPI, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); + return json_encode(self::$OpenAPI, JSON_THROW_ON_ERROR); } } diff --git a/tests/Openapi/ArrayOpenApi.php b/tests/Openapi/ArrayOpenApi.php index b903429..87ea591 100644 --- a/tests/Openapi/ArrayOpenApi.php +++ b/tests/Openapi/ArrayOpenApi.php @@ -57,10 +57,7 @@ public function one(TestOpenApiRequest $request): TestOpenApiResponse // it('test openapi build by class', function () { - - $api = new \Astral\Serialize\OpenApi\OpenApi(); - $api->buildByClass(TestOpenApiController::class); - $res = $api->toString(); - print_r($res); - + $api = new \Astral\Serialize\OpenApi\OpenApi(); + $api->buildByClass(TestOpenApiController::class); + $res = $api->toString(); }); From ab7f5d1102dcd12a53d666427e90d88b531a5ba1 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 15:21:16 +0800 Subject: [PATCH 04/43] RC1 --- src/OpenApi/Collections/OpenApiCollection.php | 10 +- .../ParameterChildrenCollection.php | 17 ++ .../Collections/ParameterCollection.php | 17 +- src/OpenApi/Enum/ParameterTypeEnum.php | 50 ++++++ src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 162 ++++++++++++------ .../Openapi/{ArrayOpenApi.php => OpenApi.php} | 20 +-- 6 files changed, 209 insertions(+), 67 deletions(-) create mode 100644 src/OpenApi/Collections/ParameterChildrenCollection.php rename tests/Openapi/{ArrayOpenApi.php => OpenApi.php} (62%) diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index c08787c..984fe8e 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -105,11 +105,14 @@ public function buildRequestBodyParameterCollections(string $className, array $g $serializeContext->from(); $properties = $serializeContext->getGroupCollection()->getProperties(); + $vols = []; foreach ($properties as $property){ $vol = new ParameterCollection( + className: $className, name: current($property->getInputNamesByGroups($groups,$className)), descriptions: '', + types: $property->getTypes(), type: ParameterTypeEnum::getByTypes($property->getTypes()), required: !$property->isNullable(), ignore: false, @@ -117,7 +120,8 @@ public function buildRequestBodyParameterCollections(string $className, array $g if($property->getChildren()){ foreach ($property->getChildren() as $children){ - $vol->children[] = $this->buildRequestBodyParameterCollections($children->getClassName()); + $className = $children->getClassName(); + $vol->children[$className] = $this->buildRequestBodyParameterCollections($className); } } @@ -144,7 +148,7 @@ public function buildResponseParameterCollections(): array return []; } - $groups = $this->response ? $this->response->groups : ['default']; + $groups = $this->response && is_array($this->response->groups) ? $this->response->groups : ['default']; $serializeContext = ContextFactory::build($responseClass); $serializeContext->from(); $properties = $serializeContext->getGroupCollection()->getProperties(); @@ -152,8 +156,10 @@ public function buildResponseParameterCollections(): array $vols = []; foreach ($properties as $property){ $vol = new ParameterCollection( + className: $responseClass, name:current($property->getOutNamesByGroups($groups,$responseClass)), descriptions: '', + types: $property->getTypes(), type: ParameterTypeEnum::getByTypes($property->getTypes()), required: !$property->isNullable(), ignore: false, diff --git a/src/OpenApi/Collections/ParameterChildrenCollection.php b/src/OpenApi/Collections/ParameterChildrenCollection.php new file mode 100644 index 0000000..879b804 --- /dev/null +++ b/src/OpenApi/Collections/ParameterChildrenCollection.php @@ -0,0 +1,17 @@ + $children */ + public array $children = [], + ) + { + } +} \ No newline at end of file diff --git a/src/OpenApi/Collections/ParameterCollection.php b/src/OpenApi/Collections/ParameterCollection.php index e9049c5..027c803 100755 --- a/src/OpenApi/Collections/ParameterCollection.php +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -6,24 +6,37 @@ use Astral\Serialize\Enums\TypeKindEnum; use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; +use Astral\Serialize\Support\Collections\TypeCollection; use Attribute; class ParameterCollection { public function __construct( + public string $className, /** @var string 元素变量名 */ public string $name, /** @var string descriptions */ public string $descriptions = '', - public ParameterTypeEnum $type = ParameterTypeEnum::STRING, + /** @var TypeCollection[] $types */ + public array $types, + public ParameterTypeEnum $type, /** @var mixed 示例值 */ public mixed $example = '', /** @var bool 是否必填 */ public bool $required = false, /** @var bool 是否忽略显示 */ public bool $ignore = false, - /** @var array $children */ + /** @var array $children */ public array $children = [], ){ } + + public function addChildren(array $collections,ParameterTypeEnum $type = ParameterTypeEnum::STRING): void + { + $children = new ParameterChildrenCollection(); + $children->type = $type; + $children->children = $collections; + $this->children[] = $children; + } } + diff --git a/src/OpenApi/Enum/ParameterTypeEnum.php b/src/OpenApi/Enum/ParameterTypeEnum.php index b45e74a..72631e2 100755 --- a/src/OpenApi/Enum/ParameterTypeEnum.php +++ b/src/OpenApi/Enum/ParameterTypeEnum.php @@ -17,6 +17,56 @@ enum ParameterTypeEnum: string case ANY_OF = 'anyOf'; case ALL_OF = 'allOf'; + public function isObject(): bool + { + return $this === self::OBJECT; + } + + public function isArray(): bool + { + return $this === self::ARRAY; + } + + public function isOf(): bool + { + return $this === self::ONE_OF || $this === self::ANY_OF || $this === self::ALL_OF; + } + + public static function getBaseEnumByTypeKindEnum(TypeCollection $collection, string $className = null): ?ParameterTypeEnum + { + return match (true){ + $collection->kind === TypeKindEnum::STRING && !$className => self::STRING, + $collection->kind === TypeKindEnum::INT && !$className => self::INTEGER, + $collection->kind === TypeKindEnum::FLOAT && !$className => self::NUMBER, + $collection->kind === TypeKindEnum::BOOLEAN && !$className=> self::BOOLEAN, + in_array($collection->kind, [TypeKindEnum::CLASS_OBJECT, TypeKindEnum::OBJECT], true) && $className === $collection->className => self::OBJECT, + in_array($collection->kind, [TypeKindEnum::ARRAY, TypeKindEnum::COLLECT_SINGLE_OBJECT, TypeKindEnum::COLLECT_UNION_OBJECT], true) && $className === $collection->className => self::ARRAY, + 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; + } + /** * @param TypeCollection[] $types */ diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php index 135dc5f..bbfeb8b 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -5,92 +5,154 @@ namespace Astral\Serialize\OpenApi\Storage\OpenAPI; use Astral\Serialize\OpenApi\Collections\ParameterCollection; +use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Astral\Serialize\OpenApi\Storage\StorageInterface; -use Exception; /** - * 文档开发者介绍 + * OpenAPI Schema 数据存储和构建器 + * 用于生成符合 OpenAPI 规范的 Schema 结构 */ class SchemaStorage implements StorageInterface { - private array|SchemaStorage $data = [ + /** + * Schema 数据结构 + */ + private array $data = [ 'type' => 'object', 'properties' => [], ]; - public function getData(): SchemaStorage|array + /** + * 获取构建的 Schema 数据 + */ + public function getData(): array { return $this->data; } /** - * Undocumented function + * 构建 OpenAPI Schema 数据结构 * - * @param array $tree - * @param mixed|null $node - * @return SchemaStorage + * @param array $parameterTree 参数集合树 + * @param array|null $currentNode 当前构建节点的引用 + * @return static */ - public function build(array $tree, mixed &$node = null): static + public function build(array $parameterTree, array &$currentNode = null): static { - if ($node === null) { - $node = &$this->data; + if ($currentNode === null) { + $currentNode = &$this->data; } - foreach ($tree as $item) { + foreach ($parameterTree as $parameter) { + - if ($item->ignore) { + // 跳过被标记为忽略的参数 + if ($parameter->ignore) { continue; } - $node['properties'][$item->name] = [ - 'type' => strtolower($item->type->getOpenApiName()), - 'description' => $item->descriptions, - 'example' => $item->example, - ]; + // 构建基础属性 Schema + $this->buildBasicPropertySchema($parameter, $currentNode); - if ($item->required) { - $node['required'][] = $item->name; + // oneOf/anyOf/allOf 格式 + if($parameter->type->isOf()){ + $this->buildOfProperties($parameter, $currentNode); + } + // 处理嵌套子属性 + else if ($parameter->children) { + $this->buildNestedProperties($parameter, $currentNode); } + } - if ($item->children) { - // list对象 - if ($item->type->isCollect()) { - $node['properties'][$item->name]['items'] = [ - 'type' => 'object', - 'properties' => [], - ]; - $tree = &$node['properties'][$item->name]['items']; - } - // 单个对象 - elseif ($item->type->existsCollectClass()) { - $node['properties'][$item->name] = [ - 'type' => 'object', - 'properties' => [], - ]; - $tree = &$node['properties'][$item->name]; - } + return $this; + } + + /** + * 构建基础属性的 Schema 结构 + */ + private function buildBasicPropertySchema(ParameterCollection $parameter, array &$currentNode): void + { + $propertyName = $parameter->name; + + $currentNode['properties'][$propertyName] = [ + 'type' => $parameter->type->value, + 'description' => $parameter->descriptions, + 'example' => $parameter->example, + ]; - foreach ($item->children as $v){ - $this->build($v, $tree); + // 添加必填字段标记 + 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; + foreach ($topParameter->types as $kindType){ + $type = ParameterTypeEnum::getBaseEnumByTypeKindEnum($kindType); + if($type){ + $node[$i] = ['type'=> $type]; + $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); } } - return $this; } - public function addProperties(string $name, string $type, string $description, string $example, bool $required = false): void + /** + * 构建嵌套属性结构 + */ + private function buildNestedProperties(ParameterCollection $topParameter, array &$currentNode): void { - $this->data['properties'][$name] = [ - 'type' => $type, - 'description' => $description, - 'example' => $example, - ]; + $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' => $topParameter->descriptions, + ]; + $nestedNode = &$currentNode['properties'][$propertyName]; + } - if ($required) { - $this->data['required'][] = $name; + // 递归构建子属性 + if ($nestedNode !== null) { + foreach ($topParameter->children as $childParameter) { + $this->build($childParameter, $nestedNode); + } } } - -} +} \ No newline at end of file diff --git a/tests/Openapi/ArrayOpenApi.php b/tests/Openapi/OpenApi.php similarity index 62% rename from tests/Openapi/ArrayOpenApi.php rename to tests/Openapi/OpenApi.php index 87ea591..579ad22 100644 --- a/tests/Openapi/ArrayOpenApi.php +++ b/tests/Openapi/OpenApi.php @@ -9,7 +9,8 @@ class OtherOpenApiArrayNestedOne public string $name_one; public int $id_one; - public OtherOpenApiArrayNestedTwo $otherNestedTwo; + public OtherOpenApiArrayNestedTwo $object_test; + } class OtherOpenApiArrayNestedTwo @@ -22,10 +23,10 @@ class OtherOpenApiArrayNestedTwo class TestOpenApiRequest extends Serialize { public string $name; - public int $id; + public int|float|string $id; - /** @var OtherOpenApiArrayNestedOne[] $otherNestedOne */ - public array $otherNestedOne; + /** @var OtherOpenApiArrayNestedOne[]|OtherOpenApiArrayNestedTwo[]|string[] $any_array */ + public array $any_array; } class TestOpenApiResponse extends Serialize @@ -39,18 +40,10 @@ class TestOpenApiController{ #[\Astral\Serialize\OpenApi\Annotations\Summary('测试方法一')] #[\Astral\Serialize\OpenApi\Annotations\Route('/test/one-action')] - public function one(TestOpenApiRequest $request): TestOpenApiResponse + public function one(TestOpenApiRequest $request) { return new TestOpenApiResponse(); } - - -// #[\Astral\Serialize\OpenApi\Annotations\Summary('测试方法二')] -// #[\Astral\Serialize\OpenApi\Annotations\Route('test/two-action')] -// #[\Astral\Serialize\OpenApi\Annotations\Response(TestOpenApiResponse::class)] -// public function one2(TestOpenApiRequest $request): void -// { -// } } }); @@ -60,4 +53,5 @@ public function one(TestOpenApiRequest $request): TestOpenApiResponse $api = new \Astral\Serialize\OpenApi\OpenApi(); $api->buildByClass(TestOpenApiController::class); $res = $api->toString(); + var_dump($res); }); From 5e1c8583f6f79df84de6ae0cf61254bca21ca087 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 15:37:28 +0800 Subject: [PATCH 05/43] fix phpstan RC2 --- src/OpenApi/Annotations/RequestBody.php | 2 +- src/OpenApi/Collections/OpenApiCollection.php | 14 +- .../ParameterChildrenCollection.php | 17 - .../Collections/ParameterCollection.php | 15 +- src/OpenApi/Enum/ParameterTypeEnum.php | 12 +- src/OpenApi/Handler/Handler.php | 7 +- src/OpenApi/Handler/ParserPartaker.php | 191 ----------- src/OpenApi/Handler/api.php | 314 ------------------ 8 files changed, 16 insertions(+), 556 deletions(-) delete mode 100644 src/OpenApi/Collections/ParameterChildrenCollection.php delete mode 100755 src/OpenApi/Handler/ParserPartaker.php delete mode 100644 src/OpenApi/Handler/api.php diff --git a/src/OpenApi/Annotations/RequestBody.php b/src/OpenApi/Annotations/RequestBody.php index 71e5a96..9323f6d 100755 --- a/src/OpenApi/Annotations/RequestBody.php +++ b/src/OpenApi/Annotations/RequestBody.php @@ -11,7 +11,7 @@ class RequestBody { public function __construct( - /** @var class-string $className */ + /** @var class-string|string $className */ public string $className = '', public ContentTypeEnum $contentType = ContentTypeEnum::JSON, public array|null $group = null diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 984fe8e..1449a95 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -19,6 +19,7 @@ use Astral\Serialize\Support\Factories\ContextFactory; use Psr\SimpleCache\InvalidArgumentException; use ReflectionMethod; +use ReflectionNamedType; class OpenApiCollection { @@ -73,7 +74,8 @@ public function buildRequestBodyByParameters(): RequestBodyStorage { $openAPIRequestBody = new RequestBodyStorage(ContentTypeEnum::JSON); $methodParam = $this->reflectionMethod->getParameters()[0] ?? null; - $requestBodyClass = $methodParam ? $methodParam->getType()?->getName() : ''; + $type = $methodParam?->getType(); + $requestBodyClass = $type instanceof ReflectionNamedType ? $type->getName() : ''; if (is_subclass_of($requestBodyClass, Serialize::class)) { $schemaStorage = (new SchemaStorage())->build($this->buildRequestBodyParameterCollections($requestBodyClass),$node); $openAPIRequestBody->withParameter($schemaStorage); @@ -96,7 +98,7 @@ public function buildResponse(): ResponseStorage /** * @param string $className * @param array $groups - * @return array + * @return array * @throws InvalidArgumentException */ public function buildRequestBodyParameterCollections(string $className, array $groups = ['default']): array @@ -111,9 +113,9 @@ public function buildRequestBodyParameterCollections(string $className, array $g $vol = new ParameterCollection( className: $className, name: current($property->getInputNamesByGroups($groups,$className)), - descriptions: '', types: $property->getTypes(), type: ParameterTypeEnum::getByTypes($property->getTypes()), + descriptions: '', required: !$property->isNullable(), ignore: false, ); @@ -132,7 +134,7 @@ className: $className, } /** - * @return array + * @return array * @throws InvalidArgumentException */ public function buildResponseParameterCollections(): array @@ -157,10 +159,10 @@ public function buildResponseParameterCollections(): array foreach ($properties as $property){ $vol = new ParameterCollection( className: $responseClass, - name:current($property->getOutNamesByGroups($groups,$responseClass)), - descriptions: '', + name: current($property->getOutNamesByGroups($groups,$responseClass)), types: $property->getTypes(), type: ParameterTypeEnum::getByTypes($property->getTypes()), + descriptions: '', required: !$property->isNullable(), ignore: false, ); diff --git a/src/OpenApi/Collections/ParameterChildrenCollection.php b/src/OpenApi/Collections/ParameterChildrenCollection.php deleted file mode 100644 index 879b804..0000000 --- a/src/OpenApi/Collections/ParameterChildrenCollection.php +++ /dev/null @@ -1,17 +0,0 @@ - $children */ - public array $children = [], - ) - { - } -} \ No newline at end of file diff --git a/src/OpenApi/Collections/ParameterCollection.php b/src/OpenApi/Collections/ParameterCollection.php index 027c803..a6d3cc6 100755 --- a/src/OpenApi/Collections/ParameterCollection.php +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -13,30 +13,17 @@ class ParameterCollection { public function __construct( public string $className, - /** @var string 元素变量名 */ public string $name, - /** @var string descriptions */ - public string $descriptions = '', /** @var TypeCollection[] $types */ public array $types, public ParameterTypeEnum $type, - /** @var mixed 示例值 */ + public string $descriptions = '', public mixed $example = '', - /** @var bool 是否必填 */ public bool $required = false, - /** @var bool 是否忽略显示 */ public bool $ignore = false, /** @var array $children */ public array $children = [], ){ } - - public function addChildren(array $collections,ParameterTypeEnum $type = ParameterTypeEnum::STRING): void - { - $children = new ParameterChildrenCollection(); - $children->type = $type; - $children->children = $collections; - $this->children[] = $children; - } } diff --git a/src/OpenApi/Enum/ParameterTypeEnum.php b/src/OpenApi/Enum/ParameterTypeEnum.php index 72631e2..c9cb3d4 100755 --- a/src/OpenApi/Enum/ParameterTypeEnum.php +++ b/src/OpenApi/Enum/ParameterTypeEnum.php @@ -32,15 +32,13 @@ public function isOf(): bool return $this === self::ONE_OF || $this === self::ANY_OF || $this === self::ALL_OF; } - public static function getBaseEnumByTypeKindEnum(TypeCollection $collection, string $className = null): ?ParameterTypeEnum + public static function getBaseEnumByTypeKindEnum(TypeCollection $collection): ?ParameterTypeEnum { return match (true){ - $collection->kind === TypeKindEnum::STRING && !$className => self::STRING, - $collection->kind === TypeKindEnum::INT && !$className => self::INTEGER, - $collection->kind === TypeKindEnum::FLOAT && !$className => self::NUMBER, - $collection->kind === TypeKindEnum::BOOLEAN && !$className=> self::BOOLEAN, - in_array($collection->kind, [TypeKindEnum::CLASS_OBJECT, TypeKindEnum::OBJECT], true) && $className === $collection->className => self::OBJECT, - in_array($collection->kind, [TypeKindEnum::ARRAY, TypeKindEnum::COLLECT_SINGLE_OBJECT, TypeKindEnum::COLLECT_UNION_OBJECT], true) && $className === $collection->className => self::ARRAY, + $collection->kind === TypeKindEnum::STRING => self::STRING, + $collection->kind === TypeKindEnum::INT => self::INTEGER, + $collection->kind === TypeKindEnum::FLOAT => self::NUMBER, + $collection->kind === TypeKindEnum::BOOLEAN => self::BOOLEAN, default => null, }; } diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index 5c5e155..2fe8b09 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -129,12 +129,7 @@ protected function scanFolderRecursively(string $folder, string $namespace): voi // 如果类尚未加载,则尝试 include if (! class_exists($className)) { - include_once $path; - @ob_clean(); - if (! class_exists($className)) { - // 如果 include 后仍然不存在该类,跳过 - continue; - } + continue; } // 调用子类实现的 buildByClass diff --git a/src/OpenApi/Handler/ParserPartaker.php b/src/OpenApi/Handler/ParserPartaker.php deleted file mode 100755 index 3efdd85..0000000 --- a/src/OpenApi/Handler/ParserPartaker.php +++ /dev/null @@ -1,191 +0,0 @@ -tree = new TreeNode(); - } - - public function addNode(string $className, ?string $group = null, ?TreeNode $childTree = null) - { - - $classRefection = new ReflectionClass($className); - - foreach ($classRefection->getProperties() as $property) { - - if ($property->isProtected()) { - continue; - } - - if ($group && class_exists(Group::class)) { - $attributes = $property->getAttributes(Group::class); - if (! $attributes) { - continue; - } - - $groups = $attributes[0]->newInstance(); - if (! in_array($group, $groups->names)) { - continue; - } - } - - $tree = new TreeNode($property); - if ($childTree) { - $childTree->addChildren($tree); - } else { - $this->tree->addChildren($tree); - } - - // // debug - // $docComment = <<getDocComment(); - $varMatch = getVarByDocComment($docComment); - if (! $varMatch) { - continue; - } - - /** - * var array<*,{string-class}> - * var {string-class}[] - * var {string-class} - */ - if ( - ! preg_match('#array<\S+,(\S+)>#', $varMatch[1], $arrayMatch) - && ! preg_match('#(\S+)\[\]#', $varMatch[1], $arrayMatch) - && ! preg_match('#(\S+)#', $varMatch[1], $arrayMatch) - ) { - continue; - } - - $listClass = trim($arrayMatch[1]); - if (in_array(strtolower($listClass), $this->ignoreConst)) { - continue; - } - - try { - $listClass = $this->getFullClassName($property, $listClass); - } catch (\Throwable $th) { - continue; - } - - // 添加子级类信息 - if ($listClass != $className) { - $this->addNode($listClass, $group, $tree); - } - } - } - - public function getFullClassName(ReflectionProperty $property, string $className): string - { - // 当前类命名空间 - $selfNamespaceName = $property->getDeclaringClass()->getNamespaceName(); - // 直接匹配类 - if (class_exists($className)) { - return $className; - } - // 判断是否是同一命名空间下的类 - elseif (class_exists($selfNamespaceName . '\\' . $className)) { - return $selfNamespaceName . '\\' . $className; - } - // 判断是否是引用类 - else { - // 获取引入文件 - $importClass = $this->parseUseStatements($property->getDeclaringClass()); - if (isset($importClass[$className])) { - return $importClass[$className]; - } - } - - throw new Exception('not find class ' . $className); - } - - /** - * 获取引入文件 - */ - private function parseUseStatements(ReflectionClass $reflectionClass): array - { - - $content = file_get_contents($reflectionClass->getFileName()); - preg_match_all('/^\s*use[\s+](.*);$/m', $content, $matches); - $classNames = []; - foreach ($matches[1] as $fullClassName) { - $parts = explode('\\', $fullClassName); - $classNames[end($parts)] = $fullClassName; - } - - return $classNames; - } - - /** - * Undocumented function - * - * @return TreeNode - */ - public function getTree() - { - return $this->tree; - } -} diff --git a/src/OpenApi/Handler/api.php b/src/OpenApi/Handler/api.php deleted file mode 100644 index db0c773..0000000 --- a/src/OpenApi/Handler/api.php +++ /dev/null @@ -1,314 +0,0 @@ -headerParameterStorages) { - $this->headerParameterStorages = new ParameterStorage(); - } - - // 添加头部参数的属性 - $this->headerParameterStorages->addHeaderProperties($name, $description, $example); - - return $this; - } - - /** - * Undocumented function - */ - public function getOpenAPI(): OpenAPI - { - return self::$OpenAPI; - } - - /** - * 增加类前缀标识 - * - * @return $this - */ - public function withControllerPrefix(string $value): self - { - $this->controllerPrefix = $value; - - return $this; - } - - /** - * 增加类后缀标识 - * - * @return $this - */ - public function withControllerSuffix(string $value): self - { - $this->controllerSuffix = $value; - - return $this; - } - - /** - * 是否开启注解异常信息 - * - * @return $this - */ - public function enableException(bool $bool = true): self - { - $this->_isIgnoreException = $bool; - - return $this; - } - - /** - * 解析Controller文件 - * - * @param array $folders 文件路径 => 命名空间 - * @return $this - */ - public function handleByFolders(array $folders): self - { - - foreach ($folders as $folder => $namespace) { - - if (! is_dir($folder)) { - continue; - } - - foreach (scandir($folder) as $file) { - - $path = $folder.'/'.$file; - if ($file == '.' || $file == '..' || strpos($file, '.') === 0) { - continue; - } - - if (is_dir($path)) { - $this->handleByFolders([$path => $namespace.'\\'.$file]); - - continue; - } - - if (pathinfo($file, PATHINFO_EXTENSION) != 'php') { - continue; - } - - $fileName = $this->controllerPrefix.trim(substr($file, 0, strpos($file, '.'))).$this->controllerSuffix; - $className = $namespace ? $namespace.'\\'.$fileName : $fileName; - - if (! class_exists($className)) { - include_once $folder.'/'.$file; - ob_clean(); // 清除一些引入进来的莫名其妙输出文件 - if (! class_exists($className)) { - continue; - } - } - - $this->createOpenAPIByClass($className); - } - } - - return $this; - } - - /** - * 构建OpenApi结构文档 - * - * @param class-string $className - */ - public function createOpenAPIByClass($className): void - { - // try { - - $classRefection = new ReflectionClass($className); - - $tagDoc = $classRefection->getAttributes(Tag::class); - /** @var Tag */ - $tagDoc = isset($tagDoc[0]) ? $tagDoc[0]->newInstance() : null; - if ($tagDoc) { - self::$OpenAPI->addTag($tagDoc->buildTagStorage()); - } - - foreach ($classRefection->getMethods() as $item) { - - /** @var ReflectionMethod */ - $reflectionMethod = $classRefection->getMethod($item->name); - $methodAttributes = $reflectionMethod->getAttributes(); - - if (! $methodAttributes) { - continue; - } - - $routeDoc = null; - $summaryDoc = null; - $requestBodyDoc = null; - $responseDoc = null; - foreach ($methodAttributes as $methodAttribute) { - switch ($methodAttribute) { - case $methodAttribute->getName() == Route::class: - /** @var Route */ - $routeDoc = $methodAttribute->newInstance(); - break; - case $methodAttribute->getName() == Summary::class: - /** @var Summary */ - $summaryDoc = $methodAttribute->newInstance(); - break; - case $methodAttribute->getName() == RequestBody::class: - /** @var RequestBody */ - $requestBodyDoc = $methodAttribute->newInstance(); - break; - case $methodAttribute->getName() == Response::class: - /** @var Response */ - $responseDoc = $methodAttribute->newInstance(); - break; - } - } - - if (! $routeDoc || ! $summaryDoc) { - continue; - } - - $methodClass = $routeDoc->getMethod(); - /** @var MethodInterface|Method| */ - $openAPIMethod = new $methodClass($summaryDoc->value, $summaryDoc->description ?: '', [$tagDoc->value ?: '']); - - // 统一header头 - if ($this->headerParameterStorages) { - $openAPIMethod->withParameters($this->headerParameterStorages->getData()); - } - - if ($requestBodyDoc) { - $openAPIRequestBody = new OpenAPIRequestBody($requestBodyDoc->contentType); - $requestBodySchema = $this->buildSchemaByClass($requestBodyDoc->className); - $openAPIRequestBody->withParameter($requestBodySchema); - $openAPIMethod->withRequestBody($openAPIRequestBody); - } else { - $methodParam = $reflectionMethod->getParameters(); - if (isset($methodParam[0]) && ($requestBodyClass = $methodParam[0]->gettype()->getName()) !== Request::class) { - $openAPIRequestBody = new OpenAPIRequestBody(ContentTypeEnum::JSON); - $requestBodySchema = $this->buildSchemaByClass($requestBodyClass); - $openAPIRequestBody->withParameter($requestBodySchema); - $openAPIMethod->withRequestBody($openAPIRequestBody); - } - } - - if ($responseDoc) { - if (! class_exists($responseDoc->className)) { - throw new Exception( - sprintf('Class "%s" does not exist in "%s" from action "%s"', - $responseDoc->className, - $reflectionMethod->getFileName(), - $reflectionMethod->getName()) - ); - } - - $openApiResponse = new OpenAPIResponse(); - $openApiResponse->description = '成功'; - $openApiResponse->withParameter($this->buildSchemaByClass($responseDoc->className)); - $openAPIMethod->addResponse($responseDoc->code, $openApiResponse); - - } else { - /** @var ReflectionType */ - $returnClass = $classRefection->getMethod($item->name)->getReturnType(); - if ($returnClass && class_exists($returnClass->getName())) { - $openApiResponse = new OpenAPIResponse(); - $openApiResponse->description = '成功'; - $openApiResponse->withParameter($this->buildSchemaByClass($returnClass->getName())); - $openAPIMethod->addResponse(200, $openApiResponse); - } - } - - // /** @var Params[] */ - // $paramsDoc = $reflectionMethod->getAttributes(Params::class); - // if ($paramsDoc) { - // $Parameter = new Parameter(); - // foreach ($paramsDoc as $v) { - // $Parameter->addProperties($v->name, $v->type, $v->value, $v->example, $v->required); - // } - - // $openAPIMethod->withParameters($Parameter->getData()); - // } - - // /** @var RequestValue[] */ - // $requestValuesDoc = $reflectionMethod->getAttributes(RequestValue::class); - // if ($requestValuesDoc && $openAPIRequestBody) { - // foreach ($requestValuesDoc as $v) { - // $requestBodySchema->addProperties($v->name, $v->type, $v->value, $v->example, $v->required); - // } - - // $openAPIRequestBody->withParameter($requestBodySchema); - // $openAPIMethod->withRequestBody($openAPIRequestBody); - // } - - self::$OpenAPI->addPath($routeDoc->route, $openAPIMethod); - } - // } catch (Throwable $th) { - // if ($this->_isIgnoreException) { - // echo '解析参数异常:'.PHP_EOL; - // exit(highlight_string(var_export($th, true))); - // } - - // } - - } - - /** - * 根据类信息构建Schema - */ - public function buildSchemaByClass(string $className): SchemaStorage - { - $schema = new SchemaStorage(); - - if (! $className) { - return $schema; - } - - $ParserPartaker = new ParserPartaker(); - $ParserPartaker->addNode($className, null); - - $tree = $ParserPartaker->getTree(); - - $schema->createTree($tree->getChildren()); - - return $schema; - } - - public function output(string $path): bool - { - return true; - } - - public function toString(): string - { - return json_encode(self::$OpenAPI, JSON_UNESCAPED_UNICODE); - } -} From 71a0e29895d261c644820c35ae177e9733913532 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 15:40:13 +0800 Subject: [PATCH 06/43] fix phpstan RC2 --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 1825245..efb4237 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e2fb9f91440aed5f8990e559a3d1b4be", + "content-hash": "7d1eb67f33b4afb14170c3b9812e0f20", "packages": [ { "name": "carbonphp/carbon-doctrine-types", From baf40079d7227a31ec43aefa62b7977cc2dce82b Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 16:03:45 +0800 Subject: [PATCH 07/43] fix phpstan RC2 --- src/OpenApi/OpenApi.php | 28 ++++++++----------- src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 1 + 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/OpenApi/OpenApi.php b/src/OpenApi/OpenApi.php index bd15630..c6b887a 100755 --- a/src/OpenApi/OpenApi.php +++ b/src/OpenApi/OpenApi.php @@ -45,13 +45,12 @@ public function buildByClass(string $className): void continue; } - $routeDoc = $summaryDoc = $requestBodyDoc = $responseDoc = $headersDoc = null; $instances = [ - Route::class => &$routeDoc, - Summary::class => &$summaryDoc, - RequestBody::class => &$requestBodyDoc, - Response::class => &$responseDoc, - Headers::class => &$headersDoc, + Route::class => null, + Summary::class => null, + RequestBody::class => null, + Response::class => null, + Headers::class => null, ]; foreach ($methodAttributes as $methodAttribute) { @@ -61,7 +60,7 @@ public function buildByClass(string $className): void } } - if (! $routeDoc || ! $summaryDoc) { + if ($instances[Route::class] === null || $instances[Summary::class] === null) { continue; } @@ -70,20 +69,15 @@ public function buildByClass(string $className): void methodName: $item->getName(), reflectionMethod: $item, tag: $tagDoc, - summary: $summaryDoc, - route: $routeDoc, - headers: $headersDoc, + summary: $instances[Summary::class], + route: $instances[Route::class], + headers: $instances[Headers::class], attributes: $methodAttributes, - requestBody: $requestBodyDoc, - response: $responseDoc, + requestBody: $instances[RequestBody::class], + response: $instances[Response::class], ); - self::$OpenAPI->addPath($openApiCollection); } } - - - - } diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php index bbfeb8b..f5e0ab8 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -35,6 +35,7 @@ public function getData(): array * * @param array $parameterTree 参数集合树 * @param array|null $currentNode 当前构建节点的引用 + * @param-out array $currentNode 当前构建节点的引用 * @return static */ public function build(array $parameterTree, array &$currentNode = null): static From 7f6e7324812ae2e052f90498f3254fddba87e83f Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 16:20:04 +0800 Subject: [PATCH 08/43] =?UTF-8?q?composer=20=E5=9B=BA=E5=AE=9A8.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 3 + composer.lock | 581 ++++++++++++++++++++++++++------------------------ 2 files changed, 300 insertions(+), 284 deletions(-) diff --git a/composer.json b/composer.json index 5e14d29..7a18e2b 100755 --- a/composer.json +++ b/composer.json @@ -41,6 +41,9 @@ "config": { "allow-plugins": { "pestphp/pest-plugin": true + }, + "platform": { + "php": "8.1.32" } } } diff --git a/composer.lock b/composer.lock index efb4237..b453161 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7d1eb67f33b4afb14170c3b9812e0f20", + "content-hash": "a5a7da27a45194d7e04046a062ca1490", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -77,26 +77,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "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", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "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.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -116,9 +119,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/inflector", @@ -649,16 +652,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -701,25 +704,26 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "phpdocumentor/reflection", - "version": "6.1.0", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "bb4dea805a645553d6d989b23dad9f8041f39502" + "reference": "d91b3270832785602adcc24ae2d0974ba99a8ff8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/bb4dea805a645553d6d989b23dad9f8041f39502", - "reference": "bb4dea805a645553d6d989b23dad9f8041f39502", + "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", @@ -730,7 +734,8 @@ }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "doctrine/coding-standard": "^12.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", @@ -738,7 +743,7 @@ "phpstan/phpstan": "^1.8", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^10.0", - "psalm/phar": "^5.24", + "psalm/phar": "^6.0", "rector/rector": "^1.0.0", "squizlabs/php_codesniffer": "^3.8" }, @@ -750,6 +755,9 @@ } }, "autoload": { + "files": [ + "src/php-parser/Modifiers.php" + ], "psr-4": { "phpDocumentor\\": "src/phpDocumentor" } @@ -768,9 +776,9 @@ ], "support": { "issues": "https://github.com/phpDocumentor/Reflection/issues", - "source": "https://github.com/phpDocumentor/Reflection/tree/6.1.0" + "source": "https://github.com/phpDocumentor/Reflection/tree/6.3.0" }, - "time": "2024-11-22T15:11:54+00:00" + "time": "2025-06-06T13:39:18+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -827,16 +835,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.1", + "version": "5.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", "shasum": "" }, "require": { @@ -885,9 +893,9 @@ "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.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" }, - "time": "2024-12-07T09:39:29+00:00" + "time": "2025-04-13T19:20:35+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1148,16 +1156,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -1170,7 +1178,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1195,7 +1203,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1211,23 +1219,24 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -1275,7 +1284,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -1291,20 +1300,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -1355,7 +1364,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -1371,20 +1380,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/translation", - "version": "v6.4.19", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "3b9bf9f33997c064885a7bfc126c14b9daa0e00e" + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/3b9bf9f33997c064885a7bfc126c14b9daa0e00e", - "reference": "3b9bf9f33997c064885a7bfc126c14b9daa0e00e", + "url": "https://api.github.com/repos/symfony/translation/zipball/7e3b3b7146c6fab36ddff304a8041174bf6e17ad", + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad", "shasum": "" }, "require": { @@ -1450,7 +1459,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.19" + "source": "https://github.com/symfony/translation/tree/v6.4.22" }, "funding": [ { @@ -1466,20 +1475,20 @@ "type": "tidelift" } ], - "time": "2025-02-13T10:18:43+00:00" + "time": "2025-05-29T07:06:44+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -1492,7 +1501,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1528,7 +1537,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1544,7 +1553,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "voku/portable-ascii", @@ -1682,16 +1691,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.4.8", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "cf16fcbb9b8107a7df6b97e497fc91e819774d8b" + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/cf16fcbb9b8107a7df6b97e497fc91e819774d8b", - "reference": "cf16fcbb9b8107a7df6b97e497fc91e819774d8b", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30", + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30", "shasum": "" }, "require": { @@ -1699,30 +1708,32 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^1.2.0", - "jean85/pretty-package-versions": "^2.0.6", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^10.1.16", + "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.0", - "phpunit/phpunit": "^10.5.36", - "sebastian/environment": "^6.1.0", - "symfony/console": "^6.4.7 || ^7.1.5", - "symfony/process": "^6.4.7 || ^7.1.5" + "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": "*", - "phpstan/phpstan": "^1.12.6", - "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.1", - "squizlabs/php_codesniffer": "^3.10.3", - "symfony/filesystem": "^6.4.3 || ^7.1.5" + "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", @@ -1759,7 +1770,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.4.8" + "source": "https://github.com/paratestphp/paratest/tree/v7.3.1" }, "funding": [ { @@ -1771,7 +1782,7 @@ "type": "paypal" } ], - "time": "2024-10-15T12:45:19+00:00" + "time": "2023-10-31T09:24:17+00:00" }, { "name": "clue/ndjson-react", @@ -2326,16 +2337,16 @@ }, { "name": "filp/whoops", - "version": "2.17.0", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3", "shasum": "" }, "require": { @@ -2385,7 +2396,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.17.0" + "source": "https://github.com/filp/whoops/tree/2.18.2" }, "funding": [ { @@ -2393,20 +2404,20 @@ "type": "github" } ], - "time": "2025-01-25T12:00:00+00:00" + "time": "2025-06-11T20:42:19+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.70.2", + "version": "v3.75.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "1ca468270efbb75ce0c7566a79cca8ea2888584d" + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1ca468270efbb75ce0c7566a79cca8ea2888584d", - "reference": "1ca468270efbb75ce0c7566a79cca8ea2888584d", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", "shasum": "" }, "require": { @@ -2414,6 +2425,7 @@ "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.3", "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", "fidry/cpu-core-counter": "^1.2", @@ -2436,18 +2448,18 @@ "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.5", - "infection/infection": "^0.29.10", - "justinrainbow/json-schema": "^5.3 || ^6.0", + "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.7", - "symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.0", - "symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.0" + "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", @@ -2488,7 +2500,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.70.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0" }, "funding": [ { @@ -2496,24 +2508,24 @@ "type": "github" } ], - "time": "2025-03-03T21:07:23+00:00" + "time": "2025-03-31T18:40:42+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -2521,8 +2533,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "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": { @@ -2545,22 +2557,22 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "jean85/pretty-package-versions", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", "shasum": "" }, "require": { @@ -2570,8 +2582,9 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", @@ -2604,9 +2617,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" }, - "time": "2024-11-18T16:19:46+00:00" + "time": "2025-03-19T14:43:43+00:00" }, { "name": "mockery/mockery", @@ -2693,16 +2706,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -2741,7 +2754,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -2749,42 +2762,44 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.5.0", + "version": "v7.12.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "f5c101b929c958e849a633283adff296ed5f38f5" + "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", - "reference": "f5c101b929c958e849a633283adff296ed5f38f5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/995245421d3d7593a6960822063bdba4f5d7cf1a", + "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a", "shasum": "" }, "require": { - "filp/whoops": "^2.16.0", - "nunomaduro/termwind": "^2.1.0", - "php": "^8.2.0", - "symfony/console": "^7.1.5" + "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 || >=12.0.0", - "phpunit/phpunit": "<10.5.1 || >=12.0.0" + "laravel/framework": ">=11.0.0" }, "require-dev": { - "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.28.0", - "laravel/pint": "^1.18.1", - "laravel/sail": "^1.36.0", - "laravel/sanctum": "^4.0.3", - "laravel/tinker": "^2.10.0", - "orchestra/testbench-core": "^9.5.3", - "pestphp/pest": "^2.36.0 || ^3.4.0", - "sebastian/environment": "^6.1.0 || ^7.2.0" + "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": { @@ -2792,9 +2807,6 @@ "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" ] - }, - "branch-alias": { - "dev-8.x": "8.x-dev" } }, "autoload": { @@ -2846,35 +2858,36 @@ "type": "patreon" } ], - "time": "2024-10-15T16:06:32+00:00" + "time": "2025-03-14T22:35:49+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301", + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^8.2", - "symfony/console": "^7.1.8" + "php": "^8.1", + "symfony/console": "^6.4.15" }, "require-dev": { - "illuminate/console": "^11.33.2", + "illuminate/console": "^10.48.24", + "illuminate/support": "^10.48.24", "laravel/pint": "^1.18.2", - "mockery/mockery": "^1.6.12", "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": "^7.1.8", + "symfony/var-dumper": "^6.4.15", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -2883,9 +2896,6 @@ "providers": [ "Termwind\\Laravel\\TermwindServiceProvider" ] - }, - "branch-alias": { - "dev-2.x": "2.x-dev" } }, "autoload": { @@ -2917,7 +2927,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v1.17.0" }, "funding": [ { @@ -2933,7 +2943,7 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2024-11-21T10:36:35+00:00" }, { "name": "pestphp/pest", @@ -3356,16 +3366,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "4248817222514421cba466bfa7adc7d8932345d4" + "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4", - "reference": "4248817222514421cba466bfa7adc7d8932345d4", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", + "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", "shasum": "" }, "require": { @@ -3442,7 +3452,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.4.0" + "source": "https://github.com/phpbench/phpbench/tree/1.4.1" }, "funding": [ { @@ -3450,20 +3460,20 @@ "type": "github" } ], - "time": "2025-01-26T19:54:45+00:00" + "time": "2025-03-12T08:01:40+00:00" }, { "name": "phpstan/phpstan", - "version": "2.1.6", + "version": "2.1.17", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", "shasum": "" }, "require": { @@ -3508,7 +3518,7 @@ "type": "github" } ], - "time": "2025-02-19T15:46:42+00:00" + "time": "2025-05-21T20:55:28+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5589,46 +5599,47 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v6.4.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "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": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", + "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": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^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": { @@ -5662,7 +5673,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v6.4.22" }, "funding": [ { @@ -5678,28 +5689,28 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-05-07T07:05:04+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.2.0", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/dependency-injection": "<5.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -5708,13 +5719,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "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": "^6.4|^7.0" + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -5742,7 +5753,7 @@ "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/v7.2.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -5758,20 +5769,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -5785,7 +5796,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5818,7 +5829,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -5834,29 +5845,29 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^5.4|^6.4|^7.0" }, "type": "library", "autoload": { @@ -5884,7 +5895,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -5900,27 +5911,27 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^6.0|^7.0" }, "type": "library", "autoload": { @@ -5948,7 +5959,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v6.4.17" }, "funding": [ { @@ -5964,24 +5975,24 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2024-12-29T13:51:37+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.2.0", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + "reference": "368128ad168f20e22c32159b9f761e456cec0c78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", + "reference": "368128ad168f20e22c32159b9f761e456cec0c78", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -6015,7 +6026,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.16" }, "funding": [ { @@ -6031,11 +6042,11 @@ "type": "tidelift" } ], - "time": "2024-11-20T11:17:29+00:00" + "time": "2024-11-20T10:57:02+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -6094,7 +6105,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -6114,7 +6125,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -6172,7 +6183,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -6192,7 +6203,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6253,7 +6264,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -6273,7 +6284,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -6329,7 +6340,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { @@ -6349,20 +6360,20 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v6.4.20", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -6390,7 +6401,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v6.4.20" }, "funding": [ { @@ -6406,20 +6417,20 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-03-10T17:11:00+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -6437,7 +6448,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -6473,7 +6484,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -6489,24 +6500,24 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.2.4", + "version": "v6.4.19", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", + "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -6535,7 +6546,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.2.4" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.19" }, "funding": [ { @@ -6551,24 +6562,24 @@ "type": "tidelift" } ], - "time": "2025-02-24T10:49:57+00:00" + "time": "2025-02-21T10:06:30+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v6.4.21", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -6578,12 +6589,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "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": "^6.4|^7.0" + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -6622,7 +6632,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v6.4.21" }, "funding": [ { @@ -6638,27 +6648,27 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-18T15:23:29+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", - "version": "0.8.4", + "version": "0.8.5", "source": { "type": "git", "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", - "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636" + "reference": "cf6fb197b676ba716837c886baca842e4db29005" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/89f0dea1cb0f0d5744d3ec1764a286af5e006636", - "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "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", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0", "symfony/finder": "^6.4.0 || ^7.0.0" }, "require-dev": { @@ -6695,9 +6705,9 @@ ], "support": { "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", - "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.4" + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.5" }, - "time": "2024-01-05T14:10:56+00:00" + "time": "2025-04-20T20:23:40+00:00" }, { "name": "theseer/tokenizer", @@ -6808,5 +6818,8 @@ "php": "^8.1" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.1.32" + }, "plugin-api-version": "2.6.0" } From 7197714e009d1789eafde8a32c1a14ba19d888ce Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 16:23:41 +0800 Subject: [PATCH 09/43] change SerializeBench --- benchmarks/SerializeBench.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 { From 0e636399b066d3b5586599cf0d8f4efb5620f206 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 12 Jun 2025 16:43:42 +0800 Subject: [PATCH 10/43] phpstan fix --- src/Annotations/Input/InputDateFormat.php | 6 +----- src/Annotations/Output/OutputDateFormat.php | 18 +++++------------- 2 files changed, 6 insertions(+), 18 deletions(-) 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 { From 8b5359c5841d9986521763d5df3abc21b81da4c5 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 13 Jun 2025 10:59:39 +0800 Subject: [PATCH 11/43] add openapi test --- src/OpenApi/Collections/OpenApiCollection.php | 75 +++++-------------- src/OpenApi/Handler/Handler.php | 2 - .../Storage/OpenAPI/ResponseStorage.php | 2 +- tests/Openapi/OpenApi.php | 63 +++++++++++++--- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 1449a95..48633a8 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -2,7 +2,6 @@ namespace Astral\Serialize\OpenApi\Collections; -use Astral\Serialize\Enums\TypeKindEnum; use Astral\Serialize\OpenApi\Annotations\Headers; use Astral\Serialize\OpenApi\Annotations\RequestBody; use Astral\Serialize\OpenApi\Annotations\Response; @@ -52,7 +51,6 @@ public function build() : Method $openAPIMethod->withRequestBody($this->requestBody !== null ? $this->buildRequestBodyByAttribute() : $this->buildRequestBodyByParameters()); $openAPIMethod->addResponse(200, $this->buildResponse()); - return $openAPIMethod; } @@ -62,7 +60,7 @@ public function build() : Method public function buildRequestBodyByAttribute(): RequestBodyStorage { $openAPIRequestBody = new RequestBodyStorage($this->requestBody->contentType); - $schemaStorage = (new SchemaStorage())->build($this->buildRequestBodyParameterCollections($this->requestBody->className,$this->requestBody->group),$n); + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($this->requestBody->className,$this->requestBody->group),$n); $openAPIRequestBody->withParameter($schemaStorage); return $openAPIRequestBody; } @@ -77,7 +75,7 @@ public function buildRequestBodyByParameters(): RequestBodyStorage $type = $methodParam?->getType(); $requestBodyClass = $type instanceof ReflectionNamedType ? $type->getName() : ''; if (is_subclass_of($requestBodyClass, Serialize::class)) { - $schemaStorage = (new SchemaStorage())->build($this->buildRequestBodyParameterCollections($requestBodyClass),$node); + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($requestBodyClass),$node); $openAPIRequestBody->withParameter($schemaStorage); } @@ -89,9 +87,23 @@ public function buildRequestBodyByParameters(): RequestBodyStorage */ 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(); - $schemaStorage = (new SchemaStorage())->build($this->buildResponseParameterCollections()); - $responseStorage->withParameter($schemaStorage); + + + 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; } @@ -101,13 +113,12 @@ public function buildResponse(): ResponseStorage * @return array * @throws InvalidArgumentException */ - public function buildRequestBodyParameterCollections(string $className, array $groups = ['default']): array + 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( @@ -123,53 +134,7 @@ className: $className, if($property->getChildren()){ foreach ($property->getChildren() as $children){ $className = $children->getClassName(); - $vol->children[$className] = $this->buildRequestBodyParameterCollections($className); - } - } - - $vols[] = $vol; - } - - return $vols; - } - - /** - * @return array - * @throws InvalidArgumentException - */ - public function buildResponseParameterCollections(): array - { - $returnClass = $this->reflectionMethod->getReturnType(); - $responseClass = match(true){ - $this->response !== null => $this->response->className, - $returnClass && is_subclass_of($returnClass,Serialize::class) => $returnClass, - default => null, - }; - - if(!$responseClass){ - return []; - } - - $groups = $this->response && is_array($this->response->groups) ? $this->response->groups : ['default']; - $serializeContext = ContextFactory::build($responseClass); - $serializeContext->from(); - $properties = $serializeContext->getGroupCollection()->getProperties(); - - $vols = []; - foreach ($properties as $property){ - $vol = new ParameterCollection( - className: $responseClass, - name: current($property->getOutNamesByGroups($groups,$responseClass)), - types: $property->getTypes(), - type: ParameterTypeEnum::getByTypes($property->getTypes()), - descriptions: '', - required: !$property->isNullable(), - ignore: false, - ); - - if($property->getChildren()){ - foreach ($property->getChildren() as $children){ - $vol->children[] = $this->buildRequestBodyParameterCollections($children->getClassName()); + $vol->children[$className] = $this->buildParameterCollections($className); } } diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index 2fe8b09..7a23515 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -137,8 +137,6 @@ protected function scanFolderRecursively(string $folder, string $namespace): voi } } - - public function output(string $path): bool { return true; diff --git a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php index d74c959..578edd4 100755 --- a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php +++ b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php @@ -8,7 +8,7 @@ class ResponseStorage implements StorageInterface { - public array $parameter; + public array $parameter = []; public function __construct( public string $contentType = 'application/json', diff --git a/tests/Openapi/OpenApi.php b/tests/Openapi/OpenApi.php index 579ad22..ec64356 100644 --- a/tests/Openapi/OpenApi.php +++ b/tests/Openapi/OpenApi.php @@ -2,7 +2,7 @@ use Astral\Serialize\Serialize; -beforeAll(function () { +beforeAll(static function () { class OtherOpenApiArrayNestedOne { @@ -31,7 +31,7 @@ class TestOpenApiRequest extends Serialize class TestOpenApiResponse extends Serialize { - public string $name; + public ?string $name; public int $id; } @@ -40,18 +40,63 @@ class TestOpenApiController{ #[\Astral\Serialize\OpenApi\Annotations\Summary('测试方法一')] #[\Astral\Serialize\OpenApi\Annotations\Route('/test/one-action')] - public function one(TestOpenApiRequest $request) + public function one(TestOpenApiRequest $request): TestOpenApiResponse { return new TestOpenApiResponse(); } + } }); -// -it('test openapi build by class', function () { - $api = new \Astral\Serialize\OpenApi\OpenApi(); +test('OpenAPI structure is correct', function () { + + $api = new \Astral\Serialize\OpenApi\OpenApi(); $api->buildByClass(TestOpenApiController::class); - $res = $api->toString(); - var_dump($res); -}); + + $openApi = $api::$OpenAPI; + + // 顶层结构断言 + expect($openApi->openapi)->toBe('3.1.1') + ->and($openApi->info->title)->toBe('API 接口') + ->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']->value, $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 From 49d5adf410457ef9ed778006bc5862ed50d48a20 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 13 Jun 2025 11:49:37 +0800 Subject: [PATCH 12/43] add openapi test --- tests/Openapi/OpenApi.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Openapi/OpenApi.php b/tests/Openapi/OpenApi.php index ec64356..6b25c81 100644 --- a/tests/Openapi/OpenApi.php +++ b/tests/Openapi/OpenApi.php @@ -1,5 +1,6 @@ buildByClass(TestOpenApiController::class); $openApi = $api::$OpenAPI; From 8a8eb3e5c2067675be64b662a7576b3e0388b9cc Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 13 Jun 2025 16:27:44 +0800 Subject: [PATCH 13/43] add customerRoute --- .openapi.php | 12 +++++ src/{OpenApi => }/OpenApi.php | 12 +++-- src/OpenApi/Handler/Handler.php | 2 +- .../Storage/OpenAPI/RequestBodyStorage.php | 3 +- tests/Openapi/OpenApi.php | 3 +- tests/Openapi/RouteOrewriteOpenApi.php | 46 +++++++++++++++++++ 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 .openapi.php rename src/{OpenApi => }/OpenApi.php (89%) create mode 100644 tests/Openapi/RouteOrewriteOpenApi.php diff --git a/.openapi.php b/.openapi.php new file mode 100644 index 0000000..2c2c114 --- /dev/null +++ b/.openapi.php @@ -0,0 +1,12 @@ + 'API Docs', + 'description' => 'API Docs description.', + 'headers' => [ + + ], + 'service' => [ + + ], +]; diff --git a/src/OpenApi/OpenApi.php b/src/OpenApi.php similarity index 89% rename from src/OpenApi/OpenApi.php rename to src/OpenApi.php index c6b887a..6372dc8 100755 --- a/src/OpenApi/OpenApi.php +++ b/src/OpenApi.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Astral\Serialize\OpenApi; +namespace Astral\Serialize; use Astral\Serialize\OpenApi\Annotations\Headers; use Astral\Serialize\OpenApi\Annotations\RequestBody; @@ -53,13 +53,17 @@ public function buildByClass(string $className): void Headers::class => null, ]; + foreach ($methodAttributes as $methodAttribute) { - $name = $methodAttribute->getName(); - if (array_key_exists($name,$instances)) { - $instances[$name] = $methodAttribute->newInstance(); + $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; } diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index 7a23515..c109963 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -21,7 +21,7 @@ abstract class Handler implements HandleInterface public function __construct( protected readonly ParameterStorage $headerParameterStorages = new ParameterStorage() ) { - self::$OpenAPI ??= (new OpenAPI())->withApiInfo(new ApiInfo('API 接口','')); + self::$OpenAPI ??= (new OpenAPI())->withApiInfo(new ApiInfo('API Doc','')); } /** diff --git a/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php index 5b74f85..1444711 100755 --- a/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php +++ b/src/OpenApi/Storage/OpenAPI/RequestBodyStorage.php @@ -6,13 +6,14 @@ use Astral\Serialize\OpenApi\Enum\ContentTypeEnum; use Astral\Serialize\OpenApi\Storage\StorageInterface; +use stdClass; /** * 参数配置 */ class RequestBodyStorage implements StorageInterface { - public array $parameters; + public array|stdClass $parameters = []; public function __construct( public ContentTypeEnum $contentType = ContentTypeEnum::JSON, diff --git a/tests/Openapi/OpenApi.php b/tests/Openapi/OpenApi.php index 6b25c81..e93ddaf 100644 --- a/tests/Openapi/OpenApi.php +++ b/tests/Openapi/OpenApi.php @@ -1,6 +1,6 @@ openapi)->toBe('3.1.1') - ->and($openApi->info->title)->toBe('API 接口') ->and($openApi->info->version)->toBe('1.0.0') ->and($openApi->tags[0]->name)->toBe('接口测试'); diff --git a/tests/Openapi/RouteOrewriteOpenApi.php b/tests/Openapi/RouteOrewriteOpenApi.php new file mode 100644 index 0000000..4ad287f --- /dev/null +++ b/tests/Openapi/RouteOrewriteOpenApi.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 From 2797635d9440859a8c5e8dadd1626a16d09170ea Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 13 Jun 2025 18:08:31 +0800 Subject: [PATCH 14/43] add config --- src/OpenApi/Handler/Config.php | 48 +++++++++++++++++++++++++++++++++ src/OpenApi/Handler/Handler.php | 15 +++++++++++ 2 files changed, 63 insertions(+) create mode 100755 src/OpenApi/Handler/Config.php diff --git a/src/OpenApi/Handler/Config.php b/src/OpenApi/Handler/Config.php new file mode 100755 index 0000000..42d22bb --- /dev/null +++ b/src/OpenApi/Handler/Config.php @@ -0,0 +1,48 @@ +withApiInfo(new ApiInfo('API Doc','')); } + public function rootPath(): string + { + return dirname(__DIR__, 3); + } + + public function config() + { + $path = $this->rootPath().'/.openapi.php'; + if(is_file($path)){ + return include $path; + } + + return include dirname(__DIR__, 3).'/.openapi.php'; + } + /** * 构建OpenApi结构文档 * From b3bfaa41ba5a68961eeb2a9422295be64de90159 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Mon, 16 Jun 2025 15:11:41 +0800 Subject: [PATCH 15/43] add config --- .openapi.php | 13 +++++++++++- src/OpenApi.php | 2 -- src/OpenApi/Handler/Config.php | 15 +++++++------ src/OpenApi/Handler/HandleInterface.php | 1 - src/OpenApi/Handler/Handler.php | 21 +++++++++++-------- src/OpenApi/Storage/OpenAPI/OpenAPI.php | 2 +- .../Storage/OpenAPI/ServersStorage.php | 13 ++++++++++-- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/.openapi.php b/.openapi.php index 2c2c114..9b018aa 100644 --- a/.openapi.php +++ b/.openapi.php @@ -1,12 +1,23 @@ 'API Docs', + 'description' => 'API Docs description.', + + /** + * 向全局头部参数存储中添加一个的头部参数。 + * @param string $name + * @param string $example + * @param string $description + */ 'headers' => [ ], - 'service' => [ + 'service' => [ + new ServersStorage('http://127.0.0.1','默认环境'), ], ]; diff --git a/src/OpenApi.php b/src/OpenApi.php index 6372dc8..1fa4e92 100755 --- a/src/OpenApi.php +++ b/src/OpenApi.php @@ -53,7 +53,6 @@ public function buildByClass(string $className): void Headers::class => null, ]; - foreach ($methodAttributes as $methodAttribute) { $inst = $methodAttribute->newInstance(); foreach (array_keys($instances) as $anchorClass) { @@ -63,7 +62,6 @@ public function buildByClass(string $className): void } } - if ($instances[Route::class] === null || $instances[Summary::class] === null) { continue; } diff --git a/src/OpenApi/Handler/Config.php b/src/OpenApi/Handler/Config.php index 42d22bb..802826c 100755 --- a/src/OpenApi/Handler/Config.php +++ b/src/OpenApi/Handler/Config.php @@ -15,8 +15,7 @@ class Config { - - public static $config; + public static array $config; public static function rootPath(): string { @@ -29,12 +28,11 @@ public static function build() return self::$config; } + self::$config = include dirname(__DIR__, 3).'/.openapi.php'; + $path = self::rootPath().'/.openapi.php'; if(is_file($path)){ - self::$config = include $path; - } - else{ - self::$config = include dirname(__DIR__, 3).'/.openapi.php'; + self::$config = array_merge(self::$config,include $path); } return self::$config; @@ -45,4 +43,9 @@ public static function get($key) return self::build()[$key] ?? ''; } + public static function has($key): bool + { + return isset(self::build()[$key]); + } + } diff --git a/src/OpenApi/Handler/HandleInterface.php b/src/OpenApi/Handler/HandleInterface.php index ee9f5ba..302a2a5 100755 --- a/src/OpenApi/Handler/HandleInterface.php +++ b/src/OpenApi/Handler/HandleInterface.php @@ -6,7 +6,6 @@ interface HandleInterface { - public function output(string $path): bool; public function toString(): string; } diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index b88a982..d08e7cf 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -15,13 +15,22 @@ abstract class Handler implements HandleInterface { - /** @var OpenAPI */ - public static OpenAPI $OpenAPI; + /** @var OpenAPI|null */ + public static ?OpenAPI $OpenAPI = null; public function __construct( protected readonly ParameterStorage $headerParameterStorages = new ParameterStorage() ) { - self::$OpenAPI ??= (new OpenAPI())->withApiInfo(new ApiInfo('API Doc','')); + + self::$OpenAPI ??= (new OpenAPI()) + ->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']); + } + } } public function rootPath(): string @@ -114,7 +123,6 @@ protected function scanFolderRecursively(string $folder, string $namespace): voi // 如果是子目录,则递归,并拼接命名空间 if (is_dir($path)) { - // 例如,如果当前命名空间是 "App": // 子目录 "Http" 则新的命名空间为 "App\Http" $newNamespace = $namespace !== '' ? ($namespace . '\\' . $file) : $file; @@ -152,11 +160,6 @@ protected function scanFolderRecursively(string $folder, string $namespace): voi } } - public function output(string $path): bool - { - return true; - } - /** * @throws JsonException */ diff --git a/src/OpenApi/Storage/OpenAPI/OpenAPI.php b/src/OpenApi/Storage/OpenAPI/OpenAPI.php index 11e066c..8ad8567 100755 --- a/src/OpenApi/Storage/OpenAPI/OpenAPI.php +++ b/src/OpenApi/Storage/OpenAPI/OpenAPI.php @@ -10,7 +10,6 @@ class OpenAPI implements StorageInterface { - public string $openapi = '3.1.1'; public ApiInfo $info; @@ -34,6 +33,7 @@ public function withApiInfo(ApiInfo $apiInfo): self return $this; } + public function withServers(array $servers): self { $this->servers = $servers; diff --git a/src/OpenApi/Storage/OpenAPI/ServersStorage.php b/src/OpenApi/Storage/OpenAPI/ServersStorage.php index db2f062..93cd827 100755 --- a/src/OpenApi/Storage/OpenAPI/ServersStorage.php +++ b/src/OpenApi/Storage/OpenAPI/ServersStorage.php @@ -12,8 +12,17 @@ class ServersStorage implements StorageInterface public function __construct( public string $url, public string $description, - public array|stdClass|null $variables = null + public array|stdClass $variables = new stdClass(), ) { - $this->variables = $this->variables ?: new stdClass(); + + } + + public function addVariable(string $name, $description, $default = ''): static + { + $this->variables = $this->variables instanceof stdClass::class ? [] : $this->variables; + + $this->variables[$name] = ['default' => $default, 'description'=> $description]; + + return $this; } } From 60ec382d69ed3b8d7feff58bc8689accd90793a6 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Mon, 16 Jun 2025 15:15:27 +0800 Subject: [PATCH 16/43] add config --- src/OpenApi/Storage/OpenAPI/ServersStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/Storage/OpenAPI/ServersStorage.php b/src/OpenApi/Storage/OpenAPI/ServersStorage.php index 93cd827..cfab890 100755 --- a/src/OpenApi/Storage/OpenAPI/ServersStorage.php +++ b/src/OpenApi/Storage/OpenAPI/ServersStorage.php @@ -19,7 +19,7 @@ public function __construct( public function addVariable(string $name, $description, $default = ''): static { - $this->variables = $this->variables instanceof stdClass::class ? [] : $this->variables; + $this->variables = $this->variables instanceof stdClass ? [] : $this->variables; $this->variables[$name] = ['default' => $default, 'description'=> $description]; From f84b001f8061d2e31b01eb1356dd9c6e3a70fdfb Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Wed, 18 Jun 2025 10:14:20 +0800 Subject: [PATCH 17/43] add openapi Optional values by enums --- composer.json | 6 +- composer.lock | 4178 ++++++++--------- docs/zh/README.md | 52 + src/OpenApi/Annotations/RequestBody.php | 2 +- src/OpenApi/Bin/openapi | 10 + src/OpenApi/Collections/OpenApiCollection.php | 4 +- src/OpenApi/Enum/ParameterTypeEnum.php | 14 +- src/OpenApi/Handler/Handler.php | 15 - src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 27 +- src/OpenApi/Web/Index.php | 12 + 10 files changed, 2209 insertions(+), 2111 deletions(-) create mode 100644 src/OpenApi/Bin/openapi create mode 100644 src/OpenApi/Web/Index.php diff --git a/composer.json b/composer.json index 7a18e2b..a221a3d 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", @@ -38,6 +39,9 @@ "Astral\\Benchmarks\\": "benchmarks/" } }, + "bin": [ + "src/OpenApi/bin/art" + ], "config": { "allow-plugins": { "pestphp/pest-plugin": true diff --git a/composer.lock b/composer.lock index b453161..80ba5cc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a5a7da27a45194d7e04046a062ca1490", + "content-hash": "6a9a46d6b2d0f3759c5c4de4bcc10d39", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -1154,6 +1154,100 @@ }, "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", @@ -1222,28 +1316,27 @@ "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/polyfill-mbstring", + "name": "symfony/polyfill-ctype", "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "ext-iconv": "*", "php": ">=7.2" }, "provide": { - "ext-mbstring": "*" + "ext-ctype": "*" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-ctype": "For best performance" }, "type": "library", "extra": { @@ -1257,7 +1350,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1266,25 +1359,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "ctype", "polyfill", - "portable", - "shim" + "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -1300,25 +1392,28 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php80", + "name": "symfony/polyfill-intl-grapheme", "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "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": { @@ -1331,21 +1426,14 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -1355,16 +1443,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "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-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -1380,66 +1470,44 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/translation", - "version": "v6.4.22", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/7e3b3b7146c6fab36ddff304a8041174bf6e17ad", - "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "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" + "php": ">=7.2" }, - "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" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { "files": [ - "Resources/functions.php" + "bootstrap.php" ], "psr-4": { - "Symfony\\Component\\Translation\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1448,18 +1516,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to internationalize your application", + "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/translation/tree/v6.4.22" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -1475,42 +1551,46 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:06:44+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/translation-contracts", - "version": "v3.6.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=8.1" + "ext-iconv": "*", + "php": ">=7.2" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" + "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\\Contracts\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1526,18 +1606,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to translation", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -1553,36 +1632,42 @@ "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { - "name": "voku/portable-ascii", - "version": "2.0.3", + "name": "symfony/polyfill-php80", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/voku/portable-ascii.git", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "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" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "voku\\": "src/voku/" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1590,80 +1675,84 @@ ], "authors": [ { - "name": "Lars Moelleken", - "homepage": "https://www.moelleken.org/" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", - "homepage": "https://github.com/voku/portable-ascii", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "ascii", - "clean", - "php" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { - "url": "https://www.paypal.me/moelleken", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/voku", + "url": "https://github.com/fabpot", "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", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-21T01:49:47+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { - "name": "webmozart/assert", - "version": "1.11.0", + "name": "symfony/service-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "1.10-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1671,78 +1760,85 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Assertions to validate method input/output with nice error messages.", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "assert", - "check", - "validate" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, - "time": "2022-06-03T18:03:27+00:00" - } - ], - "packages-dev": [ + "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": "brianium/paratest", - "version": "v7.3.1", + "name": "symfony/string", + "version": "v6.4.21", "source": { "type": "git", - "url": "https://github.com/paratestphp/paratest.git", - "reference": "551f46f52a93177d873f3be08a1649ae886b4a30" + "url": "https://github.com/symfony/string.git", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30", - "reference": "551f46f52a93177d873f3be08a1649ae886b4a30", + "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", "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" + "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": { - "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" + "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" }, - "bin": [ - "bin/paratest", - "bin/paratest.bat", - "bin/paratest_for_phpstorm" - ], "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "ParaTest\\": [ - "src/" - ] - } + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1750,67 +1846,102 @@ ], "authors": [ { - "name": "Brian Scaturro", - "email": "scaturrob@gmail.com", - "role": "Developer" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Filippo Tessarotto", - "email": "zoeslam@gmail.com", - "role": "Developer" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Parallel testing for PHP", - "homepage": "https://github.com/paratestphp/paratest", + "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": [ - "concurrent", - "parallel", - "phpunit", - "testing" + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" ], "support": { - "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.3.1" + "source": "https://github.com/symfony/string/tree/v6.4.21" }, "funding": [ { - "url": "https://github.com/sponsors/Slamdunk", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://paypal.me/filippotessarotto", - "type": "paypal" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-10-31T09:24:17+00:00" + "time": "2025-04-18T15:23:29+00:00" }, { - "name": "clue/ndjson-react", - "version": "v1.3.0", + "name": "symfony/translation", + "version": "v6.4.22", "source": { "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + "url": "https://github.com/symfony/translation.git", + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "url": "https://api.github.com/repos/symfony/translation/zipball/7e3b3b7146c6fab36ddff304a8041174bf6e17ad", + "reference": "7e3b3b7146c6fab36ddff304a8041174bf6e17ad", "shasum": "" }, "require": { - "php": ">=5.3", - "react/stream": "^1.2" + "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": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" + "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": { - "Clue\\React\\NDJson\\": "src/" - } + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1818,76 +1949,69 @@ ], "authors": [ { - "name": "Christian Lück", - "email": "christian@clue.engineering" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "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" - ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + "source": "https://github.com/symfony/translation/tree/v6.4.22" }, "funding": [ { - "url": "https://clue.engineering/support", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/clue", + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2022-12-23T10:58:28+00:00" + "time": "2025-05-29T07:06:44+00:00" }, { - "name": "composer/pcre", - "version": "3.3.2", + "name": "symfony/translation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "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" + "php": ">=8.1" }, "type": "library", "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.x-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { - "Composer\\Pcre\\": "src" - } + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1895,68 +2019,70 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { - "url": "https://packagist.com", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/composer", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { - "name": "composer/semver", - "version": "3.4.3", + "name": "voku/portable-ascii", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": ">=7.0.0" }, "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" }, + "type": "library", "autoload": { "psr-4": { - "Composer\\Semver\\": "src" + "voku\\": "src/voku/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1965,77 +2091,79 @@ ], "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" + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "ascii", + "clean", + "php" ], "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" + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { - "url": "https://packagist.com", + "url": "https://www.paypal.me/moelleken", "type": "custom" }, { - "url": "https://github.com/composer", + "url": "https://github.com/voku", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "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-09-19T14:15:21+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { - "name": "composer/xdebug-handler", - "version": "3.0.5", + "name": "webmozart/assert", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { - "Composer\\XdebugHandler\\": "src" + "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2044,71 +2172,77 @@ ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Restarts a process without Xdebug.", + "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ - "Xdebug", - "performance" + "assert", + "check", + "validate" ], "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" + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "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" - }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ { - "name": "doctrine/annotations", - "version": "2.0.2", + "name": "brianium/paratest", + "version": "v7.3.1", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" + "url": "https://github.com/paratestphp/paratest.git", + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30", + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30", "shasum": "" }, "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" + "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/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" + "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": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "ParaTest\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -2117,67 +2251,66 @@ ], "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": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" } ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", "keywords": [ - "annotations", - "docblock", - "parser" + "concurrent", + "parallel", + "phpunit", + "testing" ], "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.2" + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.3.1" }, - "time": "2024-09-05T10:17:24+00:00" + "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": "doctrine/lexer", - "version": "3.0.1", + "name": "clue/ndjson-react", + "version": "v1.3.0", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", "shasum": "" }, "require": { - "php": "^8.1" + "php": ">=5.3", + "react/stream": "^1.2" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.21" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" + "Clue\\React\\NDJson\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2186,71 +2319,75 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Christian Lück", + "email": "christian@clue.engineering" } ], - "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", + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" ], "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.1" + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://clue.engineering/support", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" + "url": "https://github.com/clue", + "type": "github" } ], - "time": "2024-02-05T11:56:58+00:00" + "time": "2022-12-23T10:58:28+00:00" }, { - "name": "evenement/evenement", - "version": "v3.0.2", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "php": ">=7.0" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpunit/phpunit": "^9 || ^6" + "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": { - "Evenement\\": "src/" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2259,53 +2396,68 @@ ], "authors": [ { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Événement is a very simple event dispatching library for PHP", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "event-dispatcher", - "event-emitter" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" }, - "time": "2023-08-08T05:53:35+00:00" + "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": "fidry/cpu-core-counter", - "version": "1.2.0", + "name": "composer/semver", + "version": "3.4.3", "source": { "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^5.3.2 || ^7.0 || ^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" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2314,63 +2466,77 @@ ], "authors": [ { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" + "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": "Tiny utility to get the number of CPU cores.", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "CPU", - "core" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "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://github.com/theofidry", + "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-08-06T10:04:20+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { - "name": "filp/whoops", - "version": "2.18.2", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3", - "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "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" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, "autoload": { "psr-4": { - "Whoops\\": "src/Whoops/" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2379,103 +2545,72 @@ ], "authors": [ { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", + "description": "Restarts a process without Xdebug.", "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" + "Xdebug", + "performance" ], "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.2" + "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://github.com/denis-sokolov", + "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": "2025-06-11T20:42:19+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.75.0", + "name": "doctrine/annotations", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" + "url": "https://github.com/doctrine/annotations.git", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", - "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", "shasum": "" }, "require": { - "clue/ndjson-react": "^1.0", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", - "ext-filter": "*", - "ext-hash": "*", - "ext-json": "*", + "doctrine/lexer": "^2 || ^3", "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" + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" }, "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" + "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": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", + "type": "library", "autoload": { "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/Fixer/Internal/*" - ] + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2483,119 +2618,140 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" }, { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@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": "A tool to automatically fix PHP code style", + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" + "annotations", + "docblock", + "parser" ], "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" + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2025-03-31T18:40:42+00:00" + "time": "2024-09-05T10:17:24+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "name": "doctrine/lexer", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "php": "^7.4|^8.0" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" + "php": "^8.1" }, "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" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, "autoload": { - "classmap": [ - "hamcrest" - ] + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "This is the PHP port of Hamcrest Matchers", + "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": [ - "test" + "annotations", + "docblock", + "lexer", + "parser", + "php" ], "support": { - "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, - "time": "2025-04-30T06:54:44+00:00" + "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": "jean85/pretty-package-versions", - "version": "2.1.1", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "composer-runtime-api": "^2.1.0", - "php": "^7.4|^8.0" + "php": ">=7.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" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Jean85\\": "src/" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2604,81 +2760,426 @@ ], "authors": [ { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "A library to get pretty versions strings of installed dependencies", + "description": "Événement is a very simple event dispatching library for PHP", "keywords": [ - "composer", - "package", - "release", - "versions" + "event-dispatcher", + "event-emitter" ], "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2025-03-19T14:43:43+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { - "name": "mockery/mockery", - "version": "1.6.12", + "name": "fidry/cpu-core-counter", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", - "php": ">=7.3" - }, - "conflict": { - "phpunit/phpunit": "<8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "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": { - "files": [ - "library/helpers.php", - "library/Mockery.php" - ], "psr-4": { - "Mockery\\": "library/Mockery" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "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" + "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", @@ -3881,317 +4382,93 @@ "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" + "sebastian/version": "^4.0.1" }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" } }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "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/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Async, Promise-based cache interface for ReactPHP", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "cache", - "caching", - "promise", - "reactphp" + "phpunit", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" + "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://opencollective.com/reactphp", - "type": "open_collective" + "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": "2022-11-30T15:59:55+00:00" + "time": "2024-10-08T15:36:51+00:00" }, { - "name": "react/child-process", - "version": "v0.6.6", + "name": "psr/cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "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" + "php": ">=8.0.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "React\\ChildProcess\\": "src/" + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4200,73 +4477,47 @@ ], "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/" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Event-driven library for executing child processes with ReactPHP.", + "description": "Common interface for caching libraries", "keywords": [ - "event-driven", - "process", - "reactphp" + "cache", + "psr", + "psr-6" ], "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-01-01T16:37:48+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "react/dns", - "version": "v1.13.0", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "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" + "php": ">=7.2.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "React\\Dns\\": "src/" + "Psr\\EventDispatcher\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4275,145 +4526,97 @@ ], "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/" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Async DNS resolver for ReactPHP", + "description": "Standard interfaces for event handling.", "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" + "events", + "psr", + "psr-14" ], "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-13T14:18:03+00:00" + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "react/event-loop", - "version": "v1.5.0", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "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" + "php": ">=8.0.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { - "React\\EventLoop\\": "src/" + "Psr\\Log\\": "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/" - }, + ], + "authors": [ { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "asynchronous", - "event-loop" + "log", + "psr", + "psr-3" ], "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2023-11-13T13:48:05+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "react/promise", - "version": "v3.2.0", + "name": "react/cache", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "php": ">=7.1.0" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { - "React\\Promise\\": "src/" + "React\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4421,11 +4624,6 @@ "MIT" ], "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, { "name": "Christian Lück", "email": "christian@clue.engineering", @@ -4436,20 +4634,27 @@ "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": "A lightweight implementation of CommonJS Promises/A for PHP", + "description": "Async, Promise-based cache interface for ReactPHP", "keywords": [ + "cache", + "caching", "promise", - "promises" + "reactphp" ], "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" }, "funding": [ { @@ -4457,40 +4662,37 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2022-11-30T15:59:55+00:00" }, { - "name": "react/socket", - "version": "v1.16.0", + "name": "react/child-process", + "version": "v0.6.6", "source": { "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "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/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" + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, "type": "library", "autoload": { "psr-4": { - "React\\Socket\\": "src/" + "React\\ChildProcess\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4519,17 +4721,15 @@ "homepage": "https://cboden.dev/" } ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "description": "Event-driven library for executing child processes with ReactPHP.", "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" + "event-driven", + "process", + "reactphp" ], "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" }, "funding": [ { @@ -4537,35 +4737,37 @@ "type": "open_collective" } ], - "time": "2024-07-26T10:38:09+00:00" + "time": "2025-01-01T16:37:48+00:00" }, { - "name": "react/stream", - "version": "v1.4.0", + "name": "react/dns", + "version": "v1.13.0", "source": { "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" + "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": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + "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\\Stream\\": "src/" + "React\\Dns\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4594,411 +4796,340 @@ "homepage": "https://cboden.dev/" } ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "description": "Async DNS resolver for 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" - } + "async", + "dns", + "dns-resolver", + "reactphp" ], - "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" + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2024-06-13T14:18:03+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "name": "react/event-loop", + "version": "v1.5.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\EventLoop\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "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": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2023-11-13T13:48:05+00:00" }, { - "name": "sebastian/comparator", - "version": "5.0.3", + "name": "react/promise", + "version": "v3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" }, { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Volker Dusch", - "email": "github@wallbash.com" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" }, { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "comparator", - "compare", - "equality" + "promise", + "promises" ], "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" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2024-05-24T10:39:05+00:00" }, { - "name": "sebastian/complexity", - "version": "3.2.0", + "name": "react/socket", + "version": "v1.16.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "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": "^10.0" + "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", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Socket\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "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": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "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/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2024-07-26T10:38:09+00:00" }, { - "name": "sebastian/diff", - "version": "5.1.1", + "name": "react/stream", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "php": ">=8.1" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Stream\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "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": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" ], "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" + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2024-06-11T12:45:25+00:00" }, { - "name": "sebastian/environment", - "version": "6.1.0", + "name": "sebastian/cli-parser", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { @@ -5007,13 +5138,10 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "suggest": { - "ext-posix": "*" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -5028,20 +5156,16 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "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" + "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": [ { @@ -5049,26 +5173,24 @@ "type": "github" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { - "name": "sebastian/exporter", - "version": "5.1.2", + "name": "sebastian/code-unit", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^10.0" @@ -5076,7 +5198,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -5091,35 +5213,15 @@ "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" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "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" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -5127,35 +5229,32 @@ "type": "github" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { - "name": "sebastian/global-state", - "version": "6.0.2", + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.1" }, "require-dev": { - "ext-dom": "*", "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -5173,15 +5272,11 @@ "email": "sebastian@phpunit.de" } ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], + "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/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -5189,33 +5284,36 @@ "type": "github" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "2.0.2", + "name": "sebastian/comparator", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -5230,16 +5328,32 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "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": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "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/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" + "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": [ { @@ -5247,26 +5361,25 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "5.0.0", + "name": "sebastian/complexity", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^10.0" @@ -5274,7 +5387,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -5289,14 +5402,16 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "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": [ { @@ -5304,32 +5419,33 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { - "name": "sebastian/object-reflector", - "version": "3.0.0", + "name": "sebastian/diff", + "version": "5.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -5345,13 +5461,24 @@ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "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": [ { @@ -5359,20 +5486,20 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { - "name": "sebastian/recursion-context", - "version": "5.0.0", + "name": "sebastian/environment", + "version": "6.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { @@ -5381,10 +5508,13 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, + "suggest": { + "ext-posix": "*" + }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -5400,21 +5530,19 @@ { "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", + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "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": [ { @@ -5422,24 +5550,26 @@ "type": "github" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { - "name": "sebastian/type", - "version": "4.0.0", + "name": "sebastian/exporter", + "version": "5.1.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", "shasum": "" }, "require": { - "php": ">=8.1" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "phpunit/phpunit": "^10.0" @@ -5447,7 +5577,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -5462,15 +5592,35 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "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": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "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/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "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": [ { @@ -5478,29 +5628,35 @@ "type": "github" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2024-03-02T07:17:12+00:00" }, { - "name": "sebastian/version", - "version": "4.0.1", + "name": "sebastian/global-state", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=8.1" + "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": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -5515,15 +5671,18 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "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": [ { @@ -5531,478 +5690,379 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { - "name": "seld/jsonlint", - "version": "1.11.0", + "name": "sebastian/lines-of-code", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", - "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0 || ^8.0" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + "phpunit/phpunit": "^10.0" }, - "bin": [ - "bin/jsonlint" - ], "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], + "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/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + "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/Seldaek", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" } ], - "time": "2024-07-11T14:55:45+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { - "name": "symfony/console", - "version": "v6.4.22", + "name": "sebastian/object-enumerator", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", - "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "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" + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.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" + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { - "source": "https://github.com/symfony/console/tree/v6.4.22" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2025-05-07T07:05:04+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v6.4.13", + "name": "sebastian/object-reflector", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "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" + "php": ">=8.1" }, "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" + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "name": "sebastian/recursion-context", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2023-02-03T07:05:40+00:00" }, { - "name": "symfony/filesystem", - "version": "v6.4.13", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "php": ">=8.1" }, "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.13" + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { - "name": "symfony/finder", - "version": "v6.4.17", + "name": "sebastian/version", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { "php": ">=8.1" }, - "require-dev": { - "symfony/filesystem": "^6.0|^7.0" - }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.17" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-12-29T13:51:37+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "symfony/options-resolver", - "version": "v6.4.16", + "name": "seld/jsonlint", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "368128ad168f20e22c32159b9f761e456cec0c78" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", - "reference": "368128ad168f20e22c32159b9f761e456cec0c78", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "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": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6010,77 +6070,78 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", + "description": "JSON Linter", "keywords": [ - "config", - "configuration", - "options" + "json", + "linter", + "parser", + "validator" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.16" + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/Seldaek", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", "type": "tidelift" } ], - "time": "2024-11-20T10:57:02+00:00" + "time": "2024-07-11T14:55:45+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "name": "symfony/event-dispatcher", + "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { - "ext-ctype": "*" + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" }, - "suggest": { - "ext-ctype": "For best performance" + "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", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6088,24 +6149,18 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -6121,41 +6176,39 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + "Symfony\\Contracts\\EventDispatcher\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -6172,18 +6225,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Generic abstractions related to dispatching event", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -6199,44 +6252,37 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "name": "symfony/filesystem", + "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "url": "https://github.com/symfony/filesystem.git", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, - "suggest": { - "ext-intl": "For best performance" + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Component\\Filesystem\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6245,26 +6291,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -6280,41 +6318,35 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "name": "symfony/finder", + "version": "v6.4.17", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/finder.git", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Component\\Finder\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6323,24 +6355,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/finder/tree/v6.4.17" }, "funding": [ { @@ -6356,29 +6382,30 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-29T13:51:37+00:00" }, { - "name": "symfony/process", - "version": "v6.4.20", + "name": "symfony/options-resolver", + "version": "v6.4.16", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "368128ad168f20e22c32159b9f761e456cec0c78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", + "reference": "368128ad168f20e22c32159b9f761e456cec0c78", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6398,10 +6425,15 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "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/process/tree/v6.4.20" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.16" }, "funding": [ { @@ -6417,46 +6449,41 @@ "type": "tidelift" } ], - "time": "2025-03-10T17:11:00+00:00" + "time": "2024-11-20T10:57:02+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.6.0", + "name": "symfony/polyfill-php81", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, - "exclude-from-classmap": [ - "/Test/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6473,18 +6500,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { @@ -6500,30 +6525,29 @@ "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/stopwatch", - "version": "v6.4.19", + "name": "symfony/process", + "version": "v6.4.20", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c" + "url": "https://github.com/symfony/process.git", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", - "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", + "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/service-contracts": "^2.5|^3" + "php": ">=8.1" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6543,10 +6567,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a way to profile code", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.4.19" + "source": "https://github.com/symfony/process/tree/v6.4.20" }, "funding": [ { @@ -6562,46 +6586,30 @@ "type": "tidelift" } ], - "time": "2025-02-21T10:06:30+00:00" + "time": "2025-03-10T17:11:00+00:00" }, { - "name": "symfony/string", - "version": "v6.4.21", + "name": "symfony/stopwatch", + "version": "v6.4.19", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", - "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", + "reference": "dfe1481c12c06266d0c3d58c0cb4b09bd497ab9c", "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" + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\String\\": "" + "Symfony\\Component\\Stopwatch\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6613,26 +6621,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.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", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.21" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.19" }, "funding": [ { @@ -6648,7 +6648,7 @@ "type": "tidelift" } ], - "time": "2025-04-18T15:23:29+00:00" + "time": "2025-02-21T10:06:30+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", diff --git a/docs/zh/README.md b/docs/zh/README.md index cadea6d..60576c3 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -95,6 +95,58 @@ class User extends Serialize { } ``` +## 生成openapi文档 + +#### 创建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(); + } +} +``` + ## DTO 转换 ### 类型转换 diff --git a/src/OpenApi/Annotations/RequestBody.php b/src/OpenApi/Annotations/RequestBody.php index 9323f6d..89e7ea4 100755 --- a/src/OpenApi/Annotations/RequestBody.php +++ b/src/OpenApi/Annotations/RequestBody.php @@ -14,7 +14,7 @@ public function __construct( /** @var class-string|string $className */ public string $className = '', public ContentTypeEnum $contentType = ContentTypeEnum::JSON, - public array|null $group = null + public array|null $groups = null ){ } } diff --git a/src/OpenApi/Bin/openapi b/src/OpenApi/Bin/openapi new file mode 100644 index 0000000..faf8312 --- /dev/null +++ b/src/OpenApi/Bin/openapi @@ -0,0 +1,10 @@ +#!/usr/bin/env php +run(); \ No newline at end of file diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 48633a8..046a0b3 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -60,7 +60,7 @@ public function build() : Method public function buildRequestBodyByAttribute(): RequestBodyStorage { $openAPIRequestBody = new RequestBodyStorage($this->requestBody->contentType); - $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($this->requestBody->className,$this->requestBody->group),$n); + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($this->requestBody->className,$this->requestBody->groups)); $openAPIRequestBody->withParameter($schemaStorage); return $openAPIRequestBody; } @@ -75,7 +75,7 @@ public function buildRequestBodyByParameters(): RequestBodyStorage $type = $methodParam?->getType(); $requestBodyClass = $type instanceof ReflectionNamedType ? $type->getName() : ''; if (is_subclass_of($requestBodyClass, Serialize::class)) { - $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($requestBodyClass),$node); + $schemaStorage = (new SchemaStorage())->build($this->buildParameterCollections($requestBodyClass)); $openAPIRequestBody->withParameter($schemaStorage); } diff --git a/src/OpenApi/Enum/ParameterTypeEnum.php b/src/OpenApi/Enum/ParameterTypeEnum.php index c9cb3d4..a5a06bb 100755 --- a/src/OpenApi/Enum/ParameterTypeEnum.php +++ b/src/OpenApi/Enum/ParameterTypeEnum.php @@ -17,6 +17,7 @@ enum ParameterTypeEnum: string case ANY_OF = 'anyOf'; case ALL_OF = 'allOf'; + public function isObject(): bool { return $this === self::OBJECT; @@ -35,7 +36,7 @@ public function isOf(): bool public static function getBaseEnumByTypeKindEnum(TypeCollection $collection): ?ParameterTypeEnum { return match (true){ - $collection->kind === TypeKindEnum::STRING => self::STRING, + $collection->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, @@ -65,6 +66,17 @@ public static function getArrayAndObjectEnumBy(array $types, string $className): 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 */ diff --git a/src/OpenApi/Handler/Handler.php b/src/OpenApi/Handler/Handler.php index d08e7cf..c6f506d 100755 --- a/src/OpenApi/Handler/Handler.php +++ b/src/OpenApi/Handler/Handler.php @@ -33,21 +33,6 @@ public function __construct( } } - public function rootPath(): string - { - return dirname(__DIR__, 3); - } - - public function config() - { - $path = $this->rootPath().'/.openapi.php'; - if(is_file($path)){ - return include $path; - } - - return include dirname(__DIR__, 3).'/.openapi.php'; - } - /** * 构建OpenApi结构文档 * diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php index f5e0ab8..29002e4 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -4,6 +4,7 @@ namespace Astral\Serialize\OpenApi\Storage\OpenAPI; +use Astral\Serialize\Enums\TypeKindEnum; use Astral\Serialize\OpenApi\Collections\ParameterCollection; use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Astral\Serialize\OpenApi\Storage\StorageInterface; @@ -77,7 +78,7 @@ private function buildBasicPropertySchema(ParameterCollection $parameter, array $currentNode['properties'][$propertyName] = [ 'type' => $parameter->type->value, - 'description' => $parameter->descriptions, + 'description' => $this->getDescriptions($parameter), 'example' => $parameter->example, ]; @@ -144,7 +145,7 @@ private function buildNestedProperties(ParameterCollection $topParameter, array $currentNode['properties'][$propertyName] = [ 'type' => 'object', 'properties' => [], - 'description' => $topParameter->descriptions, + 'description' => $this->getDescriptions($topParameter), ]; $nestedNode = &$currentNode['properties'][$propertyName]; } @@ -156,4 +157,26 @@ private function buildNestedProperties(ParameterCollection $topParameter, array } } } + + public function getDescriptions(ParameterCollection $parameter):string + { + if(!ParameterTypeEnum::hasEnum($parameter->types)){ + return $parameter->descriptions; + } + + $descriptions = $parameter->descriptions; + + $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; + } + } + } + + $descriptions .= ' Optional values: :' . implode('、', $names); + + return $descriptions; + } } \ No newline at end of file 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 From 613aa9d5da331182daf0b6dfa4e19bf06f6b3a6c Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Wed, 18 Jun 2025 17:05:35 +0800 Subject: [PATCH 18/43] fix tests --- composer.json | 3 - src/OpenApi/Enum/ParameterTypeEnum.php | 20 +++- src/OpenApi/Handler/Config.php | 2 +- src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 8 +- tests/Openapi/EnumOpenApiTest.php | 99 +++++++++++++++++++ .../Openapi/{OpenApi.php => OpenApiTest.php} | 2 +- ...enApi.php => RouteOrewriteOpenApiTest.php} | 2 +- 7 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 tests/Openapi/EnumOpenApiTest.php rename tests/Openapi/{OpenApi.php => OpenApiTest.php} (97%) rename tests/Openapi/{RouteOrewriteOpenApi.php => RouteOrewriteOpenApiTest.php} (94%) diff --git a/composer.json b/composer.json index a221a3d..54983e1 100755 --- a/composer.json +++ b/composer.json @@ -39,9 +39,6 @@ "Astral\\Benchmarks\\": "benchmarks/" } }, - "bin": [ - "src/OpenApi/bin/art" - ], "config": { "allow-plugins": { "pestphp/pest-plugin": true diff --git a/src/OpenApi/Enum/ParameterTypeEnum.php b/src/OpenApi/Enum/ParameterTypeEnum.php index a5a06bb..29bf76a 100755 --- a/src/OpenApi/Enum/ParameterTypeEnum.php +++ b/src/OpenApi/Enum/ParameterTypeEnum.php @@ -98,14 +98,32 @@ public static function getByTypes(array $types): ParameterTypeEnum } $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; } } - return $hasUnion ? self::ANY_OF : self::ONE_OF; + 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/Handler/Config.php b/src/OpenApi/Handler/Config.php index 802826c..c4a21e5 100755 --- a/src/OpenApi/Handler/Config.php +++ b/src/OpenApi/Handler/Config.php @@ -15,7 +15,7 @@ class Config { - public static array $config; + public static ?array $config = null; public static function rootPath(): string { diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php index 29002e4..e0868d0 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -98,10 +98,12 @@ public function buildOfProperties(ParameterCollection $topParameter, array &$cur $node = &$currentNode['properties'][$propertyName][$topParameter->type->value]; $i = 0; + $addedTypes = []; foreach ($topParameter->types as $kindType){ $type = ParameterTypeEnum::getBaseEnumByTypeKindEnum($kindType); - if($type){ - $node[$i] = ['type'=> $type]; + if ($type && !in_array($type->value, $addedTypes, true)) { + $node[$i] = ['type' => $type->value]; + $addedTypes[] = $type->value; $i++; } } @@ -175,7 +177,7 @@ public function getDescriptions(ParameterCollection $parameter):string } } - $descriptions .= ' Optional values: :' . implode('、', $names); + $descriptions .= 'Optional values:' . implode('、', $names); return $descriptions; } diff --git a/tests/Openapi/EnumOpenApiTest.php b/tests/Openapi/EnumOpenApiTest.php new file mode 100644 index 0000000..7cc5a67 --- /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/OpenApi.php b/tests/Openapi/OpenApiTest.php similarity index 97% rename from tests/Openapi/OpenApi.php rename to tests/Openapi/OpenApiTest.php index e93ddaf..15c6db9 100644 --- a/tests/Openapi/OpenApi.php +++ b/tests/Openapi/OpenApiTest.php @@ -81,7 +81,7 @@ public function one(TestOpenApiRequest $request): TestOpenApiResponse // id 字段是 oneOf 并包含 string, integer, number $idOneOf = $schema['properties']['id']['oneOf']; - $types = array_map(static fn($item) => $item['type']->value, $idOneOf); + $types = array_map(static fn($item) => $item['type'], $idOneOf); expect($types)->toMatchArray(['string', 'integer', 'number']); // any_array 是 oneOf 并包含至少一个 array 类型 diff --git a/tests/Openapi/RouteOrewriteOpenApi.php b/tests/Openapi/RouteOrewriteOpenApiTest.php similarity index 94% rename from tests/Openapi/RouteOrewriteOpenApi.php rename to tests/Openapi/RouteOrewriteOpenApiTest.php index 4ad287f..cffae5a 100644 --- a/tests/Openapi/RouteOrewriteOpenApi.php +++ b/tests/Openapi/RouteOrewriteOpenApiTest.php @@ -32,7 +32,7 @@ public function one(TestCustomerRouteRequest $request): void }); -test('OpenAPI structure is correct', function () { +test('OpenAPI customer route', function () { $api = new OpenApi(); $api->buildByClass(TestCustomerRouteController::class); From 2e99b8483c6911856d5531e0fa559022132ee8eb Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 09:58:45 +0800 Subject: [PATCH 19/43] add openapiAnnotation --- src/OpenApi/Annotations/OpenApi.php | 6 +- src/OpenApi/Collections/OpenApiCollection.php | 14 +++- .../Collections/ParameterCollection.php | 20 ++--- src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 12 +-- src/Support/Collections/DataCollection.php | 2 + tests/Openapi/AnnotationOpenApiTest.php | 75 +++++++++++++++++++ tests/Openapi/EnumOpenApiTest.php | 10 +-- 7 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 tests/Openapi/AnnotationOpenApiTest.php diff --git a/src/OpenApi/Annotations/OpenApi.php b/src/OpenApi/Annotations/OpenApi.php index 20f77e2..d10c6a3 100755 --- a/src/OpenApi/Annotations/OpenApi.php +++ b/src/OpenApi/Annotations/OpenApi.php @@ -6,11 +6,13 @@ use Attribute; -#[Attribute(Attribute::TARGET_METHOD)] +#[Attribute(Attribute::TARGET_PROPERTY)] class OpenApi { + public function __construct( - public string $descriptions, + public string $description = '', + public string $example = '', ) { } } diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 046a0b3..d33e3c9 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -3,6 +3,7 @@ namespace Astral\Serialize\OpenApi\Collections; use Astral\Serialize\OpenApi\Annotations\Headers; +use Astral\Serialize\OpenApi\Annotations\OpenApi; use Astral\Serialize\OpenApi\Annotations\RequestBody; use Astral\Serialize\OpenApi\Annotations\Response; use Astral\Serialize\OpenApi\Annotations\Route; @@ -126,7 +127,7 @@ className: $className, name: current($property->getInputNamesByGroups($groups,$className)), types: $property->getTypes(), type: ParameterTypeEnum::getByTypes($property->getTypes()), - descriptions: '', + openApiAnnotation: $this->getOpenApiAnnotation($property->getAttributes()), required: !$property->isNullable(), ignore: false, ); @@ -143,4 +144,15 @@ className: $className, 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 index a6d3cc6..405c7b3 100755 --- a/src/OpenApi/Collections/ParameterCollection.php +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -4,25 +4,25 @@ namespace Astral\Serialize\OpenApi\Collections; -use Astral\Serialize\Enums\TypeKindEnum; +use Astral\Serialize\OpenApi\Annotations\OpenApi; use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Astral\Serialize\Support\Collections\TypeCollection; use Attribute; +use ReflectionAttribute; class ParameterCollection { public function __construct( - public string $className, - public string $name, + public string $className, + public string $name, /** @var TypeCollection[] $types */ - public array $types, - public ParameterTypeEnum $type, - public string $descriptions = '', - public mixed $example = '', - public bool $required = false, - public bool $ignore = false, + public array $types, + public ParameterTypeEnum $type, + public OpenApi|null $openApiAnnotation = null, + public bool $required = false, + public bool $ignore = false, /** @var array $children */ - public array $children = [], + public array $children = [], ){ } } diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php index e0868d0..6a792c1 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -5,6 +5,7 @@ namespace Astral\Serialize\OpenApi\Storage\OpenAPI; use Astral\Serialize\Enums\TypeKindEnum; +use Astral\Serialize\OpenApi\Annotations\OpenApi; use Astral\Serialize\OpenApi\Collections\ParameterCollection; use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Astral\Serialize\OpenApi\Storage\StorageInterface; @@ -79,7 +80,7 @@ private function buildBasicPropertySchema(ParameterCollection $parameter, array $currentNode['properties'][$propertyName] = [ 'type' => $parameter->type->value, 'description' => $this->getDescriptions($parameter), - 'example' => $parameter->example, + 'example' => $parameter->openApiAnnotation?->example, ]; // 添加必填字段标记 @@ -162,12 +163,11 @@ private function buildNestedProperties(ParameterCollection $topParameter, array public function getDescriptions(ParameterCollection $parameter):string { + $description = $parameter->openApiAnnotation?->description ?? ''; if(!ParameterTypeEnum::hasEnum($parameter->types)){ - return $parameter->descriptions; + return $description; } - $descriptions = $parameter->descriptions; - $names = []; foreach ($parameter->types as $type) { if (TypeKindEnum::ENUM === $type->kind && enum_exists($type->className)) { @@ -177,8 +177,8 @@ public function getDescriptions(ParameterCollection $parameter):string } } - $descriptions .= 'Optional values:' . implode('、', $names); + $description .= ($description ? ' ' : '').'optional values:' . implode('、', $names); - return $descriptions; + return $description; } } \ No newline at end of file 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 index 7cc5a67..706986a 100644 --- a/tests/Openapi/EnumOpenApiTest.php +++ b/tests/Openapi/EnumOpenApiTest.php @@ -25,6 +25,7 @@ enum OpenapiUnionEnum class OpenapiEnumRequest extends Serialize { public OpenapiEnum $test_enum; + public OpenapiEnum|OpenapiUnionEnum $test_string_enum; public OpenapiEnum|OpenapiUnionEnum|string $test_string_2_enum; @@ -41,7 +42,6 @@ class OpenapiEnumController{ public function one(OpenapiEnumRequest $request): void { } - } }); @@ -68,22 +68,22 @@ public function one(OpenapiEnumRequest $request): void ]) ->and($schema['properties']['test_enum'])->toMatchArray([ 'type' => 'string', - 'description' => 'Optional values:ENUM_1、ENUM_2', + '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', + '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', + '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', + 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', 'example' => '', ]) ->and($schema['properties']['test_one_of_enum']['oneOf'])->toBeArray()->toHaveCount(2) From 74d2d0d9bb8ce957006901c32f5800b9db708886 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 09:59:08 +0800 Subject: [PATCH 20/43] add openapiAnnotation --- src/OpenApi/Collections/ParameterCollection.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/OpenApi/Collections/ParameterCollection.php b/src/OpenApi/Collections/ParameterCollection.php index 405c7b3..2e2b901 100755 --- a/src/OpenApi/Collections/ParameterCollection.php +++ b/src/OpenApi/Collections/ParameterCollection.php @@ -7,8 +7,6 @@ use Astral\Serialize\OpenApi\Annotations\OpenApi; use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum; use Astral\Serialize\Support\Collections\TypeCollection; -use Attribute; -use ReflectionAttribute; class ParameterCollection { From 6265c712c1a75e52d14d466021ab3c4e5200f1f3 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 10:01:05 +0800 Subject: [PATCH 21/43] add openapiAnnotation --- src/OpenApi/Storage/OpenAPI/SchemaStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php index 6a792c1..ea2ff5a 100755 --- a/src/OpenApi/Storage/OpenAPI/SchemaStorage.php +++ b/src/OpenApi/Storage/OpenAPI/SchemaStorage.php @@ -163,7 +163,7 @@ private function buildNestedProperties(ParameterCollection $topParameter, array public function getDescriptions(ParameterCollection $parameter):string { - $description = $parameter->openApiAnnotation?->description ?? ''; + $description = $parameter->openApiAnnotation->description ?? ''; if(!ParameterTypeEnum::hasEnum($parameter->types)){ return $description; } From fbe8af054c58a8586f34b35a11bd88494076357a Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:23:32 +0800 Subject: [PATCH 22/43] test docs --- .gitbook.yaml | 7 ++ README.md | 2 +- docs/en/SUMMARY.md | 0 ...73\345\236\213\350\275\254\346\215\242.md" | 70 ++++++++++++++ ...53\351\200\237\345\274\200\345\247\213.md" | 94 +++++++++++++++++++ 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 .gitbook.yaml create mode 100644 docs/en/SUMMARY.md create mode 100644 "docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" create mode 100644 "docs/zh/\345\277\253\351\200\237\345\274\200\345\247\213.md" diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 0000000..27bb333 --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1,7 @@ +root: docs +structure: + langs: + - label: 简体中文 + path: zh + - label: English + path: en \ No newline at end of file diff --git a/README.md b/README.md index 34176d8..20a1a89 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # Languages - [Complete documen-English](./docs/en/README.md) -- [完整文档-中文](./docs/zh/README.md) +- [完整文档-中文](https://astrals-organization.gitbook.io/php-serialize) # php-serialize An advanced PHP serialization tool leveraging attributes for flexible object-to-array and JSON conversion. Supports property aliases, type conversions, and nested object handling. Ideal for APIs, data persistence, and configuration management. diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md new file mode 100644 index 0000000..e69de29 diff --git "a/docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" "b/docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" new file mode 100644 index 0000000..9fca977 --- /dev/null +++ "b/docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.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/\345\277\253\351\200\237\345\274\200\345\247\213.md" "b/docs/zh/\345\277\253\351\200\237\345\274\200\345\247\213.md" new file mode 100644 index 0000000..9a2108e --- /dev/null +++ "b/docs/zh/\345\277\253\351\200\237\345\274\200\345\247\213.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 From c1b25b96749b45d68a3330446716a1996a684c61 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:25:00 +0800 Subject: [PATCH 23/43] test docs --- .gitbook.yaml => docs/.gitbook.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .gitbook.yaml => docs/.gitbook.yaml (100%) diff --git a/.gitbook.yaml b/docs/.gitbook.yaml similarity index 100% rename from .gitbook.yaml rename to docs/.gitbook.yaml From 829ecb697bc7b599c1257520f3e32f4d621bafcd Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:27:33 +0800 Subject: [PATCH 24/43] test docs --- docs/en/SUMMARY.md | 8 ++++++++ docs/zh/SUMMARY.md | 5 +++++ .../zh/base-mapper.md | 0 .../zh/getting-started.md | 0 4 files changed, 13 insertions(+) create mode 100644 docs/zh/SUMMARY.md rename "docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" => docs/zh/base-mapper.md (100%) rename "docs/zh/\345\277\253\351\200\237\345\274\200\345\247\213.md" => docs/zh/getting-started.md (100%) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index e69de29..35d38d0 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -0,0 +1,8 @@ +# Summary + +* [介绍](README.md) +* [快速开始](getting-started.md) +* [进阶使用](advanced/index.md) + * [配置](advanced/config.md) + * [插件](advanced/plugins.md) +* [常见问题](faq.md) \ No newline at end of file diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md new file mode 100644 index 0000000..0673a8d --- /dev/null +++ b/docs/zh/SUMMARY.md @@ -0,0 +1,5 @@ +# Summary + +* [介绍](README.md) +* [快速开始](getting-started.md) +* [进阶使用](base-mapper.md) \ No newline at end of file diff --git "a/docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" b/docs/zh/base-mapper.md similarity index 100% rename from "docs/zh/DTO \350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" rename to docs/zh/base-mapper.md diff --git "a/docs/zh/\345\277\253\351\200\237\345\274\200\345\247\213.md" b/docs/zh/getting-started.md similarity index 100% rename from "docs/zh/\345\277\253\351\200\237\345\274\200\345\247\213.md" rename to docs/zh/getting-started.md From 0d0d785e1da612ef48e07f14e9009871019863e7 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:31:18 +0800 Subject: [PATCH 25/43] test docs --- docs/.gitbook.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.gitbook.yaml b/docs/.gitbook.yaml index 27bb333..a5d997c 100644 --- a/docs/.gitbook.yaml +++ b/docs/.gitbook.yaml @@ -1,4 +1,4 @@ -root: docs +root: ./ structure: langs: - label: 简体中文 From 05aa13f75c4d0bf7b5e6301b38673c767f33124e Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:53:03 +0800 Subject: [PATCH 26/43] add config --- docs/en/README.md | 1377 -------------------------------------------- docs/en/SUMMARY.md | 8 - 2 files changed, 1385 deletions(-) delete mode 100644 docs/en/README.md delete mode 100644 docs/en/SUMMARY.md diff --git a/docs/en/README.md b/docs/en/README.md deleted file mode 100644 index 4fe1acb..0000000 --- a/docs/en/README.md +++ /dev/null @@ -1,1377 +0,0 @@ -# Astral Serialize Documentation - -## Quick Start - -### Installation - -Install using Composer: - -```bash -composer require astral/serialize -``` - -### Basic Usage - -```php -use Astral\Serialize\Serialize; - -class User extends Serialize { - public string $name, - public int $age -} - -// Create object from array -$user = User::from([ - 'name' => 'John Doe', - 'age' => 30 -]); - -// Access object properties -echo $user->name; // Output: John Doe -echo $user->age; // Output: 30 - -// Convert to array -$userArray = $user->toArray(); -// $userArray contents: -// [ -// 'name' => 'John Doe', -// 'age' => 30 -// ] -``` - -#### Other Features - -1. **Immutability**: Read-only properties cannot be modified after construction - -```php -use Astral\Serialize\Serialize; - -class User extends Serialize { - public function __construct( - public readonly string $name, - public readonly int $age - ) {} -} - -$user = User::from([ - 'name' => 'John Doe', - 'age' => 30 -]); - -try { - $user->name = 'Jane Doe'; // Compile-time error: cannot modify read-only property -} catch (Error $e) { - echo "Read-only properties cannot be reassigned"; -} -``` - -2. **Type-Safe Initialization** - -```php -$user = User::from([ - 'name' => 123, // Integer will be converted to string - 'age' => '35' // String will be converted to integer -]); - -echo $user->name; // Output: "123" -echo $user->age; // Output: 35 -``` - -3. **Constructor Initialization** - -```php -use Astral\Serialize\Serialize; - -class User extends Serialize { - public function __construct( - public readonly string $name, - public readonly int $age - ) { - // Can add additional validation or processing logic in the constructor - if (strlen($name) < 2) { - throw new \InvalidArgumentException('Name is too short'); - } - } -} -``` - -## DTO Conversion - -### Type Conversion - -#### Basic Type Conversion - -##### Method One: Constructor Property Promotion - -```php -use Astral\Serialize\Serialize; - -class Profile extends Serialize { - public function __construct( - public string $username, - public int $score, - public float $balance, - public bool $isActive - ) {} -} -``` - -##### Method Two: Traditional Property Definition - -```php -use Astral\Serialize\Serialize; - -class Profile extends Serialize { - public string $username; - public int $score; - public float $balance; - public bool $isActive; -} - -// Both methods support the same type conversion -$profile = Profile::from([ - 'username' => 123, // Integer converted to string - 'score' => '100', // String converted to integer - 'balance' => '99.99', // String converted to float - 'isActive' => 1 // Number converted to boolean -]); - -// Convert to array -$profileArray = $profile->toArray(); -``` - -##### Method Three: Read-Only Properties - -```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; - - // Manual initialization - public function __construct( - string $username, - int $score, - float $balance, - bool $isActive - ) { - $this->username = $username; - $this->score = $score; - $this->balance = $balance; - $this->isActive = $isActive; - } -} -``` - -Regardless of the method used, the `Serialize` class will work normally and provide the same type conversion and serialization functionality. - -#### Enum Conversion - -Enum conversion provides a powerful and flexible enum handling mechanism, supporting multiple enum types and conversion scenarios. - -- Supports enums with `tryFrom()` and `cases()` methods -- Automatically converts strings to enum instances during input -- Automatically converts enums to strings (enum names) during output -- Provides flexible and safe enum handling mechanisms - -##### Regular Enum - -```php -enum UserRole { - case ADMIN; - case EDITOR; - case VIEWER; -} - -class ComplexUser extends Serialize { - - public UserRole $role; - - // Supports multiple enum types - public UserStatus|UserRole $mixedStatus; -} - -$complexUser = ComplexUser::from([ - 'role' => 'ADMIN', // Automatically converted to UserRole::ADMIN - 'mixedStatus' => 'ACTIVE' // Can be UserStatus or UserRole -]); - -echo $complexUser->role; // Returns UserRole enum instance - -$complexUserArray = $complexUser->toArray(); -// $complexUserArray contents: -// [ -// 'role' => 'ADMIN', -// 'mixedStatus' => 'ACTIVE' -// ] -``` - -##### Backed Enum - -```php -use Astral\Serialize\Serialize; - -// BackedEnum -enum UserStatus: string { - case ACTIVE = 'active'; - case INACTIVE = 'inactive'; - case SUSPENDED = 'suspended'; -} - -// Define a user class with enum -class User extends Serialize { - public string $name; - - // Supports UnitEnum and BackedEnum - public UserStatus $status; - - // Supports multiple enum types - public UserStatus|string $alternateStatus; -} - -// Create user object -$user = User::from([ - 'name' => 'John Doe', - 'status' => 'active', // Automatically converted to UserStatus::ACTIVE - 'alternateStatus' => 'inactive' // Supports string or enum values -]); - -var_dump($user->status); // Output: UserStatus::ACTIVE - -// Convert to array -$userArray = $user->toArray(); -// $userArray contents: -// [ -// 'name' => 'John Doe', -// 'status' => 'ACTIVE', // Output enum name -// 'alternateStatus' => 'INACTIVE' -// ] -``` - -#### Null Value Conversion Rules Detailed Example - -When the property is not a nullable type (`?type`), `null` values will be automatically converted based on the target type: - -```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 value conversion example -$profile = NullConversionProfile::from([ - 'username' => null, // Converted to empty string '' - 'score' => null, // Converted to 0 - 'balance' => null, // Converted to 0.0 - 'tags' => null, // Converted to empty array [] - 'metadata' => null // Converted to empty object new stdClass() -]); - -// Verify conversion results -echo $profile->username; // Output: ""(empty string) -echo $profile->score; // Output: 0 -echo $profile->balance; // Output: 0.0 -var_dump($profile->tags); // Output: array(0) {} -var_dump($profile->metadata); // Output: object(stdClass)#123 (0) {} - -// Special handling for boolean values -try { - NullConversionProfile::from([ - 'isActive' => null // This will throw a type error - ]); -} catch (\TypeError $e) { - echo "Boolean type does not support null values: " . $e->getMessage(); -} -``` - -#### Nullable Type Solution - -For scenarios requiring `null` acceptance, use nullable types: - -```php -use Astral\Serialize\Serialize; - -class FlexibleProfile extends Serialize { - public function __construct( - public ?string $username, - public ?int $score, - public ?object $metadata, - public ?array $tags - ) {} -} - -// Create an object with null values -$profile = FlexibleProfile::from([ - 'username' => null, // Allows null - 'score' => null, // Allows null - 'metadata' => null, // Allows null - 'tags' => null // Allows null -]); - -// Convert to array -$profileArray = $profile->toArray(); -// $profileArray contents: -// [ -// 'username' => null, -// 'score' => null, -// 'metadata' => null, -// 'tags' => null -// ] - -// Validate nullable type behavior -echo $profile->username; // Output null -``` - -#### Union Types - -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 -3. Dynamic Type Handling - - Automatically handles input of different types - - Provides more flexible data modeling - -```php -use Astral\Serialize\Serialize; - -// Define a base user class -class User extends Serialize { - public string $name; - public int $age; -} - -// Define an admin user class -class AdminUser extends User { - public string $role; -} - -class FlexibleData extends Serialize { - // Supports integer or string type identifiers - public int|string $flexibleId; - - // Supports user object or integer identifier - public User|int $userIdentifier; - - // Supports complex union types - public AdminUser|User|int $complexIdentifier; -} - -// Scenario 1: Use integer as flexibleId -$data1 = FlexibleData::from( - flexibleId : 123, - userIdentifier : 456, - complexIdentifier : 789 -); - -$data1Array = $data1->toArray(); -// $data1Array contents: -// [ -// 'flexibleId' => 123, -// 'userIdentifier' => 456, -// 'complexIdentifier' => 789 -// ] - -// Scenario 2: Use string as flexibleId -$data2 = FlexibleData::from( - flexibleId : 'ABC123', - userIdentifier : [ - 'name' => 'John', - 'age' => 30 - ], - complexIdentifier : [ - 'name' => 'Jane', - 'age' => 25 - ] -); - -echo $data2->userIdentifier; // Output User object -echo $data2->complexIdentifier; // Output User object - -$data2Array = $data2->toArray(); -// $data2Array contents: -// [ -// 'flexibleId' => 'ABC123', -// 'userIdentifier' => User Object ( -// ['name' => 'John', 'age' => 30] -// ), -// 'complexIdentifier' => User Object ( -// ['name' => 'Jane', 'age' => 25] -// ) -// ] - -// Scenario 3: Use admin user -$data3 = FlexibleData::from( - flexibleId : 'USER001', - userIdentifier : [ - 'name' => 'Bob', - 'age' => 35, - 'role' => 'admin' - ], - complexIdentifier : [ - 'name' => 'Alice', - 'age' => 40, - 'role' => 'super_admin' - ] -); - -echo $data2->userIdentifier; // Output User object -echo $data2->complexIdentifier; // Output AdminUser object - -$data3Array = $data3->toArray(); -// $data3Array contents: -// [ -// 'flexibleId' => 'USER001', -// 'userIdentifier' => User Object ( -// ['name' => 'Bob', 'age' => 35] -// ), -// 'complexIdentifier' => AdminUser Object ( -// ['name' => 'Alice', 'age' => 40, 'role' => 'super_admin'] -// ) -// ] -``` - -#### Array Object Conversion - -##### phpDoc Definition - -```php -use Astral\Serialize\Serialize; - -// Define basic array types -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 { - // Scenario 1: Mixed type array - /** @var (ArrayOne|ArrayTwo)[] */ - public array $mixedTypeArray; - - // Scenario 2: Multiple type arrays - /** @var ArrayOne[]|ArrayTwo[] */ - public array $multiTypeArray; - - // Scenario 3: Key-value mixed type - /** @var array(string, ArrayOne|ArrayTwo) */ - public array $keyValueMixedArray; -} - -// Scenario 1: Mixed type array -$data1 = MultiArraySerialize::from( - mixedTypeArray : [ - ['name' => 'John'], // Convert to ArrayOne object - ['code' => 'ABC123'], // Convert to ArrayTwo object - ['name' => 'Jane'], // Convert to ArrayOne object - ['code' => 'DEF456'] // Convert to ArrayTwo object - ] -); - -$data1Array = $data1->toArray(); -// $data1Array contents: -// [ -// 'mixedTypeArray' => [ -// [0] => ArrayOne Object -// ( -// ['name' => 'John', 'type' => 'one'], -// ) -// [1] => ArrayTwo Object -// ( -// ['code' => 'ABC123', 'type' => 'two'], -// ) -// [2] => ArrayOne Object -// ( -// ['name' => 'Jane', 'type' => 'one'], -// ) -// [3] => ArrayTwo Object -// ( -// ['code' => 'DEF456', 'type' => 'two'], -// ) -// ] -// ] - -// Scenario 2: Multiple type arrays -$data2 = MultiArraySerialize::from( - multiTypeArray:[ - ['name' => 'Bob'], // Convert to ArrayOne object - ['name' => 'Alice'], // Convert to ArrayOne object - ['code' => 'GHI789'] // Convert to ArrayTwo object - ] -); - -$data2Array = $data2->toArray(); -// $data2Array contents: -// [ -// 'multiTypeArray' => [ -// ArrayOne Object ( -// ['name' => 'Bob', 'type' => 'one'] -// ), -// ArrayOne Object ( -// ['name' => 'Alice', 'type' => 'one'] -// ), -// ArrayTwo Object ( -// ['code' => 'GHI789', 'type' => 'two'] -// ) -// ] -// ] - -// Scenario 3: Key-value mixed type -$data3 = MultiArraySerialize::from( - keyValueMixedArray: [ - 'user1' => ['name' => 'John'], // Convert to ArrayOne object - 'system1' => ['code' => 'ABC123'], // Convert to ArrayTwo object - 'user2' => ['name' => 'Jane'] // Convert to ArrayOne object - ] -); - -$data3Array = $data3->toArray(); -// $data3Array contents: -// [ -// 'keyValueMixedArray' => [ -// 'user1' => ArrayOne Object ( -// ['name' => 'John', 'type' => 'one'] -// ), -// 'system1' => ArrayTwo Object ( -// ['code' => 'ABC123', 'type' => 'two'] -// ), -// 'user2' => ArrayOne Object ( -// ['name' => 'Jane', 'type' => 'one'] -// ) -// ] -// ] - -// Scenario 4: Handling unmatched cases -$data4 = MultiArraySerialize::from( - mixedTypeArray : [ - ['unknown' => 'data1'], - ['another' => 'data2'] - ] -); - -$data4Array = $data4->toArray(); -// $data4Array contents: -// [ -// 'mixedTypeArray' => [ -// ['unknown' => 'data1'], -// ['another' => 'data2'] -// ] -// ] -``` - -### Annotation Class Usage - -#### Property Grouping - -Property grouping provides a flexible way to control the input and output behavior of properties, allowing fine-grained data conversion management in different scenarios. - -##### Basic Usage - -Use the `#[Groups]` annotation on properties to specify the groups they belong to. - -```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; - - // Properties without a specified group will be in the default group - public string $noGroupInfo; - - // Constructor parameters also support grouping - public function __construct( - #[Groups('create','detail')] - public readonly string $email, - - #[Groups('update','detail')] - public readonly int $score - ) {} -} - -// Use default group to display all information -$user1 = User::from( - id:1, - name: 'Jane', - score: 100, - username: 'username', - email: 'jane@example.com', - sensitiveData:'Confidential info', - noGroupInfo:'Default group info' -); - -// Use default group toArray, display all information -$defaultArray = $user1->toArray(); -// $defaultArray contents: -// [ -// 'id' => '1', -// 'name' => 'Jane', -// 'username' => 'username', -// 'score' => 100, -// 'email' => 'jane@example.com', -// 'sensitiveData' => 'Confidential info', -// 'noGroupInfo' => 'Default group info' -// ] - -// Use create group to create user, only accept data with create group -$user2 = User::setGroups(['create'])->from( - id:1, - name: 'Jane', - score: 100, - username: 'username', - email: 'jane@example.com', - sensitiveData:'Confidential info', - noGroupInfo:'Default group info' -); - -// Use create group toArray -$createArray = $user2->toArray(); -// $createArray contents: -// [ -// 'name' => 'Jane', -// 'username' => 'username', -// 'email' => 'jane@example.com', -// ] - -// Use update group to update user, only accept data with update group -$user3 = User::setGroups(['update'])->from( - id:1, - name: 'Jane', - score: 100, - username: 'username', - email: 'jane@example.com', - sensitiveData:'Confidential info', - noGroupInfo:'Default group info' -); - -// Use update group toArray -$updateArray = $user3->toArray(); -// $updateArray contents: -// [ -// 'id' => '1', -// 'name' => 'Jane', -// 'score' => 100, -// ] - -// Use detail and other groups to display user, accept data with detail and other groups -$user4 = User::setGroups(['detail','other'])->from( - id:1, - name: 'Jane', - score: 100, - username: 'username', - email: 'jane@example.com', - sensitiveData:'Confidential info', - noGroupInfo:'Default group info' -); - -// Use multiple groups toArray -$multiGroupArray = $user4->toArray(); -// $multiGroupArray contents: -// [ -// 'id' => '1', -// 'name' => 'Jane', -// 'username' => 'username', -// 'score' => 100, -// 'email' => 'jane@example.com', -// 'sensitiveData' => 'Confidential info', -// ] -``` - -##### Nested Class Group Display - -```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 will hide currency -$adminUser = ComplexUser::from( - name: 'John', - sex: 1, - info: [ - 'money' => 100.00, - 'currency' => 'USD' - ] -); - -// Output data -$adminUserArray = $adminUser->toArray(); -// $adminUserArray contents: -// [ -// 'name' => 'John', -// 'sex' => 1, -// 'info' => ComplexNestedInfo Object ([ -// 'money' => 100.00 -// ]) -// ] -``` - -#### Name Mapping - -##### Basic Usage - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Serialize; - -class User extends Serialize { - // Use different property name for input - #[InputName('user_name')] - public string $name; - - // Use different property name for output - #[OutputName('user_id')] - public int $id; - - // Support different input and output names - #[InputName('register_time')] - #[OutputName('registeredAt')] - public DateTime $createdAt; -} - -// Use input data with different names -$user = User::from([ - 'user_name' => 'John', // Mapped to $name - 'id' => 123, // Remains unchanged - 'register_time' => '2023-01-01 10:00:00' // Mapped to $createdAt -]); - -// Output data -$userArray = $user->toArray(); -// $userArray contents: -// [ -// 'name' => 'John', -// 'user_id' => 123, -// 'registeredAt' => '2023-01-01 10:00:00' -// ] -``` - -##### Multiple Input/Output Name Handling - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Serialize; - -class MultiOutputUser extends Serialize { - // Multiple output names - #[OutputName('user_id')] - #[OutputName('id')] - #[OutputName('userId')] - public int $id; - - // Multiple input names, first matching name will be used - #[InputName('user_name')] - #[InputName('other_name')] - #[InputName('userName')] - public int $name; -} - -// Scenario 1: Use first matching input name -$user1 = MultiOutputUser::from([ - 'user_name' => 'John' // Use 'user_name' -]); -echo $user1->name; // Output 'John' - -// Scenario 2: Use second matching input name -$user2 = MultiOutputUser::from([ - 'other_name' => 'Jane' // Use 'other_name' -]); -echo $user2->name; // Output 'Jane' - -// Scenario 3: Use last input name -$user3 = MultiOutputUser::from([ - 'userName' => 'Bob' // Use 'userName' -]); -echo $user3->name; // Output 'Bob' - -// Scenario 4: Multiple inputs, first matching name used -$user4 = MultiOutputUser::from([ - 'userName' => 'Bob', - 'other_name' => 'Jane', - 'user_name' => 'John', -]); -echo $user4->name; // Output 'John' - -// Create user object -$user = MultiOutputUser::from([ - 'id' => 123, - 'name' => 'John' -]); - -// Convert to array -$userArray = $user->toArray(); -// $userArray contents: -// [ -// 'user_id' => 123, -// 'id' => 123, -// 'userId' => 123, -// ] -``` - -#### Complex Mapping Scenarios - -```php -use Astral\Serialize\Serialize; - -class ComplexUser extends Serialize { - // Name mapping for nested objects - #[InputName('user_profile')] - public UserProfile $profile; - - // Name mapping for array elements - #[InputName('user_tags')] - public array $tags; -} - -// Handle complex input structures -$complexUser = ComplexUser::from([ - 'user_profile' => [ - 'nickname' => 'John', - 'age' => 25 - ], - 'user_tags' => ['developer', 'programmer'] -]); - -// Convert to standard array -$complexUserArray = $complexUser->toArray(); -// $complexUserArray contents: -// [ -// 'profile' => UserProfile Object ([ -// 'nickname' => 'John', -// 'age' => 25 -// ]), -// 'tags' => ['developer', 'programmer'] -// ] -``` - -##### Mapper Mapping - -```php -use Astral\Serialize\Attributes\InputName; -use Astral\Serialize\Attributes\OutputName; -use Astral\Serialize\Support\Mappers\{ - CamelCaseMapper, - SnakeCaseMapper, - PascalCaseMapper, - KebabCaseMapper -}; -use Astral\Serialize\Serialize; - -class User extends Serialize { - // Directly specify mapping names - #[InputName('user_name')] - #[OutputName('userName')] - public string $name; - - // Use mapper for style conversion - #[InputName(CamelCaseMapper::class)] - #[OutputName(SnakeCaseMapper::class)] - public int $userId; - - // Support multiple mappings and groups - #[InputName('email', groups: 'profile')] - #[OutputName('userEmail', groups: 'api')] - public string $email; -} - -// Use different mapping strategies -$user = User::from([ - 'user_name' => 'John', // Mapped to $name - 'user_id' => 123, // Use CamelCaseMapper conversion - 'email' => 'user@example.com' // Only effective in 'profile' group -]); - -// Output with different mappings -$userArray = $user->toArray( - inputGroups: ['profile'], // Use only input mappings in 'profile' group - outputGroups: ['api'] // Use only output mappings in 'api' group -); -``` - -##### Global Class Mapping - -```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 { - // Class-level mapping automatically applies to all properties - public string $firstName; - public string $lastName; - public int $userId; - public DateTime $registeredAt; -} - -// Use global mapping -$user = GlobalMappedUser::from([ - 'first_name' => 'John', // From snake_case to firstName - 'last_name' => 'Doe', // From snake_case to lastName - 'user_id' => 123, // From snake_case to userId - 'registered_at' => '2023-01-01' // From snake_case to registeredAt -]); - -// Output will be converted to camelCase -$userArray = $user->toArray(); -// $userArray contents: -// [ -// 'firstName' => 'John', -// 'lastName' => 'Doe', -// 'userId' => 123, -// 'registeredAt' => '2023-01-01' -// ] -``` - -###### Property Mapping Takes Precedence Over Class-Level Mapping - -```php -#[InputName(SnakeCaseMapper::class)] -class PartialOverrideUser extends Serialize { - #[InputName(PascalCaseMapper::class)] - public string $userName; // Prioritize PascalCase mapping - - public string $userEmail; // Continue using class-level global mapping -} - -$partialUser = PartialOverrideUser::from([ - 'User_name' => 'John', // Use snake_case mapping - 'UserName' => 'Jane', // Use PascalCase mapping - 'user_email' => 'user@example.com' // Use snake_case mapping -]); - -$partialUser->toArray(); -// $partialUser contents: -// [ -// 'userName' => 'Jane', -// 'userEmail' => 'user@example.com', -// ] -``` - -###### Global Class Mapping with Groups - -Needs to be used in conjunction with `Groups` annotation - -```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; -} - -// Use admin group -$complexUser = ComplexMappedUser::setGroup('external')->from( - first_name: 'John', - last_name: 'Doe', - full_name: 'John Doe' -); - -$complexUser = $complexUser->toArray(); -// $complexUser contents: -// [ -// 'FirstName' => 'John', -// 'LastName' => 'Doe', -// 'FullName' => 'John Doe', -// ] - -// If specific OutputName/InputName is familiar, attribute rules take priority -// Use public group -$complexUser = ComplexMappedUser::setGroup('api')->from( - first_name: 'John', - last_name: 'Doe', - full_name: 'John Doe' -); - -$complexUser = $complexUser->toArray(); -// $complexUser contents: -// [ -// 'firstName' => 'John', -// 'lastName' => 'Doe', -// 'userEmail' => 'John Doe', -// ] -``` - -#### Custom Mapper - -```php -// Custom mapper needs to extend NameMapper and implement resolve -class CustomMapper implements NameMapper { - public function resolve(string $name): string { - // Implement custom naming conversion logic - return str_replace('user', 'customer', $name); - } -} - -class AdvancedUser extends Serialize { - #[InputName(CustomMapper::class)] - public string $name; -} -``` - -#### Field Ignoring - -1. **Security Control** - - Prevent accidental leakage of sensitive information - - Fine-grained control of data input and output - -2. **Data Filtering** - - Filter fields based on different scenarios - - Customize data views for different APIs or user roles - -3. **Performance Optimization** - - Reduce serialization overhead for unnecessary fields - - Streamline data transmission - -##### Basic Usage - -```php -use Astral\Serialize\Attributes\InputIgnore; -use Astral\Serialize\Attributes\OutputIgnore; -use Astral\Serialize\Serialize; - -class User extends Serialize { - - public string $name; - - // Fields ignored during input - #[InputIgnore] - public string $internalId; - - // Fields ignored during output - #[OutputIgnore] - public string $tempData; -} - -// Create user object -$user = User::from([ - 'name' => 'John', - 'internalId' => 'secret123', // This field will be ignored - 'tempData' => 'temporary' // This field will be ignored -]); - -echo $user->internalId; // This will output '' - -// Convert to array -$userArray = $user->toArray(); -// $userArray contents: -// [ -// 'name' => 'John', -// 'internalId' => '', -// ] -``` - -##### Group Ignoring - -Group ignoring needs to be used in conjunction with the Groups annotation - -```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; -} - -// Default group -$complexUser = ComplexUser::from([ - 'name' => 'John', - 'secretKey' => 'confidential', - 'sensitiveInfo' => 'Sensitive information', - 'globalInputIgnore' => 'Global input ignore', - 'globalOutputIgnore' => 'Global output ignore' -]); - -echo $complexUser->globalInputIgnore; // Output '' -echo $complexUser->globalOutputIgnore; // Output 'Global output ignore' - -$complexUser = $complexUser->toArray(); -// $complexUser contents: -// [ -// 'name' => 'John', -// 'secretKey' => 'confidential', -// 'sensitiveInfo' => 'Sensitive information', -// 'globalInputIgnore' => '', -// ] - -// Use admin group -$complexUser = ComplexUser::setGroups('admin')->from([ - 'name' => 'John', - 'secretKey' => 'confidential', - 'sensitiveInfo' => 'Sensitive information', - 'globalInputIgnore' => 'Global input ignore', - 'globalOutputIgnore' => 'Global output ignore' -]); - -$complexUser = $complexUser->toArray(); -// $complexUser contents: -// [ -// 'name' => '', -// 'secretKey' => 'confidential', -// 'globalInputIgnore' => '', -// ] - -// Use public group -$complexUser = ComplexUser::setGroups('public')->from([ - 'name' => 'John', - 'secretKey' => 'confidential', - 'sensitiveInfo' => 'Sensitive information', - 'globalInputIgnore' => 'Global input ignore', - 'globalOutputIgnore' => 'Global output ignore' -]); - -$complexUser = $complexUser->toArray(); -// $complexUser contents: -// [ -// 'name' => 'John', -// 'globalInputIgnore' => '', -// ] -``` - -### Nested Object Mocking - -#### Basic Usage - -```php -class ComplexUserFaker extends Serialize { - #[FakerObject(UserProfile::class)] - public UserProfile $profile; -} -``` - -#### Demonstration Example - -```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 contents: -// [ -// '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 Class Method Mocking - -```php -class UserService { - public function generateUserData(): array { - return ['name' => 'Generated User']; - } -} - -class UserFaker extends Serialize { - #[FakerMethod(UserService::class, 'generateUserData')] - public array $userData; -} -``` - -#### Complete Example - -```php -use Astral\Serialize\Serialize; -use Astral\Serialize\Attributes\Faker\FakerMethod; -use Astral\Serialize\Attributes\Faker\FakerObject; -use Astral\Serialize\Attributes\Faker\FakerCollection; - -// User Profile Class -class UserProfile extends Serialize { - public string $nickname; - public int $age; - public string $email; - public array $types = ['type1' => 'money', 'type2' => 'score']; -} - -// User Service Class, providing data generation methods -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 Method Mocking Example -class UserFaker extends Serialize { - // Use method to generate simple data - #[FakerMethod(UserService::class, 'generateUserData')] - public array $userData; - - // Use method to generate object - #[FakerMethod(UserService::class, 'generateUserProfile')] - public UserProfile $userProfile; - - // Get specific attribute - #[FakerMethod(UserService::class, 'generateUserProfile', returnType: 'age')] - public int $age; - - // Get specific attribute with multi-level access using [.] - #[FakerMethod(UserService::class, 'generateUserProfile', returnType: 'types.type2')] - public string $type2; - - // Pass parameters - #[FakerMethod(UserService::class, 'generateUserList', params: ['count' => 3])] - public array $userList; -} - -// Generate mock data -$userFaker = UserFaker::faker(); - -// Convert to array -$userFakerArray = $userFaker->toArray(); -// $userFakerArray contents: -// [ -// 'userData' => [ -// 'name' => 'Generated User', -// 'email' => 'generated.user@example.com', -// 'age' => 30 -// ], -// 'userProfile' => UserProfile Object ( -// [ -// 'nickname' => 'GeneratedNickname', -// 'age' => 25, // Randomly generated -// 'email' => 'profile@example.com' -// 'types' => ['type1' => 'money', 'type2' => 'score'] -// ] -// ), -// 'age' => 99 , // Randomly generated -// 'type2' => 'score', -// 'userList' => [ -// ['name' => 'User 0', 'email' => 'user0@example.com'], -// ['name' => 'User 1', 'email' => 'user1@example.com'], -// ['name' => 'User 2', 'email' => 'user2@example.com'] -// ] -// ] diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md deleted file mode 100644 index 35d38d0..0000000 --- a/docs/en/SUMMARY.md +++ /dev/null @@ -1,8 +0,0 @@ -# Summary - -* [介绍](README.md) -* [快速开始](getting-started.md) -* [进阶使用](advanced/index.md) - * [配置](advanced/config.md) - * [插件](advanced/plugins.md) -* [常见问题](faq.md) \ No newline at end of file From dab46008b7e285198a96668430df21402a8d87c1 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:54:24 +0800 Subject: [PATCH 27/43] add config --- docs/.gitbook.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/.gitbook.yaml b/docs/.gitbook.yaml index a5d997c..4f8fe5f 100644 --- a/docs/.gitbook.yaml +++ b/docs/.gitbook.yaml @@ -1,7 +1,4 @@ root: ./ structure: - langs: - - label: 简体中文 - path: zh - - label: English - path: en \ No newline at end of file + readme: ./zh/README.md + summary: ./zh/SUMMARY.md \ No newline at end of file From d974fdec5c8dee72dd4055107d8b17ff95e238a1 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 15:59:41 +0800 Subject: [PATCH 28/43] gitbook test --- docs/en/.gitbook.yaml | 4 + docs/en/README.md | 1647 +++++++++++++++++++++++++++++++++++ docs/en/SUMMARY.md | 5 + docs/en/base-mapper.md | 70 ++ docs/en/getting-started.md | 94 ++ docs/{ => zh}/.gitbook.yaml | 0 6 files changed, 1820 insertions(+) create mode 100644 docs/en/.gitbook.yaml create mode 100644 docs/en/README.md create mode 100644 docs/en/SUMMARY.md create mode 100644 docs/en/base-mapper.md create mode 100644 docs/en/getting-started.md rename docs/{ => zh}/.gitbook.yaml (100%) diff --git a/docs/en/.gitbook.yaml b/docs/en/.gitbook.yaml new file mode 100644 index 0000000..f455ec9 --- /dev/null +++ b/docs/en/.gitbook.yaml @@ -0,0 +1,4 @@ +root: ./ +structure: + readme: ./en/README.md + summary: ./en/SUMMARY.md \ No newline at end of file diff --git a/docs/en/README.md b/docs/en/README.md new file mode 100644 index 0000000..60576c3 --- /dev/null +++ b/docs/en/README.md @@ -0,0 +1,1647 @@ +# Astral Serialize 文档 + +## 快速开始 + +### 安装 + +使用 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('名称太短'); + } + } +} +``` + +## 生成openapi文档 + +#### 创建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(); + } +} +``` + +## 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'] +// ] +// ] +``` diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md new file mode 100644 index 0000000..0673a8d --- /dev/null +++ b/docs/en/SUMMARY.md @@ -0,0 +1,5 @@ +# Summary + +* [介绍](README.md) +* [快速开始](getting-started.md) +* [进阶使用](base-mapper.md) \ No newline at end of file diff --git a/docs/en/base-mapper.md b/docs/en/base-mapper.md new file mode 100644 index 0000000..9fca977 --- /dev/null +++ b/docs/en/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/en/getting-started.md b/docs/en/getting-started.md new file mode 100644 index 0000000..9a2108e --- /dev/null +++ b/docs/en/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/.gitbook.yaml b/docs/zh/.gitbook.yaml similarity index 100% rename from docs/.gitbook.yaml rename to docs/zh/.gitbook.yaml From 8ddc8bff1dce1351792318e721274f331e6d0892 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 16:00:30 +0800 Subject: [PATCH 29/43] gitbook test --- docs/en/.gitbook.yaml | 4 ++-- docs/zh/.gitbook.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/.gitbook.yaml b/docs/en/.gitbook.yaml index f455ec9..692a078 100644 --- a/docs/en/.gitbook.yaml +++ b/docs/en/.gitbook.yaml @@ -1,4 +1,4 @@ root: ./ structure: - readme: ./en/README.md - summary: ./en/SUMMARY.md \ No newline at end of file + readme: README.md + summary: SUMMARY.md \ No newline at end of file diff --git a/docs/zh/.gitbook.yaml b/docs/zh/.gitbook.yaml index 4f8fe5f..692a078 100644 --- a/docs/zh/.gitbook.yaml +++ b/docs/zh/.gitbook.yaml @@ -1,4 +1,4 @@ root: ./ structure: - readme: ./zh/README.md - summary: ./zh/SUMMARY.md \ No newline at end of file + readme: README.md + summary: SUMMARY.md \ No newline at end of file From 3c5fceac3af1d1905f228be74fc933fcd0aa96cb Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 16:06:18 +0800 Subject: [PATCH 30/43] gitbook test --- docs/en/README.md | 1070 +++++++++++++++++--------------------------- docs/en/SUMMARY.md | 4 +- 2 files changed, 402 insertions(+), 672 deletions(-) diff --git a/docs/en/README.md b/docs/en/README.md index 60576c3..4cc0adf 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,16 +1,16 @@ -# Astral Serialize 文档 +# Astral Serialize Documentation -## 快速开始 +## Quick Start -### 安装 +### Installation -使用 Composer 安装: +Install using Composer: ```bash composer require astral/serialize ``` -### 基本用法 +### Basic Usage ```php use Astral\Serialize\Serialize; @@ -20,28 +20,28 @@ class User extends Serialize { public int $age } -// 从数组创建对象 +// Create object from array $user = User::from([ - 'name' => '张三', + 'name' => 'John Doe', 'age' => 30 ]); -// 访问对象属性 -echo $user->name; // 输出: 张三 -echo $user->age; // 输出: 30 +// Access object properties +echo $user->name; // Output: John Doe +echo $user->age; // Output: 30 -// 转换为数组 +// Convert to array $userArray = $user->toArray(); -// $userArray 的内容: +// $userArray contents: // [ -// 'name' => '张三', +// 'name' => 'John Doe', // 'age' => 30 // ] ``` -#### 其他特性 +### Other Features -1. **不可变性**:只读属性在构造后无法修改 +1. **Immutability**: Read-only properties cannot be modified after construction ```php use Astral\Serialize\Serialize; @@ -54,30 +54,30 @@ class User extends Serialize { } $user = User::from([ - 'name' => '张三', + 'name' => 'John Doe', 'age' => 30 ]); try { - $user->name = '李四'; // 编译时错误:无法修改只读属性 + $user->name = 'Jane Doe'; // Compile-time error: cannot modify read-only property } catch (Error $e) { - echo "只读属性不能被重新赋值"; + echo "Read-only properties cannot be reassigned"; } ``` -2. **类型安全的初始化** +2. **Type-Safe Initialization** ```php $user = User::from([ - 'name' => 123, // 整数会被转换为字符串 - 'age' => '35' // 字符串会被转换为整数 + 'name' => 123, // Integer will be converted to string + 'age' => '35' // String will be converted to integer ]); -echo $user->name; // 输出: "123" -echo $user->age; // 输出: 35 +echo $user->name; // Output: "123" +echo $user->age; // Output: 35 ``` -3. **构造函数初始化** +3. **Constructor Initialization** ```php use Astral\Serialize\Serialize; @@ -87,73 +87,21 @@ class User extends Serialize { public readonly string $name, public readonly int $age ) { - // 可以在构造函数中添加额外的验证或处理逻辑 + // Can add additional validation or processing logic in the constructor if (strlen($name) < 2) { - throw new \InvalidArgumentException('名称太短'); + throw new \InvalidArgumentException('Name is too short'); } } } ``` -## 生成openapi文档 +## DTO Conversion -#### 创建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; -} -``` +### Type Conversion -#### 创建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(); - } -} -``` - -## DTO 转换 - -### 类型转换 +#### Basic Type Conversion -#### 基本类型转换 - -##### 方式一:构造函数属性提升 +##### Method One: Constructor Property Promotion ```php use Astral\Serialize\Serialize; @@ -168,7 +116,7 @@ class Profile extends Serialize { } ``` -##### 方式二:传统属性定义 +##### Method Two: Traditional Property Definition ```php use Astral\Serialize\Serialize; @@ -180,19 +128,19 @@ class Profile extends Serialize { public bool $isActive; } -// 两种方式都支持相同的类型转换 +// Both methods support the same type conversion $profile = Profile::from([ - 'username' => 123, // 整数转换为字符串 - 'score' => '100', // 字符串转换为整数 - 'balance' => '99.99', // 字符串转换为浮点数 - 'isActive' => 1 // 数字转换为布尔值 + 'username' => 123, // Integer converted to string + 'score' => '100', // String converted to integer + 'balance' => '99.99', // String converted to float + 'isActive' => 1 // Number converted to boolean ]); -// 转换为数组 +// Convert to array $profileArray = $profile->toArray(); ``` -##### 方式三:只读属性 +##### Method Three: Read-Only Properties ```php use Astral\Serialize\Serialize; @@ -203,7 +151,7 @@ class Profile extends Serialize { public readonly float $balance; public readonly bool $isActive; - // 手动初始化 + // Manual initialization public function __construct( string $username, int $score, @@ -218,18 +166,18 @@ class Profile extends Serialize { } ``` -无论使用哪种方式,`Serialize` 类都能正常工作,并提供相同的类型转换和序列化功能。 +Regardless of the method used, the `Serialize` class will work normally and provide the same type conversion and serialization functionality. -#### 枚举转换 +#### Enum Conversion -枚举转换提供了强大且灵活的枚举处理机制,支持多种枚举类型和转换场景。 +Enum conversion provides a powerful and flexible enum handling mechanism, supporting multiple enum types and conversion scenarios. -- 支持 `tryFrom()` 和 `cases()` 方法的枚举类型 -- 输入时自动将字符串转换为枚举实例 -- 输出时自动将枚举转换为字符串(枚举名称) -- 提供灵活且安全的枚举处理机制 +- Supports enums with `tryFrom()` and `cases()` methods +- Automatically converts strings to enum instances during input +- Automatically converts enums to strings (enum names) during output +- Provides flexible and safe enum handling mechanisms -##### 普通枚举 +##### Regular Enum ```php enum UserRole { @@ -242,26 +190,26 @@ class ComplexUser extends Serialize { public UserRole $role; - // 支持多种枚举类型 + // Supports multiple enum types public UserStatus|UserRole $mixedStatus; } $complexUser = ComplexUser::from([ - 'role' => 'ADMIN', // 自动转换为 UserRole::ADMIN - 'mixedStatus' => 'ACTIVE' // 可以是 UserStatus 或 UserRole + 'role' => 'ADMIN', // Automatically converted to UserRole::ADMIN + 'mixedStatus' => 'ACTIVE' // Can be UserStatus or UserRole ]); -echo $complexUser->role; // 返回 UserRole枚举实例 +echo $complexUser->role; // Returns UserRole enum instance $complexUserArray = $complexUser->toArray(); -// $complexUserArray 的内容: +// $complexUserArray contents: // [ // 'role' => 'ADMIN', // 'mixedStatus' => 'ACTIVE' // ] ``` -##### 回退枚举 +##### Backed Enum ```php use Astral\Serialize\Serialize; @@ -273,39 +221,39 @@ enum UserStatus: string { case SUSPENDED = 'suspended'; } -// 定义带有枚举的用户类 +// Define a user class with enum class User extends Serialize { public string $name; - // 支持 UnitEnum 和 BackedEnum + // Supports UnitEnum and BackedEnum public UserStatus $status; - // 支持多枚举类型 + // Supports multiple enum types public UserStatus|string $alternateStatus; } -// 创建用户对象 +// Create user object $user = User::from([ - 'name' => '张三', - 'status' => 'active', // 自动转换为 UserStatus::ACTIVE - 'alternateStatus' => 'inactive' // 支持字符串或枚举值 + 'name' => 'John Doe', + 'status' => 'active', // Automatically converted to UserStatus::ACTIVE + 'alternateStatus' => 'inactive' // Supports string or enum values ]); -var_dump($user->status); // 输出: UserStatus::ACTIVE +var_dump($user->status); // Output: UserStatus::ACTIVE -// 转换为数组 +// Convert to array $userArray = $user->toArray(); -// $userArray 的内容: +// $userArray contents: // [ -// 'name' => '张三', -// 'status' => 'ACTIVE', // 输出枚举名称 +// 'name' => 'John Doe', +// 'status' => 'ACTIVE', // Output enum name // 'alternateStatus' => 'INACTIVE' // ] ``` -#### Null 值转换规则详细示例 +#### Null Value Conversion Rules Detailed Example -当属性不是可空类型(`?type`)时,`null` 值会根据目标类型自动转换: +When the property is not a nullable type (`?type`), `null` values will be automatically converted based on the target type: ```php use Astral\Serialize\Serialize; @@ -318,35 +266,35 @@ class NullConversionProfile extends Serialize { public object $metadata; } -// Null 值转换示例 +// Null value conversion example $profile = NullConversionProfile::from([ - 'username' => null, // 转换为空字符串 '' - 'score' => null, // 转换为 0 - 'balance' => null, // 转换为 0.0 - 'tags' => null, // 转换为空数组 [] - 'metadata' => null // 转换为空对象 new stdClass() + 'username' => null, // Converted to empty string '' + 'score' => null, // Converted to 0 + 'balance' => null, // Converted to 0.0 + 'tags' => null, // Converted to empty array [] + 'metadata' => null // Converted to empty object 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) {} +// Verify conversion results +echo $profile->username; // Output: ""(empty string) +echo $profile->score; // Output: 0 +echo $profile->balance; // Output: 0.0 +var_dump($profile->tags); // Output: array(0) {} +var_dump($profile->metadata); // Output: object(stdClass)#123 (0) {} -// 布尔值的特殊处理 +// Special handling for boolean values try { NullConversionProfile::from([ - 'isActive' => null // 这将抛出类型错误 + 'isActive' => null // This will throw a type error ]); } catch (\TypeError $e) { - echo "布尔类型不支持 null 值:" . $e->getMessage(); + echo "Boolean type does not support null values: " . $e->getMessage(); } ``` -#### 可空类型的方案 +#### Nullable Type Solution -对于需要接受 `null` 的场景,使用可空类型: +For scenarios requiring `null` acceptance, use nullable types: ```php use Astral\Serialize\Serialize; @@ -360,17 +308,17 @@ class FlexibleProfile extends Serialize { ) {} } -// 创建包含 null 值的对象 +// Create an object with null values $profile = FlexibleProfile::from([ - 'username' => null, // 允许 null - 'score' => null, // 允许 null - 'metadata' => null, // 允许 null - 'tags' => null // 允许 null + 'username' => null, // Allows null + 'score' => null, // Allows null + 'metadata' => null, // Allows null + 'tags' => null // Allows null ]); -// 转换为数组 +// Convert to array $profileArray = $profile->toArray(); -// $profileArray 的内容: +// $profileArray contents: // [ // 'username' => null, // 'score' => null, @@ -378,127 +326,127 @@ $profileArray = $profile->toArray(); // 'tags' => null // ] -// 验证可空类型的行为 -echo $profile->username; // 输出 null +// Validate nullable type behavior +echo $profile->username; // Output null ``` -#### 联合类型 +#### Union Types -1. 可以混合使用基本类型和对象类型 -2. 对象层级匹配 - 对于多个对象类型,会选择最匹配的类型 - 支持继承层级的智能匹配 -3. 动态类型处理 - 自动处理不同类型的输入 - 提供更加灵活的数据建模方式 +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 +3. Dynamic Type Handling + - Automatically handles input of different types + - Provides more flexible data modeling ```php use Astral\Serialize\Serialize; -// 定义一个基础用户类 +// Define a base user class class User extends Serialize { public string $name; public int $age; } -// 定义一个管理员用户类 +// Define an admin user class class AdminUser extends User { public string $role; } class FlexibleData extends Serialize { - // 支持整数或字符串类型的标识符 + // Supports integer or string type identifiers public int|string $flexibleId; - // 支持用户对象或整数标识符 + // Supports user object or integer identifier public User|int $userIdentifier; - // 支持多种复杂的联合类型 + // Supports complex union types public AdminUser|User|int $complexIdentifier; } -// 场景1:使用整数作为 flexibleId -$data1 = FlexibleData::from([ - 'flexibleId' => 123, - 'userIdentifier' => 456, - 'complexIdentifier' => 789 -]); +// Scenario 1: Use integer as flexibleId +$data1 = FlexibleData::from( + flexibleId : 123, + userIdentifier : 456, + complexIdentifier : 789 +); $data1Array = $data1->toArray(); -// $data1Array 的内容: +// $data1Array contents: // [ // 'flexibleId' => 123, // 'userIdentifier' => 456, // 'complexIdentifier' => 789 // ] -// 场景2:使用字符串作为 flexibleId -$data2 = FlexibleData::from([ - 'flexibleId' => 'ABC123', - 'userIdentifier' => [ - 'name' => '张三', +// Scenario 2: Use string as flexibleId +$data2 = FlexibleData::from( + flexibleId : 'ABC123', + userIdentifier : [ + 'name' => 'John', 'age' => 30 ], - 'complexIdentifier' => [ - 'name' => '李四', + complexIdentifier : [ + 'name' => 'Jane', 'age' => 25 ] -]); +); -echo $data2->userIdentifier; // 输出 User 对象 -echo $data2->complexIdentifier; // 输出 User 对象 +echo $data2->userIdentifier; // Output User object +echo $data2->complexIdentifier; // Output User object $data2Array = $data2->toArray(); -// $data2Array 的内容: +// $data2Array contents: // [ // 'flexibleId' => 'ABC123', // 'userIdentifier' => User Object ( -// ['name' => '张三', 'age' => 30] +// ['name' => 'John', 'age' => 30] // ), // 'complexIdentifier' => User Object ( -// ['name' => '李四', 'age' => 25] +// ['name' => 'Jane', 'age' => 25] // ) // ] -// 场景3:使用管理员用户 -$data3 = FlexibleData::from([ - 'flexibleId' => 'USER001', - 'userIdentifier' => [ - 'name' => '王五', +// Scenario 3: Use admin user +$data3 = FlexibleData::from( + flexibleId : 'USER001', + userIdentifier : [ + 'name' => 'Bob', 'age' => 35, 'role' => 'admin' ], - 'complexIdentifier' => [ - 'name' => '赵六', + complexIdentifier : [ + 'name' => 'Alice', 'age' => 40, 'role' => 'super_admin' ] -]); +); -echo $data2->userIdentifier; // 输出 User 对象 -echo $data2->complexIdentifier; // 输出 AdminUser 对象 +echo $data2->userIdentifier; // Output User object +echo $data2->complexIdentifier; // Output AdminUser object $data3Array = $data3->toArray(); -// $data3Array 的内容: +// $data3Array contents: // [ // 'flexibleId' => 'USER001', // 'userIdentifier' => User Object ( -// ['name' => '王五', 'age' => 35] +// ['name' => 'Bob', 'age' => 35] // ), // 'complexIdentifier' => AdminUser Object ( -// ['name' => '赵六', 'age' => 40, 'role' => 'super_admin'] +// ['name' => 'Alice', 'age' => 40, 'role' => 'super_admin'] // ) // ] ``` -#### 数组对象转换 +#### Array Object Conversion -##### phpDoc定义 +##### phpDoc Definition ```php use Astral\Serialize\Serialize; -// 定义基础数组类型 +// Define basic array types class ArrayOne extends Serialize { public string $type = 'one'; public string $name; @@ -510,36 +458,36 @@ class ArrayTwo extends Serialize { } class MultiArraySerialize extends Serialize { - // 场景1:混合类型数组 + // Scenario 1: Mixed type array /** @var (ArrayOne|ArrayTwo)[] */ public array $mixedTypeArray; - // 场景2:多类型数组 + // Scenario 2: Multiple type arrays /** @var ArrayOne[]|ArrayTwo[] */ public array $multiTypeArray; - // 场景3:键值对混合类型 + // Scenario 3: Key-value mixed type /** @var array(string, ArrayOne|ArrayTwo) */ public array $keyValueMixedArray; } -// 场景1:混合类型数组 +// Scenario 1: Mixed type array $data1 = MultiArraySerialize::from( mixedTypeArray : [ - ['name' => '张三'], // 转化 ArrayOne 对象 - ['code' => 'ABC123'], // 转化 ArrayTwo 对象 - ['name' => '李四'], // 转化 ArrayOne 对象 - ['code' => 'DEF456'] // 转化 ArrayTwo 对象 + ['name' => 'John'], // Convert to ArrayOne object + ['code' => 'ABC123'], // Convert to ArrayTwo object + ['name' => 'Jane'], // Convert to ArrayOne object + ['code' => 'DEF456'] // Convert to ArrayTwo object ] ); $data1Array = $data1->toArray(); -// $data1Array 的内容: +// $data1Array contents: // [ // 'mixedTypeArray' => [ // [0] => ArrayOne Object // ( -// ['name' => '张三', 'type' => 'one'], +// ['name' => 'John', 'type' => 'one'], // ) // [1] => ArrayTwo Object // ( @@ -547,7 +495,7 @@ $data1Array = $data1->toArray(); // ) // [2] => ArrayOne Object // ( -// ['name' => '李四', 'type' => 'one'], +// ['name' => 'Jane', 'type' => 'one'], // ) // [3] => ArrayTwo Object // ( @@ -556,24 +504,24 @@ $data1Array = $data1->toArray(); // ] // ] -// 场景2:多类型数组 +// Scenario 2: Multiple type arrays $data2 = MultiArraySerialize::from( multiTypeArray:[ - ['name' => '王五'], // 转化 ArrayOne 对象 - ['name' => '赵六'], // 转化 ArrayOne 对象 - ['code' => 'GHI789'] // 转化 ArrayTwo 对象 + ['name' => 'Bob'], // Convert to ArrayOne object + ['name' => 'Alice'], // Convert to ArrayOne object + ['code' => 'GHI789'] // Convert to ArrayTwo object ] ); $data2Array = $data2->toArray(); -// $data2Array 的内容: +// $data2Array contents: // [ // 'multiTypeArray' => [ // ArrayOne Object ( -// ['name' => '王五', 'type' => 'one'] +// ['name' => 'Bob', 'type' => 'one'] // ), // ArrayOne Object ( -// ['name' => '赵六', 'type' => 'one'] +// ['name' => 'Alice', 'type' => 'one'] // ), // ArrayTwo Object ( // ['code' => 'GHI789', 'type' => 'two'] @@ -581,32 +529,32 @@ $data2Array = $data2->toArray(); // ] // ] -// 场景3:键值对混合类型 +// Scenario 3: Key-value mixed type $data3 = MultiArraySerialize::from( keyValueMixedArray: [ - 'user1' => ['name' => '张三'], // 转化 ArrayOne 对象 - 'system1' => ['code' => 'ABC123'], // 转化 ArrayTwo 对象 - 'user2' => ['name' => '李四'] // 转化 ArrayOne 对象 + 'user1' => ['name' => 'John'], // Convert to ArrayOne object + 'system1' => ['code' => 'ABC123'], // Convert to ArrayTwo object + 'user2' => ['name' => 'Jane'] // Convert to ArrayOne object ] ); $data3Array = $data3->toArray(); -// $data3Array 的内容: +// $data3Array contents: // [ // 'keyValueMixedArray' => [ // 'user1' => ArrayOne Object ( -// ['name' => '张三', 'type' => 'one'] +// ['name' => 'John', 'type' => 'one'] // ), // 'system1' => ArrayTwo Object ( // ['code' => 'ABC123', 'type' => 'two'] // ), // 'user2' => ArrayOne Object ( -// ['name' => '李四', 'type' => 'one'] +// ['name' => 'Jane', 'type' => 'one'] // ) // ] // ] -// 场景4:无法匹配时的处理 +// Scenario 4: Handling unmatched cases $data4 = MultiArraySerialize::from( mixedTypeArray : [ ['unknown' => 'data1'], @@ -615,7 +563,7 @@ $data4 = MultiArraySerialize::from( ); $data4Array = $data4->toArray(); -// $data4Array 的内容: +// $data4Array contents: // [ // 'mixedTypeArray' => [ // ['unknown' => 'data1'], @@ -624,15 +572,15 @@ $data4Array = $data4->toArray(); // ] ``` -### 注解类使用 +### Annotation Class Usage -#### 属性分组 +#### Property Grouping -属性分组提供了一种灵活的方式来控制属性的输入和输出行为,允许在不同场景下精细地管理数据转换。 +Property grouping provides a flexible way to control the input and output behavior of properties, allowing fine-grained data conversion management in different scenarios. -##### 基本用法 +##### Basic Usage -在属性上使用 `#[Groups]` 注解来指定属性所属的分组。 +Use the `#[Groups]` annotation on properties to specify the groups they belong to. ```php use Astral\Serialize\Attributes\Groups; @@ -652,10 +600,10 @@ class User extends Serialize { #[Groups('other')] public string $sensitiveData; - // 没有指定Group 的属性将会被默认分组在default分组中 + // Properties without a specified group will be in the default group public string $noGroupInfo; - // 构造函数参数也支持分组 + // Constructor parameters also support grouping public function __construct( #[Groups('create','detail')] public readonly string $email, @@ -665,118 +613,95 @@ class User extends Serialize { ) {} } - - -// 使用 默认分组展示所有信息 +// Use default group to display all information $user1 = User::from( id:1, - name: '李四', + name: 'Jane', score: 100, username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' + email: 'jane@example.com', + sensitiveData:'Confidential info', + noGroupInfo:'Default group info' ); -// 使用默认分组 toArray,展示所有信息 +// Use default group toArray, display all information $defaultArray = $user1->toArray(); -// $defaultArray 的内容: +// $defaultArray contents: // [ // 'id' => '1', -// 'name' => '李四', +// 'name' => 'Jane', // 'username' => 'username', // 'score' => 100, -// 'email' => 'zhangsan@example.com', -// 'sensitiveData' => '机密信息', -// 'noGroupInfo' => '默认分组信息' +// 'email' => 'jane@example.com', +// 'sensitiveData' => 'Confidential info', +// 'noGroupInfo' => 'Default group info' // ] -// 指定分组内容输入 -$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的数据信息 +// Use create group to create user, only accept data with create group $user2 = User::setGroups(['create'])->from( id:1, - name: '李四', + name: 'Jane', score: 100, username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' + email: 'jane@example.com', + sensitiveData:'Confidential info', + noGroupInfo:'Default group info' ); -// 使用 create 分组 toArray +// Use create group toArray $createArray = $user2->toArray(); -// $createArray 的内容: +// $createArray contents: // [ -// 'name' => '李四', +// 'name' => 'Jane', // 'username' => 'username', -// 'email' => 'zhangsan@example.com', +// 'email' => 'jane@example.com', // ] -// 使用 update 分组更新用户 只会接受group为update的数据信息 +// Use update group to update user, only accept data with update group $user3 = User::setGroups(['update'])->from( id:1, - name: '李四', + name: 'Jane', score: 100, username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' + email: 'jane@example.com', + sensitiveData:'Confidential info', + noGroupInfo:'Default group info' ); -// 使用 update 分组 toArray +// Use update group toArray $updateArray = $user3->toArray(); -// $updateArray 的内容: +// $updateArray contents: // [ // 'id' => '1', -// 'name' => '李四', +// 'name' => 'Jane', // 'score' => 100, // ] -// 使用 detail 和 other 展示用户 会接受group为detail和other的数据信息 +// Use detail and other groups to display user, accept data with detail and other groups $user4 = User::setGroups(['detail','other'])->from( id:1, - name: '李四', + name: 'Jane', score: 100, username: 'username', - email: 'zhangsan@example.com', - sensitiveData:'机密信息', - noGroupInfo:'默认分组信息' + email: 'jane@example.com', + sensitiveData:'Confidential info', + noGroupInfo:'Default group info' ); -// 使用多个分组 toArray +// Use multiple groups toArray $multiGroupArray = $user4->toArray(); -// $multiGroupArray 的内容: +// $multiGroupArray contents: // [ // 'id' => '1', -// 'name' => '李四', +// 'name' => 'Jane', // 'username' => 'username', // 'score' => 100, -// 'email' => 'zhangsan@example.com', -// 'sensitiveData' => '机密信息', +// 'email' => 'jane@example.com', +// 'sensitiveData' => 'Confidential info', // ] ``` -##### 嵌套类指定Group类展示 +##### Nested Class Group Display ```php class ComplexUser extends Serialize { @@ -796,21 +721,21 @@ class ComplexNestedInfo extends Serialize { public string $currency; } -// ComplexNestedInfo 会自动隐藏currency +// ComplexNestedInfo will hide currency $adminUser = ComplexUser::from( - name: '张三', + name: 'John', sex: 1, info: [ 'money' => 100.00, - 'currency' => 'CNY' - ]; + 'currency' => 'USD' + ] ); -// 输出数据 +// Output data $adminUserArray = $adminUser->toArray(); -// $adminUserArray 的内容: +// $adminUserArray contents: // [ -// 'name' => '张三', +// 'name' => 'John', // 'sex' => 1, // 'info' => ComplexNestedInfo Object ([ // 'money' => 100.00 @@ -818,9 +743,9 @@ $adminUserArray = $adminUser->toArray(); // ] ``` -#### 名称映射 +#### Name Mapping -##### 基础使用 +##### Basic Usage ```php use Astral\Serialize\Attributes\InputName; @@ -828,38 +753,38 @@ use Astral\Serialize\Attributes\OutputName; use Astral\Serialize\Serialize; class User extends Serialize { - // 输入时使用不同的属性名 + // Use different property name for input #[InputName('user_name')] public string $name; - // 输出时使用不同的属性名 + // Use different property name for output #[OutputName('user_id')] public int $id; - // 同时支持输入和输出不同名称 + // Support different input and output names #[InputName('register_time')] #[OutputName('registeredAt')] public DateTime $createdAt; } -// 使用不同名称的输入数据 +// Use input data with different names $user = User::from([ - 'user_name' => '张三', // 映射到 $name - 'id' => 123, // 保持不变 - 'register_time' => '2023-01-01 10:00:00' // 映射到 $createdAt + 'user_name' => 'John', // Mapped to $name + 'id' => 123, // Remains unchanged + 'register_time' => '2023-01-01 10:00:00' // Mapped to $createdAt ]); -// 输出数据 +// Output data $userArray = $user->toArray(); -// $userArray 的内容: +// $userArray contents: // [ -// 'name' => '张三', +// 'name' => 'John', // 'user_id' => 123, // 'registeredAt' => '2023-01-01 10:00:00' // ] ``` -##### 多输入/输出名称处理 +##### Multiple Input/Output Name Handling ```php use Astral\Serialize\Attributes\InputName; @@ -867,56 +792,54 @@ use Astral\Serialize\Attributes\OutputName; use Astral\Serialize\Serialize; class MultiOutputUser extends Serialize { - // 多个输出名称 + // Multiple output names #[OutputName('user_id')] #[OutputName('id')] #[OutputName('userId')] public int $id; - // 多个输出名称 按照声明顺序取地一个匹配的name + // Multiple input names, first matching name will be used #[InputName('user_name')] #[InputName('other_name')] #[InputName('userName')] public int $name; - } -// 场景1:使用第一个匹配的输入名称 -$user1 = MultiInputUser::from([ - 'user_name' => '张三' // 使用 'user_name' +// Scenario 1: Use first matching input name +$user1 = MultiOutputUser::from([ + 'user_name' => 'John' // Use 'user_name' ]); -echo $user1->name; // 输出 '张三' +echo $user1->name; // Output 'John' -// 场景2:使用第二个匹配的输入名称 -$user2 = MultiInputUser::from([ - 'other_name' => '李四' // 使用 'other_name' +// Scenario 2: Use second matching input name +$user2 = MultiOutputUser::from([ + 'other_name' => 'Jane' // Use 'other_name' ]); -echo $user2->name; // 输出 '李四' +echo $user2->name; // Output 'Jane' -// 场景3:使用最后的输入名称 -$user3 = MultiInputUser::from([ - 'userName' => '王五' // 使用 'userName' +// Scenario 3: Use last input name +$user3 = MultiOutputUser::from([ + 'userName' => 'Bob' // Use 'userName' ]); -echo $user3->name; // 输出 '王五' +echo $user3->name; // Output 'Bob' -// 场景4:传入多个的时候 按照声明顺序取地一个匹配的name -$user4 = MultiInputUser::from([ - 'userName' => '王五', - 'other_name' => '李四', - 'user_name' => '张三', +// Scenario 4: Multiple inputs, first matching name used +$user4 = MultiOutputUser::from([ + 'userName' => 'Bob', + 'other_name' => 'Jane', + 'user_name' => 'John', ]); -echo $user4->name; // 输出 '张三' +echo $user4->name; // Output 'John' -// 创建用户对象 +// Create user object $user = MultiOutputUser::from([ 'id' => 123, - 'name' => '张三' + 'name' => 'John' ]); -// 转换为数组 -// tips: 因为id 有多个outputname 所以输出了 ['user_id','id','userId'] +// Convert to array $userArray = $user->toArray(); -// $userArray 的内容: +// $userArray contents: // [ // 'user_id' => 123, // 'id' => 123, @@ -924,43 +847,43 @@ $userArray = $user->toArray(); // ] ``` -##### 复杂映射场景 +#### Complex Mapping Scenarios ```php use Astral\Serialize\Serialize; class ComplexUser extends Serialize { - // 嵌套对象的名称映射 + // Name mapping for nested objects #[InputName('user_profile')] public UserProfile $profile; - // 数组元素的名称映射 + // Name mapping for array elements #[InputName('user_tags')] public array $tags; } -// 处理复杂的输入结构 +// Handle complex input structures $complexUser = ComplexUser::from([ 'user_profile' => [ - 'nickname' => '小明', + 'nickname' => 'John', 'age' => 25 ], 'user_tags' => ['developer', 'programmer'] ]); -// 转换为标准数组 +// Convert to standard array $complexUserArray = $complexUser->toArray(); -// $complexUserArray 的内容: +// $complexUserArray contents: // [ // 'profile' => UserProfile Object ([ -// 'nickname' => '小明', +// 'nickname' => 'John', // 'age' => 25 // ]), // 'tags' => ['developer', 'programmer'] // ] ``` -##### Mapper映射 +##### Mapper Mapping ```php use Astral\Serialize\Attributes\InputName; @@ -973,42 +896,38 @@ use Astral\Serialize\Support\Mappers\{ }; use Astral\Serialize\Serialize; -#[Groups('profile','api')] class User extends Serialize { - // 直接指定映射名称 - #[InputName('user_name', groups: ['profile','api'])] - #[OutputName('userName', groups: ['profile','api'])] + // Directly specify mapping names + #[InputName('user_name')] + #[OutputName('userName')] public string $name; - // 使用映射器进行风格转换 - #[InputName(CamelCaseMapper::class, groups: ['profile','api'])] - #[OutputName(SnakeCaseMapper::class, groups: ['profile','api'])] + // Use mapper for style conversion + #[InputName(CamelCaseMapper::class)] + #[OutputName(SnakeCaseMapper::class)] public int $userId; - // 支持多个映射和分组 - #[InputName('profile-email', groups: 'profile')] - #[OutputName('userEmail', groups: 'profile')] + // Support multiple mappings and groups + #[InputName('email', groups: 'profile')] + #[OutputName('userEmail', groups: 'api')] public string $email; } -// 使用不同的映射策略 -$user = User::setGroups('profile')::from([ - 'user_name' => '张三', // 映射到 $name - 'userId' => 123, // 使用 CamelCaseMapper 转换 - 'profile-email' => 'user@example.com' // 仅在 'profile' 分组生效 +// Use different mapping strategies +$user = User::from([ + 'user_name' => 'John', // Mapped to $name + 'user_id' => 123, // Use CamelCaseMapper conversion + 'email' => 'user@example.com' // Only effective in 'profile' group ]); -// 输出时应用不同的映射 -$userArray = $user->toArray(); -// $userArray 的内容: -// [ -// 'userName' => '张三', -// 'user_id' => '三', -// 'userEmail' => user@example.com, -// ] +// Output with different mappings +$userArray = $user->toArray( + inputGroups: ['profile'], // Use only input mappings in 'profile' group + outputGroups: ['api'] // Use only output mappings in 'api' group +); ``` - -##### 全局类映射 + +##### Global Class Mapping ```php use Astral\Serialize\Attributes\InputName; @@ -1024,61 +943,60 @@ use Astral\Serialize\Serialize; #[InputName(SnakeCaseMapper::class)] #[OutputName(CamelCaseMapper::class)] class GlobalMappedUser extends Serialize { - // 类级别的映射会自动应用到所有属性 + // Class-level mapping automatically applies to all properties public string $firstName; public string $lastName; public int $userId; public DateTime $registeredAt; } -// 使用全局映射 +// Use global mapping $user = GlobalMappedUser::from([ - 'first_name' => '张', // 从蛇形映射到 firstName - 'last_name' => '三', // 从蛇形映射到 lastName - 'user_id' => 123, // 从蛇形映射到 userId - 'registered_at' => '2023-01-01' // 从蛇形映射到 registeredAt + 'first_name' => 'John', // From snake_case to firstName + 'last_name' => 'Doe', // From snake_case to lastName + 'user_id' => 123, // From snake_case to userId + 'registered_at' => '2023-01-01' // From snake_case to registeredAt ]); -// 输出时会转换为驼峰命名 +// Output will be converted to camelCase $userArray = $user->toArray(); -// $userArray 的内容: +// $userArray contents: // [ -// 'firstName' => '张', -// 'lastName' => '三', +// 'firstName' => 'John', +// 'lastName' => 'Doe', // 'userId' => 123, // 'registeredAt' => '2023-01-01' // ] ``` -###### 属性映射大于类级映射 +###### Property Mapping Takes Precedence Over Class-Level Mapping ```php - #[InputName(SnakeCaseMapper::class)] class PartialOverrideUser extends Serialize { #[InputName(PascalCaseMapper::class)] - public string $userName; // 优先使用帕斯卡命名映射 + public string $userName; // Prioritize PascalCase mapping - public string $userEmail; // 继续使用类级别的全局映射 + public string $userEmail; // Continue using class-level global mapping } $partialUser = PartialOverrideUser::from([ - 'User_name' => '张三', // 使用蛇形映射 - 'UserName' => '李四', // 使用帕斯卡映射 - 'user_email' => 'user@example.com' // 使用蛇形映射 + 'User_name' => 'John', // Use snake_case mapping + 'UserName' => 'Jane', // Use PascalCase mapping + 'user_email' => 'user@example.com' // Use snake_case mapping ]); $partialUser->toArray(); -// $partialUser 的内容: +// $partialUser contents: // [ -// 'userName' => '李四', +// 'userName' => 'Jane', // 'userEmail' => 'user@example.com', // ] ``` -###### 全局类映射的分组使用 +###### Global Class Mapping with Groups -需要搭配`Groups`注解一起使用 +Needs to be used in conjunction with `Groups` annotation ```php use Astral\Serialize\Attributes\Groups; @@ -1092,7 +1010,6 @@ use Astral\Serialize\Support\Mappers\{ }; use Astral\Serialize\Serialize; - #[InputName(SnakeCaseMapper::class, groups: 'external')] #[InputName(CamelCaseMapper::class, groups: 'api')] #[OutputName(PascalCaseMapper::class, groups: ['external','api'])] @@ -1104,52 +1021,51 @@ class ComplexMappedUser extends Serialize { #[Groups('external', 'api')] public string $lastName; - #[InputName('full_name', groups: 'special')] #[OutputName('userEmail', groups: 'api')] #[Groups('external', 'api')] public string $fullName; } -// 使用admin分组 +// Use admin group $complexUser = ComplexMappedUser::setGroup('external')->from( - first_name :'张', - last_name :'三' - full_name: '张三' + first_name: 'John', + last_name: 'Doe', + full_name: 'John Doe' ); $complexUser = $complexUser->toArray(); -// $complexUser 的内容: +// $complexUser contents: // [ -// 'FirstName' => '张', -// 'LastName' => '三', -// 'FullName' => 张三, +// 'FirstName' => 'John', +// 'LastName' => 'Doe', +// 'FullName' => 'John Doe', // ] -// 如果熟悉指定了OutputName/InputName 则属性规则优先 -// 使用public分组 +// If specific OutputName/InputName is familiar, attribute rules take priority +// Use public group $complexUser = ComplexMappedUser::setGroup('api')->from( - first_name :'张', - last_name :'三' - full_name: '张三' + first_name: 'John', + last_name: 'Doe', + full_name: 'John Doe' ); $complexUser = $complexUser->toArray(); -// $complexUser 的内容: +// $complexUser contents: // [ -// 'FirstName' => '张', -// 'LastName' => '三', -// 'userEmail' => 张三, +// 'firstName' => 'John', +// 'lastName' => 'Doe', +// 'userEmail' => 'John Doe', // ] ``` -#### 自定义映射器 +#### Custom Mapper ```php -// 自定义映射器 需要继承NameMapper 并实现 resolve +// Custom mapper needs to extend NameMapper and implement resolve class CustomMapper implements NameMapper { public function resolve(string $name): string { - // 实现自定义的命名转换逻辑 + // Implement custom naming conversion logic return str_replace('user', 'customer', $name); } } @@ -1160,62 +1076,61 @@ class AdvancedUser extends Serialize { } ``` -#### 字段忽略 +#### Field Ignoring -1. **安全性控制** - - 防止敏感信息的意外泄露 - - 精细控制数据的输入和输出 +1. **Security Control** + - Prevent accidental leakage of sensitive information + - Fine-grained control of data input and output -2. **数据过滤** - - 根据不同场景过滤字段 - - 为不同的 API 或用户角色定制数据视图 +2. **Data Filtering** + - Filter fields based on different scenarios + - Customize data views for different APIs or user roles -3. **性能优化** - - 减少不必要字段的序列化开销 - - 精简数据传输 +3. **Performance Optimization** + - Reduce serialization overhead for unnecessary fields + - Streamline data transmission -##### 基础使用 +##### Basic Usage ```php use Astral\Serialize\Attributes\InputIgnore; use Astral\Serialize\Attributes\OutputIgnore; use Astral\Serialize\Serialize; - class User extends Serialize { public string $name; - // 输入时忽略的字段 + // Fields ignored during input #[InputIgnore] public string $internalId; - // 输出时忽略的字段 + // Fields ignored during output #[OutputIgnore] public string $tempData; } -// 创建用户对象 +// Create user object $user = User::from([ - 'name' => '张三', - 'internalId' => 'secret123', // 这个字段会被忽略 - 'tempData' => 'temporary' // 这个字段会被忽略 + 'name' => 'John', + 'internalId' => 'secret123', // This field will be ignored + 'tempData' => 'temporary' // This field will be ignored ]); -echo $user->internalId; // 这里会输出 '' +echo $user->internalId; // This will output '' -// 转换为数组 +// Convert to array $userArray = $user->toArray(); -// $userArray 的内容: +// $userArray contents: // [ -// 'name' => '张三', +// 'name' => 'John', // 'internalId' => '', // ] ``` -##### 分组忽略 +##### Group Ignoring -忽略分组需要搭配Groups注解一起使用 +Group ignoring needs to be used in conjunction with the Groups annotation ```php use Astral\Serialize\Attributes\Input\InputIgnore; @@ -1241,251 +1156,68 @@ class ComplexUser extends Serialize { #[InputIgnore] public string $globalInputIgnore; - #[OutputIgnore] + #[OutputIgnore] public string $globalOutputIgnore; } -// 默认分组 +// Default group $complexUser = ComplexUser::from([ - 'name' => '张三', + 'name' => 'John', 'secretKey' => 'confidential', - 'sensitiveInfo' => '机密信息', - 'globalInputIgnore' => '全局输入忽略', - 'globalOutputIgnore' => '全局输出忽略' + 'sensitiveInfo' => 'Sensitive information', + 'globalInputIgnore' => 'Global input ignore', + 'globalOutputIgnore' => 'Global output ignore' ]); -echo $complexUser->globalInputIgnore; // 输出 ‘’ -echo $complexUser->globalOutputIgnore; // 输出 ‘全局输出忽略’ +echo $complexUser->globalInputIgnore; // Output '' +echo $complexUser->globalOutputIgnore; // Output 'Global output ignore' $complexUser = $complexUser->toArray(); -// $complexUser 的内容: +// $complexUser contents: // [ -// 'name' => '张三', +// 'name' => 'John', // 'secretKey' => 'confidential', -// 'sensitiveInfo' => '机密信息', +// 'sensitiveInfo' => 'Sensitive information', // 'globalInputIgnore' => '', // ] - -// 使用admin分组 +// Use admin group $complexUser = ComplexUser::setGroups('admin')->from([ - 'name' => '张三', + 'name' => 'John', 'secretKey' => 'confidential', - 'sensitiveInfo' => '机密信息' - 'globalInputIgnore' => '全局输入忽略', - 'globalOutputIgnore' => '全局输出忽略' + 'sensitiveInfo' => 'Sensitive information', + 'globalInputIgnore' => 'Global input ignore', + 'globalOutputIgnore' => 'Global output ignore' ]); $complexUser = $complexUser->toArray(); -// $complexUser 的内容: +// $complexUser contents: // [ // 'name' => '', // 'secretKey' => 'confidential', -// 'globalInputIgnore' => '', +// 'globalInputIgnore' => '', // ] -// 使用public分组 +// Use public group $complexUser = ComplexUser::setGroups('public')->from([ - 'name' => '张三', + 'name' => 'John', 'secretKey' => 'confidential', - 'sensitiveInfo' => '机密信息' - 'globalInputIgnore' => '全局输入忽略', - 'globalOutputIgnore' => '全局输出忽略' + 'sensitiveInfo' => 'Sensitive information', + 'globalInputIgnore' => 'Global input ignore', + 'globalOutputIgnore' => 'Global output ignore' ]); $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 的内容: +// $complexUser contents: // [ -// 'registeredAt' => '2023-08-16 18:00:00', // 转换为上海时区 -// 'birthDate' => '1990-08-15' +// 'name' => 'John', +// 'globalInputIgnore' => '', // ] ``` -## Faker +### Nested Object Mocking -### 简单属性模拟 - -```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'] -// ] -// ] -``` - -### 嵌套对象模拟 - -#### 基本用法 +#### Basic Usage ```php class ComplexUserFaker extends Serialize { @@ -1494,7 +1226,7 @@ class ComplexUserFaker extends Serialize { } ``` -#### 演示实例 +#### Demonstration Example ```php use Astral\Serialize\Serialize; @@ -1519,13 +1251,12 @@ class ComplexUserFaker extends Serialize { #[FakerObject(UserTag::class)] public UserTag|UserProfile $primaryTag; - } $complexUserFaker = ComplexUserFaker::faker(); $complexUserFakerArray = $complexUserFaker->toArray(); -// $complexUserFakerArray 的内容: +// $complexUserFakerArray contents: // [ // 'profile' => UserProfile Object ( // ['nickname' => 'RandomNickname', 'age' => 28, 'email' => 'random.user@example.com', 'avatar' => 'https://example.com/avatars/random-avatar.jpg'] @@ -1536,7 +1267,7 @@ $complexUserFakerArray = $complexUserFaker->toArray(); // ] ``` -### Faker类方法模拟 +### Faker Class Method Mocking ```php class UserService { @@ -1551,7 +1282,7 @@ class UserFaker extends Serialize { } ``` -#### 完整的示例 +#### Complete Example ```php use Astral\Serialize\Serialize; @@ -1559,7 +1290,7 @@ use Astral\Serialize\Attributes\Faker\FakerMethod; use Astral\Serialize\Attributes\Faker\FakerObject; use Astral\Serialize\Attributes\Faker\FakerCollection; -// 用户配置文件类 +// User Profile Class class UserProfile extends Serialize { public string $nickname; public int $age; @@ -1567,7 +1298,7 @@ class UserProfile extends Serialize { public array $types = ['type1' => 'money', 'type2' => 'score']; } -// 用户服务类,提供数据生成方法 +// User Service Class, providing data generation methods class UserService { public function generateUserData(): array { return [ @@ -1593,35 +1324,35 @@ class UserService { } } -// Faker 方法模拟示例 +// Faker Method Mocking Example class UserFaker extends Serialize { - // 使用方法生成简单数据 + // Use method to generate simple data #[FakerMethod(UserService::class, 'generateUserData')] public array $userData; - // 使用方法生成对象 + // Use method to generate object #[FakerMethod(UserService::class, 'generateUserProfile')] public UserProfile $userProfile; - // 获取指定属性 - #[FakerMethod(UserService::class, 'generateUserProfile',returnType:'age')] + // Get specific attribute + #[FakerMethod(UserService::class, 'generateUserProfile', returnType: 'age')] public int $age; - // 获取指定属性 多级可以使用[.]链接 - #[FakerMethod(UserService::class, 'generateUserProfile',returnType:'types.type2')] + // Get specific attribute with multi-level access using [.] + #[FakerMethod(UserService::class, 'generateUserProfile', returnType: 'types.type2')] public string $type2; - // 传入参数 - #[FakerMethod(UserService::class, 'generateUserList',params:['count'=> 3])] + // Pass parameters + #[FakerMethod(UserService::class, 'generateUserList', params: ['count' => 3])] public array $userList; } -// 生成模拟数据 +// Generate mock data $userFaker = UserFaker::faker(); -// 转换为数组 +// Convert to array $userFakerArray = $userFaker->toArray(); -// $userFakerArray 的内容: +// $userFakerArray contents: // [ // 'userData' => [ // 'name' => 'Generated User', @@ -1631,12 +1362,12 @@ $userFakerArray = $userFaker->toArray(); // 'userProfile' => UserProfile Object ( // [ // 'nickname' => 'GeneratedNickname', -// 'age' => 25, // 随机生成 +// 'age' => 25, // Randomly generated // 'email' => 'profile@example.com' // 'types' => ['type1' => 'money', 'type2' => 'score'] // ] // ), -// 'age' => 99 , // 随机生成 +// 'age' => 99 , // Randomly generated // 'type2' => 'score', // 'userList' => [ // ['name' => 'User 0', 'email' => 'user0@example.com'], @@ -1644,4 +1375,3 @@ $userFakerArray = $userFaker->toArray(); // ['name' => 'User 2', 'email' => 'user2@example.com'] // ] // ] -``` diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 0673a8d..1f4abc1 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -1,5 +1,5 @@ # Summary * [介绍](README.md) -* [快速开始](getting-started.md) -* [进阶使用](base-mapper.md) \ No newline at end of file +* [Quick Start](getting-started.md) +* [Type Conversion](base-mapper.md) \ No newline at end of file From e684ad1f541ba4b4166fbf328507fa1dd557bfde Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 16:12:56 +0800 Subject: [PATCH 31/43] gitbook test --- docs/en/SUMMARY.md | 2 +- docs/en/description.md | 16 ++++++++++++++++ docs/zh/SUMMARY.md | 2 +- docs/zh/description.md | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 docs/en/description.md create mode 100644 docs/zh/description.md diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 1f4abc1..3ecce96 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -1,5 +1,5 @@ # Summary -* [介绍](README.md) +* [介绍](description.md) * [Quick Start](getting-started.md) * [Type Conversion](base-mapper.md) \ No newline at end of file diff --git a/docs/en/description.md b/docs/en/description.md new file mode 100644 index 0000000..aac5fa9 --- /dev/null +++ b/docs/en/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/zh/SUMMARY.md b/docs/zh/SUMMARY.md index 0673a8d..95a486f 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -1,5 +1,5 @@ # Summary -* [介绍](README.md) +* [介绍](description.md) * [快速开始](getting-started.md) * [进阶使用](base-mapper.md) \ 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 From 9d9d261d1965c44f72dcea5a0da4003c8dcf670a Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 16:22:05 +0800 Subject: [PATCH 32/43] gitbook test --- docs/zh/README.md | 81 ----------------- docs/zh/SUMMARY.md | 11 ++- docs/zh/mapper/array-mapper.md | 132 ++++++++++++++++++++++++++++ docs/zh/{ => mapper}/base-mapper.md | 0 docs/zh/mapper/enum-mapper.md | 82 +++++++++++++++++ docs/zh/mapper/null-mapper.md | 78 ++++++++++++++++ docs/zh/mapper/union-mapper.md | 108 +++++++++++++++++++++++ 7 files changed, 410 insertions(+), 82 deletions(-) create mode 100644 docs/zh/mapper/array-mapper.md rename docs/zh/{ => mapper}/base-mapper.md (100%) create mode 100644 docs/zh/mapper/enum-mapper.md create mode 100644 docs/zh/mapper/null-mapper.md create mode 100644 docs/zh/mapper/union-mapper.md diff --git a/docs/zh/README.md b/docs/zh/README.md index 60576c3..43d49ac 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -220,88 +220,7 @@ class Profile extends Serialize { 无论使用哪种方式,`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 值转换规则详细示例 diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md index 95a486f..e4c7886 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -1,5 +1,14 @@ # Summary +## Use headings to create page groups like this one + * [介绍](description.md) * [快速开始](getting-started.md) -* [进阶使用](base-mapper.md) \ No newline at end of file + +## 属性转换 + +* [基本类型转换](mapper/base-mapper.md) +* [Null值转换](mapper/null-mapper.md) +* [枚举转换](mapper/enum-mapper.md) +* [数组对象转换](mapper/array-mapper.md) +* [联合类型转换](mapper/union-mapper.md) \ 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..539f2fb --- /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/base-mapper.md b/docs/zh/mapper/base-mapper.md similarity index 100% rename from docs/zh/base-mapper.md rename to docs/zh/mapper/base-mapper.md diff --git a/docs/zh/mapper/enum-mapper.md b/docs/zh/mapper/enum-mapper.md new file mode 100644 index 0000000..a918fef --- /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..2f11deb --- /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..48ea6ac --- /dev/null +++ b/docs/zh/mapper/union-mapper.md @@ -0,0 +1,108 @@ +#### 联合类型 + +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 From 86de98cc3755e4d448b1b90e85c4742624a80c09 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 16:35:24 +0800 Subject: [PATCH 33/43] gitbook test --- docs/zh/SUMMARY.md | 17 +- docs/zh/annotation/alisa-annotation.md | 141 +++++++++++++++++ docs/zh/annotation/date-annotation.md | 92 +++++++++++ docs/zh/annotation/group-annotation.md | 191 +++++++++++++++++++++++ docs/zh/annotation/ignore-annotation.md | 140 +++++++++++++++++ docs/zh/annotation/mapper-annotation.md | 198 ++++++++++++++++++++++++ docs/zh/faker/collection-faker.md | 49 ++++++ docs/zh/faker/method-faker.md | 109 +++++++++++++ docs/zh/faker/nested-faker.md | 52 +++++++ docs/zh/faker/value-faker.md | 36 +++++ 10 files changed, 1024 insertions(+), 1 deletion(-) create mode 100644 docs/zh/annotation/alisa-annotation.md create mode 100644 docs/zh/annotation/date-annotation.md create mode 100644 docs/zh/annotation/group-annotation.md create mode 100644 docs/zh/annotation/ignore-annotation.md create mode 100644 docs/zh/annotation/mapper-annotation.md create mode 100644 docs/zh/faker/collection-faker.md create mode 100644 docs/zh/faker/method-faker.md create mode 100644 docs/zh/faker/nested-faker.md create mode 100644 docs/zh/faker/value-faker.md diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md index e4c7886..1adba5a 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -11,4 +11,19 @@ * [Null值转换](mapper/null-mapper.md) * [枚举转换](mapper/enum-mapper.md) * [数组对象转换](mapper/array-mapper.md) -* [联合类型转换](mapper/union-mapper.md) \ No newline at end of file +* [联合类型转换](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) + +## 参数快速Faker + +* [简单属性Faker](faker/value-faker.md) +* [集合Faker](faker/collection-faker.md) +* [嵌套Faker](faker/nested-faker.md) +* [方法Faker](faker/method-faker.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..e665b18 --- /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/date-annotation.md b/docs/zh/annotation/date-annotation.md new file mode 100644 index 0000000..a8de4f1 --- /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..c6b48ac --- /dev/null +++ b/docs/zh/annotation/group-annotation.md @@ -0,0 +1,191 @@ +#### 属性分组 + +属性分组提供了一种灵活的方式来控制属性的输入和输出行为,允许在不同场景下精细地管理数据转换。 + +##### 基本用法 + +在属性上使用 `#[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 +// ]) +// ] +``` \ 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..0cd5eb2 --- /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..e2827db --- /dev/null +++ b/docs/zh/annotation/mapper-annotation.md @@ -0,0 +1,198 @@ +##### 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; +} +``` \ 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..ec68dfe --- /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..cdd8020 --- /dev/null +++ b/docs/zh/faker/method-faker.md @@ -0,0 +1,109 @@ +### 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..216abd2 --- /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..2b79213 --- /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 From be44e205e352b2e84998a917f345db20a2bdecf1 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 16:36:56 +0800 Subject: [PATCH 34/43] gitbook test --- docs/zh/README.md | 1574 +------------------------------------------- docs/zh/SUMMARY.md | 4 +- 2 files changed, 13 insertions(+), 1565 deletions(-) diff --git a/docs/zh/README.md b/docs/zh/README.md index 43d49ac..9b10cc7 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -1,1566 +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('名称太短'); - } - } -} -``` - -## 生成openapi文档 - -#### 创建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(); - } -} -``` - -## 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` 类都能正常工作,并提供相同的类型转换和序列化功能。 - - - -#### 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 index 1adba5a..e7851c7 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -1,8 +1,6 @@ # Summary -## Use headings to create page groups like this one - -* [介绍](description.md) +* [介绍](README.md) * [快速开始](getting-started.md) ## 属性转换 From ca7e79fe1742c0f88f4af45a232db6826be8a914 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:14:31 +0800 Subject: [PATCH 35/43] gitbook zh success --- README.md | 2 +- docs/zh/SUMMARY.md | 9 ++- docs/zh/annotation/alisa-annotation.md | 8 +-- docs/zh/annotation/customer-annotation.md | 73 +++++++++++++++++++++++ docs/zh/annotation/date-annotation.md | 6 +- docs/zh/annotation/group-annotation.md | 6 +- docs/zh/annotation/ignore-annotation.md | 6 +- docs/zh/annotation/mapper-annotation.md | 62 +++++++++---------- docs/zh/faker/collection-faker.md | 2 +- docs/zh/faker/method-faker.md | 6 +- docs/zh/faker/nested-faker.md | 6 +- docs/zh/faker/value-faker.md | 2 +- docs/zh/mapper/array-mapper.md | 4 +- docs/zh/mapper/base-mapper.md | 10 ++-- docs/zh/mapper/enum-mapper.md | 6 +- docs/zh/mapper/null-mapper.md | 4 +- docs/zh/mapper/union-mapper.md | 10 +--- docs/zh/openapi/base-openapi.md | 51 ++++++++++++++++ 18 files changed, 201 insertions(+), 72 deletions(-) create mode 100644 docs/zh/annotation/customer-annotation.md create mode 100644 docs/zh/openapi/base-openapi.md diff --git a/README.md b/README.md index 20a1a89..73c0688 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # Languages -- [Complete documen-English](./docs/en/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/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md index e7851c7..7a4f9db 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -14,14 +14,19 @@ ## 注解类使用 * [属性分组](annotation/group-annotation.md) -* [输入/输入出名称映射](annotation/alisa-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](faker/value-faker.md) * [集合Faker](faker/collection-faker.md) * [嵌套Faker](faker/nested-faker.md) -* [方法Faker](faker/method-faker.md) \ No newline at end of file +* [方法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 index e665b18..ff61fb7 100644 --- a/docs/zh/annotation/alisa-annotation.md +++ b/docs/zh/annotation/alisa-annotation.md @@ -1,6 +1,6 @@ -#### 名称映射 +## 名称映射 -##### 基础使用 +### 基础使用 ```php use Astral\Serialize\Attributes\InputName; @@ -39,7 +39,7 @@ $userArray = $user->toArray(); // ] ``` -##### 多输入/输出名称处理 +### 多输入/输出名称处理 ```php use Astral\Serialize\Attributes\InputName; @@ -104,7 +104,7 @@ $userArray = $user->toArray(); // ] ``` -##### 复杂映射场景 +### 复杂映射场景 ```php use Astral\Serialize\Serialize; 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 index a8de4f1..1d37142 100644 --- a/docs/zh/annotation/date-annotation.md +++ b/docs/zh/annotation/date-annotation.md @@ -1,4 +1,4 @@ -#### 时间转换 +## 时间转换 1. 格式灵活性 支持多种输入和输出时间格式 @@ -10,7 +10,7 @@ 自动将字符串转换为 DateTime 对象 保证类型的一致性和正确性 -##### 基础使用 +### 基础使用 ```php use Astral\Serialize\Attributes\Input\InputDateFormat; @@ -57,7 +57,7 @@ $orderArray = $order->toArray(); // ] ``` -##### 带时区的时间转换 +### 带时区的时间转换 ```php diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index c6b48ac..3cfd040 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -1,8 +1,8 @@ -#### 属性分组 +## 属性分组 属性分组提供了一种灵活的方式来控制属性的输入和输出行为,允许在不同场景下精细地管理数据转换。 -##### 基本用法 +### 基本用法 在属性上使用 `#[Groups]` 注解来指定属性所属的分组。 @@ -148,7 +148,7 @@ $multiGroupArray = $user4->toArray(); // ] ``` -##### 嵌套类指定Group类展示 +### 嵌套类指定Group类展示 ```php class ComplexUser extends Serialize { diff --git a/docs/zh/annotation/ignore-annotation.md b/docs/zh/annotation/ignore-annotation.md index 0cd5eb2..7bfec48 100644 --- a/docs/zh/annotation/ignore-annotation.md +++ b/docs/zh/annotation/ignore-annotation.md @@ -1,4 +1,4 @@ -#### 字段忽略 +## 字段忽略 1. **安全性控制** - 防止敏感信息的意外泄露 @@ -12,7 +12,7 @@ - 减少不必要字段的序列化开销 - 精简数据传输 -##### 基础使用 +### 基础使用 ```php use Astral\Serialize\Attributes\InputIgnore; @@ -51,7 +51,7 @@ $userArray = $user->toArray(); // ] ``` -##### 分组忽略 +### 分组忽略 忽略分组需要搭配Groups注解一起使用 diff --git a/docs/zh/annotation/mapper-annotation.md b/docs/zh/annotation/mapper-annotation.md index e2827db..0d6448d 100644 --- a/docs/zh/annotation/mapper-annotation.md +++ b/docs/zh/annotation/mapper-annotation.md @@ -1,4 +1,6 @@ -##### Mapper映射 +### Mapper映射 + +### 属性映射 ```php use Astral\Serialize\Attributes\InputName; @@ -46,7 +48,7 @@ $userArray = $user->toArray(); // ] ``` -##### 全局类映射 +### 全局类映射 ```php use Astral\Serialize\Attributes\InputName; @@ -88,33 +90,7 @@ $userArray = $user->toArray(); // ] ``` -###### 属性映射大于类级映射 - -```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`注解一起使用 @@ -180,7 +156,7 @@ $complexUser = $complexUser->toArray(); // 'userEmail' => 张三, // ] ``` -#### 自定义映射器 +### 自定义映射器 ```php // 自定义映射器 需要继承NameMapper 并实现 resolve @@ -195,4 +171,30 @@ 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/faker/collection-faker.md b/docs/zh/faker/collection-faker.md index ec68dfe..a7b2126 100644 --- a/docs/zh/faker/collection-faker.md +++ b/docs/zh/faker/collection-faker.md @@ -1,4 +1,4 @@ -### 集合模拟 +## 集合模拟 ```php diff --git a/docs/zh/faker/method-faker.md b/docs/zh/faker/method-faker.md index cdd8020..5d7de3e 100644 --- a/docs/zh/faker/method-faker.md +++ b/docs/zh/faker/method-faker.md @@ -1,4 +1,6 @@ -### Faker类方法模拟 +## Faker类方法模拟 + +### 基本用法 ```php class UserService { @@ -13,7 +15,7 @@ class UserFaker extends Serialize { } ``` -#### 完整的示例 +### 完整的示例 ```php use Astral\Serialize\Serialize; diff --git a/docs/zh/faker/nested-faker.md b/docs/zh/faker/nested-faker.md index 216abd2..b479a52 100644 --- a/docs/zh/faker/nested-faker.md +++ b/docs/zh/faker/nested-faker.md @@ -1,6 +1,6 @@ -### 嵌套对象模拟 +## 嵌套对象模拟 -#### 基本用法 +### 基本用法 ```php class ComplexUserFaker extends Serialize { @@ -9,7 +9,7 @@ class ComplexUserFaker extends Serialize { } ``` -#### 演示实例 +### 演示实例 ```php use Astral\Serialize\Serialize; diff --git a/docs/zh/faker/value-faker.md b/docs/zh/faker/value-faker.md index 2b79213..8645904 100644 --- a/docs/zh/faker/value-faker.md +++ b/docs/zh/faker/value-faker.md @@ -1,4 +1,4 @@ -### 简单属性模拟 +## 简单属性模拟 ```php class UserFaker extends Serialize { diff --git a/docs/zh/mapper/array-mapper.md b/docs/zh/mapper/array-mapper.md index 539f2fb..17434cc 100644 --- a/docs/zh/mapper/array-mapper.md +++ b/docs/zh/mapper/array-mapper.md @@ -1,6 +1,6 @@ -#### 数组对象转换 +## 数组对象转换 -##### phpDoc定义 +### phpDoc定义 ```php use Astral\Serialize\Serialize; diff --git a/docs/zh/mapper/base-mapper.md b/docs/zh/mapper/base-mapper.md index 9fca977..4dfc598 100644 --- a/docs/zh/mapper/base-mapper.md +++ b/docs/zh/mapper/base-mapper.md @@ -1,8 +1,8 @@ -### 类型转换 +## 类型转换 -#### 基本类型转换 +### 基本类型转换 -##### 方式一:构造函数属性提升 +#### 方式一:构造函数属性提升 ```php use Astral\Serialize\Serialize; @@ -17,7 +17,7 @@ class Profile extends Serialize { } ``` -##### 方式二:传统属性定义 +#### 方式二:传统属性定义 ```php use Astral\Serialize\Serialize; @@ -41,7 +41,7 @@ $profile = Profile::from([ $profileArray = $profile->toArray(); ``` -##### 方式三:只读属性 +#### 方式三:只读属性 ```php use Astral\Serialize\Serialize; diff --git a/docs/zh/mapper/enum-mapper.md b/docs/zh/mapper/enum-mapper.md index a918fef..f2b2b2b 100644 --- a/docs/zh/mapper/enum-mapper.md +++ b/docs/zh/mapper/enum-mapper.md @@ -1,4 +1,4 @@ -#### 枚举转换 +## 枚举转换 枚举转换提供了强大且灵活的枚举处理机制,支持多种枚举类型和转换场景。 @@ -7,7 +7,7 @@ - 输出时自动将枚举转换为字符串(枚举名称) - 提供灵活且安全的枚举处理机制 -##### 普通枚举 +### 普通枚举 ```php enum UserRole { @@ -39,7 +39,7 @@ $complexUserArray = $complexUser->toArray(); // ] ``` -##### 回退枚举 +### 回退枚举 ```php use Astral\Serialize\Serialize; diff --git a/docs/zh/mapper/null-mapper.md b/docs/zh/mapper/null-mapper.md index 2f11deb..728e5c9 100644 --- a/docs/zh/mapper/null-mapper.md +++ b/docs/zh/mapper/null-mapper.md @@ -1,4 +1,4 @@ -#### Null值转换规则详细示例 +## Null值转换规则详细示例 当属性不是可空类型(`?type`)时,`null` 值会根据目标类型自动转换: @@ -39,7 +39,7 @@ try { } ``` -#### 可空类型的方案 +## 可空类型的方案 对于需要接受 `null` 的场景,使用可空类型: diff --git a/docs/zh/mapper/union-mapper.md b/docs/zh/mapper/union-mapper.md index 48ea6ac..08d838e 100644 --- a/docs/zh/mapper/union-mapper.md +++ b/docs/zh/mapper/union-mapper.md @@ -1,12 +1,8 @@ -#### 联合类型 +## 联合类型 1. 可以混合使用基本类型和对象类型 -2. 对象层级匹配 - 对于多个对象类型,会选择最匹配的类型 - 支持继承层级的智能匹配 -3. 动态类型处理 - 自动处理不同类型的输入 - 提供更加灵活的数据建模方式 +2. 对象层级匹配。对于多个对象类型,会选择最匹配的类型。支持继承层级的智能匹配 +3. 动态类型处理,自动处理不同类型的,输入提供更加灵活的数据建模方式 ```php use Astral\Serialize\Serialize; 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 From cab96856863a4126635aec960800c5d6681e9988 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:15:44 +0800 Subject: [PATCH 36/43] gitbook zh success --- docs/zh/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md index 7a4f9db..45772e4 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -20,7 +20,7 @@ * [时间格式映射](annotation/date-annotation.md) * [自定义注解](annotation/customer-annotation.md) -## 参数快速Faker +## 参数快速模拟生成 * [简单属性Faker](faker/value-faker.md) * [集合Faker](faker/collection-faker.md) From 3220fe41ac6a5a44343e6b6e04bbee2244e72767 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:27:22 +0800 Subject: [PATCH 37/43] gitbook zh success --- docs/zh/annotation/group-annotation.md | 255 +++++++++++-------------- 1 file changed, 113 insertions(+), 142 deletions(-) diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index 3cfd040..a4c5543 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -1,10 +1,20 @@ -## 属性分组 +## 属性分组(Groups) 属性分组提供了一种灵活的方式来控制属性的输入和输出行为,允许在不同场景下精细地管理数据转换。 -### 基本用法 +--- -在属性上使用 `#[Groups]` 注解来指定属性所属的分组。 +### 🧠 分组原理说明 + +- 使用 `#[Groups(...)]` 注解可将属性归类到一个或多个分组中。 +- 支持: + - **输入时** 按分组过滤数据字段 + - **输出时** 按分组筛选输出字段 +- 未指定分组的属性将自动归入 `"default"` 分组。 + +--- + +### ✨ 基本示例 ```php use Astral\Serialize\Attributes\Groups; @@ -12,180 +22,141 @@ use Astral\Serialize\Serialize; class User extends Serialize { - #[Groups('update','detail')] + #[Groups('update', 'detail')] public string $id; #[Groups('create', 'update', 'detail')] public string $name; - #[Groups('create','detail')] + #[Groups('create', 'detail')] public string $username; #[Groups('other')] public string $sensitiveData; - // 没有指定Group 的属性将会被默认分组在default分组中 + // 未指定分组,默认为 default 分组 public string $noGroupInfo; - // 构造函数参数也支持分组 public function __construct( - #[Groups('create','detail')] + #[Groups('create', 'detail')] public readonly string $email, - - #[Groups('update','detail')] + + #[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', +] +*/ +``` + +### 按分组输出 -// 使用 默认分组展示所有信息 -$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' => '机密信息', -// ] +```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' => '机密信息', +] +*/ ``` -### 嵌套类指定Group类展示 +### 嵌套对象的分组 ```php class ComplexUser extends Serialize { - public string $name; - public int $sex; - public ComplexNestedInfo $info; } class ComplexNestedInfo extends Serialize { - - #[Groups(ComplexAUser::class)] + #[Groups(ComplexUser::class)] public float $money; public string $currency; } - -// ComplexNestedInfo 会自动隐藏currency -$adminUser = ComplexUser::from( - name: '张三', - sex: 1, - info: [ +php +复制 +编辑 +$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 -// ]) -// ] + ], +]); + +// 默认输出包含所有字段 +$adminUser->toArray(); +/* +[ + 'name' => '张三', + 'sex' => 1, + 'info' => [ + 'money' => 100.00 + ] +] +*/ ``` \ No newline at end of file From 171df3cb1d08c15d76efa47f92655221fc55c285 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:28:11 +0800 Subject: [PATCH 38/43] gitbook zh success --- docs/zh/annotation/group-annotation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index a4c5543..6e6ddfd 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -47,7 +47,7 @@ class User extends Serialize { } ``` -### 按分组接收 +### ✨按分组接收 ```php // 使用 create 分组创建用户,只接受 group=create 的字段 @@ -71,7 +71,7 @@ $user->toArray(); */ ``` -### 按分组输出 +### ✨按分组输出 ```php $user = User::from([ @@ -121,7 +121,7 @@ $user->withGroups(['detail', 'other'])->toArray(); */ ``` -### 嵌套对象的分组 +### ✨嵌套对象的分组 ```php class ComplexUser extends Serialize { From 4c4d50bf91b66c4a930272053c2fb1dd8f3a7484 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:28:42 +0800 Subject: [PATCH 39/43] gitbook zh success --- docs/zh/annotation/group-annotation.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index 6e6ddfd..99ee1fb 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -136,9 +136,7 @@ class ComplexNestedInfo extends Serialize { public string $currency; } -php -复制 -编辑 + $adminUser = ComplexUser::from([ 'name' => '张三', 'sex' => 1, From 7d74bbcb0186e4d2e20eaae9cd37ff740881cd26 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:31:49 +0800 Subject: [PATCH 40/43] gitbook zh success --- docs/zh/annotation/mapper-annotation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/zh/annotation/mapper-annotation.md b/docs/zh/annotation/mapper-annotation.md index 0d6448d..61257d7 100644 --- a/docs/zh/annotation/mapper-annotation.md +++ b/docs/zh/annotation/mapper-annotation.md @@ -1,4 +1,4 @@ -### Mapper映射 +## Mapper映射 ### 属性映射 @@ -106,7 +106,6 @@ use Astral\Serialize\Support\Mappers\{ }; use Astral\Serialize\Serialize; - #[InputName(SnakeCaseMapper::class, groups: 'external')] #[InputName(CamelCaseMapper::class, groups: 'api')] #[OutputName(PascalCaseMapper::class, groups: ['external','api'])] From a3722bb59d03016d5d1da223dbdaadb373f1c5bf Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:32:18 +0800 Subject: [PATCH 41/43] gitbook zh success --- docs/zh/annotation/group-annotation.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index 99ee1fb..9be3d23 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -4,7 +4,7 @@ --- -### 🧠 分组原理说明 +### 分组原理说明 - 使用 `#[Groups(...)]` 注解可将属性归类到一个或多个分组中。 - 支持: @@ -14,7 +14,7 @@ --- -### ✨ 基本示例 +### 基本示例 ```php use Astral\Serialize\Attributes\Groups; @@ -47,7 +47,7 @@ class User extends Serialize { } ``` -### ✨按分组接收 +### 按分组接收 ```php // 使用 create 分组创建用户,只接受 group=create 的字段 @@ -71,7 +71,7 @@ $user->toArray(); */ ``` -### ✨按分组输出 +### 按分组输出 ```php $user = User::from([ @@ -121,7 +121,7 @@ $user->withGroups(['detail', 'other'])->toArray(); */ ``` -### ✨嵌套对象的分组 +### 嵌套对象的分组 ```php class ComplexUser extends Serialize { From 8fc847527ceb3ce316920e5221fa75cee8d490b7 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:38:27 +0800 Subject: [PATCH 42/43] gitbook zh success --- docs/zh/annotation/group-annotation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index 9be3d23..8ef2be5 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -146,7 +146,8 @@ $adminUser = ComplexUser::from([ ], ]); -// 默认输出包含所有字段 +// info只会输出$money +// 因为ComplexNestedInfo 绑定了 ComplexUser的类Group $adminUser->toArray(); /* [ From 771941534b6212af5a9853ea45e87094bfddd40c Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 20 Jun 2025 17:38:50 +0800 Subject: [PATCH 43/43] gitbook zh success --- docs/zh/annotation/group-annotation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/annotation/group-annotation.md b/docs/zh/annotation/group-annotation.md index 8ef2be5..e38b948 100644 --- a/docs/zh/annotation/group-annotation.md +++ b/docs/zh/annotation/group-annotation.md @@ -147,7 +147,7 @@ $adminUser = ComplexUser::from([ ]); // info只会输出$money -// 因为ComplexNestedInfo 绑定了 ComplexUser的类Group +// 因为 ComplexNestedInfo 绑定了 ComplexUser的类Group $adminUser->toArray(); /* [