diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..9a152e5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,36 @@ +version: 2.1 + +orbs: + ci-caching: jobcloud/ci-caching@1.0.2 + ci-php: jobcloud/ci-php@0.29 + +workflows: + test-avro-validator: + jobs: + - ci-caching/build-docker-images: + dockerComposeFile: "./docker/docker-compose.yml" + - ci-php/install-dependencies: + dockerComposeFile: "./docker/docker-compose.yml" + dependencyCheckSumFile: "./composer.json" + requires: + - ci-caching/build-docker-images + - ci-php/coverage: + dockerComposeFile: "./docker/docker-compose.yml" + dependencyCheckSumFile: "./composer.json" + requires: + - ci-php/install-dependencies + - ci-php/code-style: + dockerComposeFile: "./docker/docker-compose.yml" + dependencyCheckSumFile: "./composer.json" + requires: + - ci-php/install-dependencies + - ci-php/static-analysis: + dockerComposeFile: "./docker/docker-compose.yml" + dependencyCheckSumFile: "./composer.json" + requires: + - ci-php/install-dependencies + - ci-php/infection-testing: + dockerComposeFile: "./docker/docker-compose.yml" + dependencyCheckSumFile: "./composer.json" + requires: + - ci-php/install-dependencies diff --git a/.gitignore b/.gitignore index 4c36e38..0be92be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ vendor/ +build/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ae19a2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 JobCloud AG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..62eac28 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +.PHONY: clean code-style coverage help test test-unit test-integration static-analysis infection-testing install-dependencies update-dependencies +.DEFAULT_GOAL := help + +PHPSPEC = ./vendor/bin/phpspec run --format dot -vvv -c phpspec.yml +PHPSTAN = ./vendor/bin/phpstan +PHPCS = ./vendor/bin/phpcs --extensions=php +INFECTION = ./vendor/bin/infection +CONSOLE = ./bin/console + +clean: + rm -rf ./build ./vendor + +fix-code-style: + ${PHPCBF} + +code-style: + mkdir -p build/logs/phpcs + ${PHPCS} + +coverage: + mkdir -p build/logs/phpspec/coverage + php -dpcov.enabled=1 -dpcov.directory=./src ${PHPSPEC} + ./vendor/bin/coverage-check build/logs/phpspec/coverage/coverage.xml 98 + +test: test-unit test-integration + +test-unit: + ${PHPSPEC} --no-coverage + +infection-testing: + make coverage + cp -f build/logs/phpspec/coverage/xml/index.xml build/logs/phpspec/coverage/junit.xml + ${INFECTION} --test-framework=phpspec --only-covered --coverage=build/logs/phpspec/coverage --min-msi=88 --threads=`nproc` + +static-analysis: + ${PHPSTAN} analyse src --no-progress + +install-dependencies: + composer install + +update-dependencies: + composer update + +help: + # Usage: + # make [OPTION=value] + # + # Targets: + # clean Cleans the coverage and the vendor directory + # code-style Check code style using phpcs + # coverage Generate code coverage (html, clover) + # help You're looking at it! + # test (default) Run all the tests + # test-unit Run the unit tests with phpspec + # static-analysis Run static analysis using phpstan + # install-dependencies Run composer install + # update-dependencies Run composer update diff --git a/README.md b/README.md index 5ca4ff7..eead57d 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,32 @@ var_dump($validator->validate( 'marketplace.ecommerce.entity.order' )); ``` + +## Command +There's a command making use of the validator shipped with this package: + +``` +$ bin/avro-validator validate + +Description: + Validates a payload against a schema + +Usage: + validate [options] [--] [] + +Arguments: + schema Path to the schema file + namespace Schema namespace + payload Path to the payload file + +Options: + -f, --format=FORMAT Output format of the result [default: "pretty"] + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +``` + diff --git a/bin/avro-validator b/bin/avro-validator new file mode 100755 index 0000000..40c47a6 --- /dev/null +++ b/bin/avro-validator @@ -0,0 +1,19 @@ +#!/usr/bin/env php +add(new ValidateCommand()); + +$app->run(new ArgvInput(), new ConsoleOutput()); diff --git a/composer.json b/composer.json index 3456276..650df17 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,27 @@ { "name": "jobcloud/avro-validator", + "license": "MIT", + "require": { + "php": ">=7.3", + "ext-json": "*", + "symfony/console": "^5.1.5" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^0.14.3", + "friends-of-phpspec/phpspec-code-coverage": "dev-master@dev", + "phpspec/phpspec": "^6.2.1", + "phpstan/phpstan": "^0.12.11", + "phpstan/phpstan-deprecation-rules": "^0.12.2", + "phpstan/phpstan-strict-rules": "^0.12.2", + "rregeer/phpunit-coverage-check": "^0.3.1", + "squizlabs/php_codesniffer": "^3.4.2", + "infection/infection": "^0.17", + "infection/phpspec-adapter": "^0.1.1" + }, "autoload": { "psr-4": { "Jobcloud\\Avro\\Validator\\": "src/" } - } + }, + "bin": ["bin/avro-validator"] } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..33533c5 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3617 @@ +{ + "_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": "ef80af82d920f1236c138dc4afa8bf89", + "packages": [ + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://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" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "symfony/console", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "ae789a8a2ad189ce7e8216942cdb9b77319f5eb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/ae789a8a2ad189ce7e8216942cdb9b77319f5eb8", + "reference": "ae789a8a2ad189ce7e8216942cdb9b77319f5eb8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2020-09-18T14:27:32+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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" + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b740103edbdcc39602239ee8860f0f45a8eb9aa5", + "reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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" + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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" + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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" + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/string", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "4a9afe9d07bac506f75bcee8ed3ce76da5a9343e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/4a9afe9d07bac506f75bcee8ed3ce76da5a9343e", + "reference": "4a9afe9d07bac506f75bcee8ed3ce76da5a9343e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony String component", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "time": "2020-09-15T12:23:47+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "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": "2020-08-25T05:50:16+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ebd27a9866ae8254e873866f795491f02418c5a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ebd27a9866ae8254e873866f795491f02418c5a5", + "reference": "ebd27a9866ae8254e873866f795491f02418c5a5", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "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" + ], + "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": "2020-08-19T10:27:58+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "ergebnis/phpstan-rules", + "version": "0.14.4", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/phpstan-rules.git", + "reference": "72eeba43afe81837d67349b3794c2f4157a2640c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/72eeba43afe81837d67349b3794c2f4157a2640c", + "reference": "72eeba43afe81837d67349b3794c2f4157a2640c", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "nikic/php-parser": "^4.2.3", + "php": "^7.1", + "phpstan/phpstan": "~0.11.15 || ~0.12.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.3.0", + "ergebnis/license": "~0.1.0", + "ergebnis/php-cs-fixer-config": "^2.1.0", + "ergebnis/test-util": "~1.0.0", + "infection/infection": "~0.13.6", + "nette/di": "^3.0.1", + "phpstan/phpstan-deprecation-rules": "~0.11.2", + "phpstan/phpstan-strict-rules": "~0.11.1", + "phpunit/phpunit": "^7.5.20", + "psalm/plugin-phpunit": "~0.9.0", + "psr/container": "^1.0.0", + "vimeo/psalm": "^3.9.5", + "zendframework/zend-servicemanager": "^2.0.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com" + } + ], + "description": "Provides additional rules for phpstan/phpstan.", + "homepage": "https://github.com/ergebnis/phpstan-rules", + "keywords": [ + "PHPStan", + "phpstan-extreme-rules", + "phpstan-rules" + ], + "funding": [ + { + "url": "https://paypal.me/localheinz", + "type": "custom" + }, + { + "url": "https://www.amazon.de/hz/wishlist/ls/2NCHMSJ4BC1OW", + "type": "custom" + }, + { + "url": "https://www.buymeacoffee.com/localheinz", + "type": "custom" + }, + { + "url": "https://github.com/localheinz", + "type": "github" + } + ], + "time": "2020-03-13T20:00:07+00:00" + }, + { + "name": "friends-of-phpspec/phpspec-code-coverage", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/friends-of-phpspec/phpspec-code-coverage.git", + "reference": "0c310bfd4a0873021f4c475b516f5a7d4d5adf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/friends-of-phpspec/phpspec-code-coverage/zipball/0c310bfd4a0873021f4c475b516f5a7d4d5adf22", + "reference": "0c310bfd4a0873021f4c475b516f5a7d4d5adf22", + "shasum": "" + }, + "require": { + "php": ">= 7.1.3", + "phpspec/phpspec": "^5.0 || ^6.0", + "phpunit/php-code-coverage": "^6.0 || ^7.0 || ^8.0" + }, + "conflict": { + "sebastian/comparator": "< 2.0" + }, + "require-dev": { + "drupol/php-conventions": "^1.7.1 || ^1.8.16" + }, + "suggest": { + "ext-pcov": "Install PCov extension to generate code coverage.", + "ext-xdebug": "Install Xdebug to generate phpspec code coverage." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "FriendsOfPhpSpec\\PhpSpec\\CodeCoverage\\": "src/" + }, + "files": [ + "src/bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "ek9", + "email": "dev@ek9.co", + "homepage": "https://ek9.co" + }, + { + "name": "Henrik Bjornskov" + }, + { + "name": "Stéphane Hulard", + "email": "s.hulard@chstudio.fr", + "homepage": "https://chstudio.fr" + }, + { + "name": "Pol Dellaiera", + "email": "pol.dellaiera@protonmail.com", + "homepage": "https://not-a-number.io/" + }, + { + "name": "Jay Linski", + "homepage": "https://twitter.com/jay_linski" + } + ], + "description": "Generate Code Coverage reports for PhpSpec tests", + "homepage": "https://github.com/friends-of-phpspec/phpspec-code-coverage", + "keywords": [ + "code-coverage", + "coverage", + "phpspec", + "report", + "spec", + "test", + "tests" + ], + "time": "2020-09-29T12:22:31+00:00" + }, + { + "name": "infection/abstract-testframework-adapter", + "version": "0.3.1", + "source": { + "type": "git", + "url": "https://github.com/infection/abstract-testframework-adapter.git", + "reference": "c52539339f28d6b67625ff24496289b3e6d66025" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/abstract-testframework-adapter/zipball/c52539339f28d6b67625ff24496289b3e6d66025", + "reference": "c52539339f28d6b67625ff24496289b3e6d66025", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^2.16", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\AbstractTestFramework\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Abstract Test Framework Adapter for Infection", + "time": "2020-08-30T13:50:12+00:00" + }, + { + "name": "infection/extension-installer", + "version": "0.1.1", + "source": { + "type": "git", + "url": "https://github.com/infection/extension-installer.git", + "reference": "ff30c0adffcdbc747c96adf92382ccbe271d0afd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/extension-installer/zipball/ff30c0adffcdbc747c96adf92382ccbe271d0afd", + "reference": "ff30c0adffcdbc747c96adf92382ccbe271d0afd", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.9", + "friendsofphp/php-cs-fixer": "^2.16", + "infection/infection": "^0.15.2", + "php-coveralls/php-coveralls": "^2.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.10", + "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpstan/phpstan-webmozart-assert": "^0.12.2", + "phpunit/phpunit": "^8.5", + "vimeo/psalm": "^3.8" + }, + "type": "composer-plugin", + "extra": { + "class": "Infection\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Infection\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Infection Extension Installer", + "time": "2020-04-25T22:40:05+00:00" + }, + { + "name": "infection/include-interceptor", + "version": "0.2.4", + "source": { + "type": "git", + "url": "https://github.com/infection/include-interceptor.git", + "reference": "e3cf9317a7fd554ab60a5587f028b16418cc4264" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/include-interceptor/zipball/e3cf9317a7fd554ab60a5587f028b16418cc4264", + "reference": "e3cf9317a7fd554ab60a5587f028b16418cc4264", + "shasum": "" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "infection/infection": "^0.15.0", + "phan/phan": "^2.4 || ^3", + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "^0.12.8", + "phpunit/phpunit": "^8.5", + "vimeo/psalm": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\StreamWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Stream Wrapper: Include Interceptor. Allows to replace included (autoloaded) file with another one.", + "time": "2020-08-07T22:40:37+00:00" + }, + { + "name": "infection/infection", + "version": "0.17.6", + "source": { + "type": "git", + "url": "https://github.com/infection/infection.git", + "reference": "941d40af2de0890dc1dcf679850124d12c9556c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/infection/zipball/941d40af2de0890dc1dcf679850124d12c9556c3", + "reference": "941d40af2de0890dc1dcf679850124d12c9556c3", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.2", + "composer/xdebug-handler": "^1.3.3", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "infection/abstract-testframework-adapter": "^0.3.0", + "infection/extension-installer": "^0.1.0", + "infection/include-interceptor": "^0.2.4", + "justinrainbow/json-schema": "^5.2", + "nikic/php-parser": "^4.9 <4.10", + "ondram/ci-detector": "^3.3.0", + "php": "^7.3", + "sanmai/pipeline": "^3.1 || ^5.0", + "sebastian/diff": "^3.0.2 || ^4.0", + "seld/jsonlint": "^1.7", + "symfony/console": "^3.4.29 || ^4.1.19 || ^5.0", + "symfony/filesystem": "^3.4.29 || ^4.1.19 || ^5.0", + "symfony/finder": "^3.4.29 || ^4.1.19 || ^5.0", + "symfony/process": "^3.4.29 || ^4.1.19 || ^5.0", + "thecodingmachine/safe": "^1.0", + "webmozart/assert": "^1.3", + "webmozart/path-util": "^2.3" + }, + "conflict": { + "phpunit/php-code-coverage": ">9 <9.1.4", + "phpunit/phpunit": ">=9.3", + "symfony/console": "=4.1.5" + }, + "require-dev": { + "ext-simplexml": "*", + "helmich/phpunit-json-assert": "^3.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.8", + "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan-webmozart-assert": "^0.12.2", + "phpunit/phpunit": "^8.2.5 <8.4", + "symfony/phpunit-bridge": "^4.3.4 || ^5.0", + "symfony/yaml": "^5.0", + "thecodingmachine/phpstan-safe-rule": "^1.0" + }, + "bin": [ + "bin/infection" + ], + "type": "library", + "autoload": { + "psr-4": { + "Infection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com", + "homepage": "https://twitter.com/maks_rafalko" + }, + { + "name": "Oleg Zhulnev", + "homepage": "https://github.com/sidz" + }, + { + "name": "Gert de Pagter", + "homepage": "https://github.com/BackEndTea" + }, + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com", + "homepage": "https://twitter.com/tfidry" + }, + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://www.alexeykopytko.com" + }, + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.", + "keywords": [ + "coverage", + "mutant", + "mutation framework", + "mutation testing", + "testing", + "unit testing" + ], + "time": "2020-10-02T07:20:09+00:00" + }, + { + "name": "infection/phpspec-adapter", + "version": "0.1.1", + "source": { + "type": "git", + "url": "https://github.com/infection/phpspec-adapter.git", + "reference": "3c1e40774bae3a081b547903b0f19cd0ccdaeed8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/phpspec-adapter/zipball/3c1e40774bae3a081b547903b0f19cd0ccdaeed8", + "reference": "3c1e40774bae3a081b547903b0f19cd0ccdaeed8", + "shasum": "" + }, + "require": { + "infection/abstract-testframework-adapter": "^0.3.0", + "infection/include-interceptor": "^0.2.3", + "php": "^7.3", + "symfony/filesystem": "^3.4.29 || ^4.0 || ^5.0", + "symfony/process": "^3.4.29 || ^4.0 || ^5.0", + "symfony/yaml": "^3.4.29 || ^4.0 || ^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "infection/infection": "^0.15.2", + "php-coveralls/php-coveralls": "^2.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.10", + "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpstan/phpstan-webmozart-assert": "^0.12.2", + "phpunit/phpunit": "^8.5", + "thecodingmachine/safe": "^0.1.16", + "vimeo/psalm": "^3.8" + }, + "type": "infection-extension", + "extra": { + "infection": { + "class": "Infection\\TestFramework\\PhpSpec\\PhpSpecAdapterFactory" + } + }, + "autoload": { + "psr-4": { + "Infection\\TestFramework\\PhpSpec\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "PHPSpec Test Framework Adapter for Infection", + "time": "2020-03-14T19:43:56+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.10", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2020-05-27T16:41:55+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.9.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "88e519766fc58bd46b8265561fb79b54e2e00b28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/88e519766fc58bd46b8265561fb79b54e2e00b28", + "reference": "88e519766fc58bd46b8265561fb79b54e2e00b28", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-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" + ], + "time": "2020-08-30T16:15:20+00:00" + }, + { + "name": "ondram/ci-detector", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/OndraM/ci-detector.git", + "reference": "594e61252843b68998bddd48078c5058fe9028bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/OndraM/ci-detector/zipball/594e61252843b68998bddd48078c5058fe9028bd", + "reference": "594e61252843b68998bddd48078c5058fe9028bd", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.2", + "lmc/coding-standard": "^1.3 || ^2.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/extension-installer": "^1.0.3", + "phpstan/phpstan": "^0.12.0", + "phpstan/phpstan-phpunit": "^0.12.1", + "phpunit/phpunit": "^7.1 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "OndraM\\CiDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Machulda", + "email": "ondrej.machulda@gmail.com" + } + ], + "description": "Detect continuous integration environment and provide unified access to properties of current build", + "keywords": [ + "CircleCI", + "Codeship", + "Wercker", + "adapter", + "appveyor", + "aws", + "aws codebuild", + "bamboo", + "bitbucket", + "buddy", + "ci-info", + "codebuild", + "continuous integration", + "continuousphp", + "drone", + "github", + "gitlab", + "interface", + "jenkins", + "teamcity", + "travis" + ], + "time": "2020-09-04T11:21:14+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" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "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": "account@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.", + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "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", + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/php-diff", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/php-diff.git", + "reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/php-diff/zipball/fc1156187f9f6c8395886fe85ed88a0a245d72e9", + "reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Diff": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton" + } + ], + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", + "time": "2020-09-18T13:47:07+00:00" + }, + { + "name": "phpspec/phpspec", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/phpspec.git", + "reference": "a40d53c8564f97eca75919769f93410dd3dba5e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/a40d53c8564f97eca75919769f93410dd3dba5e8", + "reference": "a40d53c8564f97eca75919769f93410dd3dba5e8", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "ext-tokenizer": "*", + "php": "^7.2, <7.5", + "phpspec/php-diff": "^1.0.0", + "phpspec/prophecy": "^1.9", + "sebastian/exporter": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "symfony/console": "^3.4 || ^4.0 || ^5.0", + "symfony/event-dispatcher": "^3.4 || ^4.0 || ^5.0", + "symfony/finder": "^3.4 || ^4.0 || ^5.0", + "symfony/process": "^3.4 || ^4.0 || ^5.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0" + }, + "conflict": { + "sebastian/comparator": "<1.2.4" + }, + "require-dev": { + "behat/behat": "^3.3", + "phpunit/phpunit": "^7.0", + "symfony/filesystem": "^3.4 || ^4.0 || ^5.0" + }, + "suggest": { + "phpspec/nyan-formatters": "Adds Nyan formatters" + }, + "bin": [ + "bin/phpspec" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "PhpSpec": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "homepage": "http://marcelloduarte.net/" + }, + { + "name": "Ciaran McNulty", + "homepage": "https://ciaranmcnulty.com/" + } + ], + "description": "Specification-oriented BDD framework for PHP 7.1+", + "homepage": "http://phpspec.net/", + "keywords": [ + "BDD", + "SpecBDD", + "TDD", + "spec", + "specification", + "testing", + "tests" + ], + "time": "2020-07-08T14:05:47+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d", + "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0 <9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-09-29T09:10:42+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "0.12.49", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "9a6136c2b39d5214da78de37128d5fe08e5d5b05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a6136c2b39d5214da78de37128d5fe08e5d5b05", + "reference": "9a6136c2b39d5214da78de37128d5fe08e5d5b05", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2020-10-12T14:10:44+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "0.12.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "bfabc6a1b4617fbcbff43f03a4c04eae9bafae21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/bfabc6a1b4617fbcbff43f03a4c04eae9bafae21", + "reference": "bfabc6a1b4617fbcbff43f03a4c04eae9bafae21", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^0.12.26" + }, + "require-dev": { + "consistence/coding-standard": "^3.0.1", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^1.0", + "phing/phing": "^2.16.0", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0", + "slevomat/coding-standard": "^4.5.2" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "time": "2020-07-21T14:52:30+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "0.12.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "334898a32217e4605e0f9cfa3d3fc3101bda26be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/334898a32217e4605e0f9cfa3d3fc3101bda26be", + "reference": "334898a32217e4605e0f9cfa3d3fc3101bda26be", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^0.12.33" + }, + "require-dev": { + "consistence/coding-standard": "^3.0.1", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^1.0", + "phing/phing": "^2.16.0", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0", + "slevomat/coding-standard": "^4.5.2" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "time": "2020-08-30T15:42:06+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.3", + "phpunit/php-file-iterator": "^3.0", + "phpunit/php-text-template": "^2.0", + "phpunit/php-token-stream": "^4.0", + "sebastian/code-unit-reverse-lookup": "^2.0", + "sebastian/environment": "^5.0", + "sebastian/version": "^3.0", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.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 provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-05-23T08:02:54+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "18c887016e60e52477e54534956d7b47bc52cd84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/18c887016e60e52477e54534956d7b47bc52cd84", + "reference": "18c887016e60e52477e54534956d7b47bc52cd84", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:03:05+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-08-04T08:28:15+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" + ], + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "rregeer/phpunit-coverage-check", + "version": "0.3.1", + "source": { + "type": "git", + "url": "https://github.com/richardregeer/phpunit-coverage-check.git", + "reference": "9618fa74477fbc448c1b0599bef5153d170094bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/richardregeer/phpunit-coverage-check/zipball/9618fa74477fbc448c1b0599bef5153d170094bd", + "reference": "9618fa74477fbc448c1b0599bef5153d170094bd", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "bin": [ + "bin/coverage-check" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Richard Regeer", + "email": "rich2309@gmail.com" + } + ], + "description": "Check the code coverage using the clover report of phpunit", + "keywords": [ + "ci", + "code coverage", + "php", + "phpunit", + "testing", + "unittest" + ], + "time": "2019-10-14T07:04:13+00:00" + }, + { + "name": "sanmai/pipeline", + "version": "v5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sanmai/pipeline.git", + "reference": "a51d82ca3653f3d417230218a8fe3738cdd207cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/a51d82ca3653f3d417230218a8fe3738cdd207cd", + "reference": "a51d82ca3653f3d417230218a8fe3738cdd207cd", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.13", + "infection/infection": ">=0.10.5", + "league/pipeline": "^1.0 || ^0.3", + "phan/phan": "^1.1 || ^2.0", + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": ">=0.10", + "phpunit/phpunit": "^7.4 || ^8.1 || ^9.0", + "vimeo/psalm": "^2.0 || ^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pipeline\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" + } + ], + "description": "General-purpose collections pipeline", + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2020-08-27T13:29:50+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7a8ff306445707539c1a6397372a982a1ec55120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7a8ff306445707539c1a6397372a982a1ec55120", + "reference": "7a8ff306445707539c1a6397372a982a1ec55120", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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" + ], + "time": "2020-09-30T06:47:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ffc949a1a2aae270ea064453d7535b82e4c32092" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ffc949a1a2aae270ea064453d7535b82e4c32092", + "reference": "ffc949a1a2aae270ea064453d7535b82e4c32092", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2020-09-28T05:32:55+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "ed8c9cd355089134bc9cba421b5cfdd58f0eaef7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/ed8c9cd355089134bc9cba421b5cfdd58f0eaef7", + "reference": "ed8c9cd355089134bc9cba421b5cfdd58f0eaef7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2020-09-28T05:17:32+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "590cfec960b77fd55e39b7d9246659e95dd6d337" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/590cfec960b77fd55e39b7d9246659e95dd6d337", + "reference": "590cfec960b77fd55e39b7d9246659e95dd6d337", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "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": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2020-08-25T06:56:57+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.5.6", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "e97627871a7eab2f70e59166072a6b767d5834e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e97627871a7eab2f70e59166072a6b767d5834e0", + "reference": "e97627871a7eab2f70e59166072a6b767d5834e0", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2020-08-10T04:50:15+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "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", + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d5de97d6af175a9e8131c546db054ca32842dd0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d5de97d6af175a9e8131c546db054ca32842dd0f", + "reference": "d5de97d6af175a9e8131c546db054ca32842dd0f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/error-handler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2020-09-18T14:27:32+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2", + "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "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" + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "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": "2020-09-27T14:02:37+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2020-09-02T16:23:27+00:00" + }, + { + "name": "symfony/process", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d3a2e64866169586502f0cd9cab69135ad12cee9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d3a2e64866169586502f0cd9cab69135ad12cee9", + "reference": "d3a2e64866169586502f0cd9cab69135ad12cee9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "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": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2020-09-02T16:23:27+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e147a68cb66a8b510f4b7481fe4da5b2ab65ec6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e147a68cb66a8b510f4b7481fe4da5b2ab65ec6a", + "reference": "e147a68cb66a8b510f4b7481fe4da5b2ab65ec6a", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2020-09-27T03:44:28+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "a6b795aeb367c90cc6ed88dadb4cdcac436377c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a6b795aeb367c90cc6ed88dadb4cdcac436377c2", + "reference": "a6b795aeb367c90cc6ed88dadb4cdcac436377c2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12", + "squizlabs/php_codesniffer": "^3.2", + "thecodingmachine/phpstan-strict-rules": "^0.12" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-4": { + "Safe\\": [ + "lib/", + "deprecated/", + "generated/" + ] + }, + "files": [ + "deprecated/apc.php", + "deprecated/libevent.php", + "deprecated/mssql.php", + "deprecated/stats.php", + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/ingres-ii.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/msql.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/mysqlndMs.php", + "generated/mysqlndQc.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/password.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pdf.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/simplexml.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "time": "2020-10-08T08:40:29+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "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", + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "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" + ], + "time": "2020-07-08T17:02:28+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "friends-of-phpspec/phpspec-code-coverage": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.3", + "ext-json": "*" + }, + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/docker/.env b/docker/.env new file mode 100644 index 0000000..9ba3356 --- /dev/null +++ b/docker/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=avro-validator diff --git a/docker/dev/php/Dockerfile b/docker/dev/php/Dockerfile new file mode 100644 index 0000000..438629d --- /dev/null +++ b/docker/dev/php/Dockerfile @@ -0,0 +1,33 @@ +FROM php:7.3-cli-alpine3.12 + +# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser +ENV COMPOSER_ALLOW_SUPERUSER 1 +ARG HOST_USER_ID + +# PHP: Copy configuration files & remove dist files +RUN mkdir /phpIni +COPY dev/php/files/bin/ /usr/local/bin/ + +# SYS: Install required packages +RUN apk --no-cache upgrade && \ + apk --no-cache add bash git sudo openssh autoconf gcc g++ make shadow + +# we need support for users with ID higher than 65k, so instead of using this: +#RUN adduser -u $HOST_USER_ID -D -H $HOST_USER +# we do it manually +RUN if [ -n "$HOST_USER_ID" ] && [ "$HOST_USER_ID" -lt 60001 ]; then \ + usermod -u ${HOST_USER_ID} -o www-data; \ + fi + +# COMPOSER: install binary and prestissimo +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer && \ + composer global require hirak/prestissimo + +# PHP: Install php extensions +RUN pecl channel-update pecl.php.net && \ + pecl install pcov && \ + php-ext-enable pcov + +USER www-data + +WORKDIR /var/www/html diff --git a/docker/dev/php/files/bin/php-ext-disable b/docker/dev/php/files/bin/php-ext-disable new file mode 100755 index 0000000..e23b4b8 --- /dev/null +++ b/docker/dev/php/files/bin/php-ext-disable @@ -0,0 +1,11 @@ +#!/bin/bash +if [ $# -eq 0 ] + then + echo "Please pass a php module name" + exit +fi + +for phpmod in "$@" +do + rm -f /usr/local/etc/php/conf.d/*$phpmod*.ini +done diff --git a/docker/dev/php/files/bin/php-ext-enable b/docker/dev/php/files/bin/php-ext-enable new file mode 100755 index 0000000..43e06b9 --- /dev/null +++ b/docker/dev/php/files/bin/php-ext-enable @@ -0,0 +1,20 @@ +#!/bin/bash + +if [[ ${#} -eq 0 ]] ; then + echo -e "\\nPHP module name required!\\n" + exit 1 +fi + +for phpmod in "${@}" ; do + + files=($(find /phpIni -type f -iname "*${phpmod}*.ini" -exec ls -1 '{}' +)) + + for i in "${files[@]}" ; do + ln -s "${i}" /usr/local/etc/php/conf.d + done + + if [[ ${#files[@]} -eq 0 ]] ; then + docker-php-ext-enable "${phpmod}" + fi + +done diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..554b55c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.2' +services: + php: + build: + context: ./ + dockerfile: dev/php/Dockerfile + args: + HOST_USER_ID: ${USER_ID} + container_name: avro-validator + hostname: avro-validator + tty: true + volumes: + - ../:/var/www/html diff --git a/example/data.json b/example/data-correct.json similarity index 100% rename from example/data.json rename to example/data-correct.json diff --git a/example/data-wrong.json b/example/data-wrong.json new file mode 100644 index 0000000..33aa30e --- /dev/null +++ b/example/data-wrong.json @@ -0,0 +1,64 @@ +{ + "id": "1", + "orderNumber": "000000001", + "account": { + "id": 42 + }, + "user": { + "id": "9ffd059d-ffb7-4b3e-8991-064c263a55b2", + "username": "john.doe@jobcloud.ch" + }, + "checkoutCompletedAt": "2019-10-29T14:31:40+01:00", + "shippingAddress": { + "id": 1, + "firstName": "Sherlock", + "lastName": "Holmes", + "phoneNumber": null, + "company": null, + "countryCode": "GB", + "provinceCode": null, + "provinceName": "Greater London", + "street": "Baker Street 221b", + "city": "London", + "postcode": "NWB" + }, + "billingAddress": { + "id": 2, + "firstName": "Sherlock", + "lastName": "Holmes", + "phoneNumber": null, + "company": null, + "countryCode": "GB", + "provinceCode": null, + "provinceName": "Greater London", + "street": "Baker Street 221b", + "city": "London", + "postcode": "NWB" + }, + "promotionCoupon": null, + "items": [ + { + "id": 1, + "quantity": 5, + "unitPrice": 19.99, + "total": 99.95, + "unitsTotal": "foo", + "variant": { + "id": 10, + "code": "LOGAN_MUG_CODE", + "translations": [ + { + "locale": "de_DE", + "name": "Logan Becher" + }, + { + "locale": "en_GB", + "name": "Logan Mug" + } + ] + } + } + ], + "itemsTotal": 99.95, + "total": 99.95 +} diff --git a/example/example.php b/example/example.php index ea2867f..9331e77 100755 --- a/example/example.php +++ b/example/example.php @@ -13,6 +13,6 @@ $validator = new Validator($recordRegistry); var_dump($validator->validate( - file_get_contents(__DIR__ . '/data.json'), + file_get_contents(__DIR__ . '/data-wrong.json'), 'marketplace.ecommerce.entity.order' )); diff --git a/example/schema.json b/example/schema.json index c03a1ff..5b6aacd 100644 --- a/example/schema.json +++ b/example/schema.json @@ -52,17 +52,17 @@ }, { "name": "unitPrice", - "type": "double" + "type": "int" }, { "name": "total", - "type": "double" + "type": "int" }, { "name": "unitsTotal", "type": [ "null", - "double" + "int" ], "default": null }, @@ -172,7 +172,7 @@ "fields": [ { "name": "id", - "type": "int" + "type": "string" } ] }, @@ -183,7 +183,7 @@ "fields": [ { "name": "id", - "type": "string" + "type": "int" }, { "name": "orderNumber", @@ -226,11 +226,11 @@ }, { "name": "itemsTotal", - "type": "double" + "type": "int" }, { "name": "total", - "type": "double" + "type": "int" } ] } diff --git a/infection.json b/infection.json new file mode 100644 index 0000000..3b40d88 --- /dev/null +++ b/infection.json @@ -0,0 +1,15 @@ +{ + "timeout": 10, + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "build\/logs\/infection\/infection.log", + "summary": "build\/logs\/infection\/infection-summary.log" + }, + "mutators": { + "@default": true + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..4918b30 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,16 @@ + + + src + + + + + + + + + + + + + diff --git a/phpspec.yml b/phpspec.yml new file mode 100644 index 0000000..2af36fe --- /dev/null +++ b/phpspec.yml @@ -0,0 +1,16 @@ +extensions: + LeanPHP\PhpSpec\CodeCoverage\CodeCoverageExtension: + whitelist: + - src + blacklist: + - src/Command + format: + - text + - html + - xml + - clover + output: + html: ./build/logs/phpspec/coverage/html + xml: ./build/logs/phpspec/coverage/xml + clover: ./build/logs/phpspec/coverage/coverage.xml + show_only_summary: true \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..7a73c51 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,18 @@ +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + +parameters: + level: 7 + treatPhpDocTypesAsCertain: false + paths: + - app + ignoreErrors: + - '#Comparison operation "<=" between int and 9223372036854775807 is always true\.#' + +rules: + - Ergebnis\PHPStan\Rules\Expressions\NoEmptyRule + - Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule + - Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule + - Ergebnis\PHPStan\Rules\Expressions\NoEvalRule + - Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule diff --git a/spec/Jobcloud/Avro/Validator/RecordRegistrySpec.php b/spec/Jobcloud/Avro/Validator/RecordRegistrySpec.php new file mode 100644 index 0000000..998f5b8 --- /dev/null +++ b/spec/Jobcloud/Avro/Validator/RecordRegistrySpec.php @@ -0,0 +1,37 @@ + 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'id', + 'type' => 'string', + ], + ], + ]; + + $this->beConstructedThrough('fromSchema', [json_encode($record)]); + + $this->getRecord(sprintf('%s.%s', $record['namespace'], $record['name']))->shouldBe($record); + $this->getRecord('foo')->shouldBe(null); + } + + public function it_throws_exception_on_malformed_schema(): void + { + $this->beConstructedThrough('fromSchema', [json_encode([])]); + + $this->shouldThrow(RecordRegistryException::class)->duringInstantiation(); + } +} diff --git a/spec/Jobcloud/Avro/Validator/ValidatorSpec.php b/spec/Jobcloud/Avro/Validator/ValidatorSpec.php new file mode 100644 index 0000000..276dc63 --- /dev/null +++ b/spec/Jobcloud/Avro/Validator/ValidatorSpec.php @@ -0,0 +1,524 @@ +beConstructedWith($recordRegistry); + } + + public function it_throws_exception_on_missing_schema(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn(null); + + $this->shouldThrow(MissingSchemaException::class)->during('validate', ['{}', $schemaName]); + } + + public function it_throws_exception_on_invalid_schema(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn([]); + + $this->shouldThrow(InvalidSchemaException::class)->during('validate', ['{}', $schemaName]); + } + + public function it_detects_missing_fields(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'id', + 'type' => 'string', + ], + [ + 'name' => 'age', + 'type' => 'int', + ], + ], + ]); + + $payload = []; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([ + [ + 'path' => '$', + 'type' => 'missingField', + 'message' => 'Field "id" is missing in payload', + ], + [ + 'path' => '$', + 'type' => 'missingField', + 'message' => 'Field "age" is missing in payload', + ], + ]); + } + + public function it_detects_wrong_field_values(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn($this->getSampleSchema()); + $recordRegistry->getRecord('array')->willReturn(null); + + $invalidStringValue = 42; + $invalidIntValue = 'foo'; + $invalidLongValue = 'long'; + $invalidDoubleValue = 42; + $invalidArrayValue = 42; + $invalidNullOrArrayValue = 42; + + $payload = [ + 'stringTest' => $invalidStringValue, + 'intTest' => $invalidIntValue, + 'longTest' => $invalidLongValue, + 'doubleTest' => $invalidDoubleValue, + 'arrayTest' => $invalidArrayValue, + 'multipleTypeTest' => $invalidNullOrArrayValue, + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([ + [ + 'path' => '$.stringTest', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "string", but was "int"', + 'value' => $invalidStringValue, + ], + [ + 'path' => '$.intTest', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "int", but was "string"', + 'value' => $invalidIntValue, + ], + [ + 'path' => '$.longTest', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "long", but was "string"', + 'value' => $invalidLongValue, + ], + [ + 'path' => '$.doubleTest', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "double", but was "int"', + 'value' => $invalidDoubleValue, + ], + [ + 'path' => '$.arrayTest', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "array", but was "int"', + 'value' => $invalidArrayValue, + ], + [ + 'path' => '$.multipleTypeTest', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "null" or "array", but was "int"', + 'value' => $invalidNullOrArrayValue, + ], + ]); + } + + public function it_validates_array_items(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'arrayTest', + 'type' => [ + 'type' => 'array', + 'items' => 'string', + ], + ], + ], + ]); + + $invalidArrayItemValue = 42; + + $payload = [ + 'arrayTest' => [$invalidArrayItemValue], + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([ + [ + 'path' => '$.arrayTest[0]', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "string", but was "int"', + 'value' => $invalidArrayItemValue, + ], + ]); + } + + public function it_throws_exception_for_unsupported_type_enum(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'enumTest', + 'type' => 'enum', + ], + ], + ]); + + $payload = [ + 'enumTest' => 42, + ]; + + $this->shouldThrow(UnsupportedTypeException::class)->during('validate', [ + $this->encodePayload($payload), + $schemaName + ]); + } + + public function it_validates_union_sub_schemas(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'subSchema', + 'type' => ['wrong.sub.schema', 'foo.bar.boo'], + ], + ], + ]); + $recordRegistry->getRecord('wrong.sub.schema')->willReturn([ + 'type' => 'record', + 'name' => 'schema', + 'namespace' => 'wrong.sub', + 'fields' => [ + [ + 'name' => 'age', + 'type' => 'int', + ], + ], + ]); + $recordRegistry->getRecord('foo.bar.boo')->willReturn([ + 'type' => 'record', + 'name' => 'boo', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'id', + 'type' => 'string', + ], + ], + ]); + + $invalidArrayItemValue = 42; + + $payload = [ + 'subSchema' => [ + 'id' => $invalidArrayItemValue + ], + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([ + [ + 'path' => '$.subSchema.id', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "string", but was "int"', + 'value' => $invalidArrayItemValue, + ], + ]); + } + + public function it_does_not_report_errors_for_valid_data(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn($this->getSampleSchema()); + + $payload = [ + 'stringTest' => 'foo', + 'intTest' => 42, + 'longTest' => 1234567890, + 'doubleTest' => 42.0, + 'arrayTest' => ['foo', 'bar'], + 'multipleTypeTest' => null, + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([]); + } + + public function it_detects_wrong_int_vs_long_values(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'minLongButIsInt', + 'type' => 'int', + ], + [ + 'name' => 'maxLongButIsInt', + 'type' => 'int', + ], + [ + 'name' => 'minIntPlusOneButIsInt', + 'type' => 'int', + ], + [ + 'name' => 'maxIntPlusOneButIsInt', + 'type' => 'int', + ], + [ + 'name' => 'minInt', + 'type' => 'int', + ], + [ + 'name' => 'maxInt', + 'type' => 'int', + ], + [ + 'name' => 'intInLong', + 'type' => 'long', + ], + ], + ]); + + $invalidMinLongButIsIntValue = intval(-9223372036854775808); + $invalidMaxLongButIsIntValue = 9223372036854775807; + $invalidMinIntPlusOneButIsInt = -2147483649; + $invalidMaxIntPlusOneButIsInt = 2147483648; + $minInt = -2147483648; + $maxInt = 2147483647; + + $payload = [ + 'minLongButIsInt' => $invalidMinLongButIsIntValue, + 'maxLongButIsInt' => $invalidMaxLongButIsIntValue, + 'minIntPlusOneButIsInt' => $invalidMinIntPlusOneButIsInt, + 'maxIntPlusOneButIsInt' => $invalidMaxIntPlusOneButIsInt, + 'maxInt' => $minInt, + 'minInt' => $maxInt, + 'intInLong' => 42, + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([ + [ + 'path' => '$.minLongButIsInt', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "int", but was "long"', + 'value' => $invalidMinLongButIsIntValue, + ], + [ + 'path' => '$.maxLongButIsInt', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "int", but was "long"', + 'value' => $invalidMaxLongButIsIntValue, + ], + [ + 'path' => '$.minIntPlusOneButIsInt', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "int", but was "long"', + 'value' => $invalidMinIntPlusOneButIsInt, + ], + [ + 'path' => '$.maxIntPlusOneButIsInt', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "int", but was "long"', + 'value' => $invalidMaxIntPlusOneButIsInt, + ], + ]); + } + + public function it_handles_inlined_schemas(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $inlinedSchemaName = 'account'; + $inlinedSchemaName2 = 'foo'; + + $inlinedSchema = [ + 'type' => 'record', + 'name' => $inlinedSchemaName, + 'fields' => [ + [ + 'name' => 'accountId', + 'type' => 'string', + ], + ], + ]; + + $inlinedSchema2 = [ + 'type' => 'record', + 'name' => $inlinedSchemaName2, + 'fields' => [ + [ + 'name' => 'missingField', + 'type' => 'string', + ], + ], + ]; + + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'account1', + 'type' => $inlinedSchema, + ], + [ + 'name' => 'account2', + 'type' => [ + $inlinedSchema2, + $inlinedSchemaName, + ], + ], + ], + ]); + $recordRegistry->getRecord($inlinedSchemaName)->shouldBeCalled()->willReturn(null, $inlinedSchema); + $recordRegistry->addRecord($inlinedSchema)->shouldBeCalled(); + $recordRegistry->getRecord($inlinedSchemaName2)->shouldBeCalled()->willReturn(null); + $recordRegistry->addRecord($inlinedSchema2)->shouldBeCalled(); + + $payload = [ + 'account1' => [ + 'accountId' => 'foobar', + ], + 'account2' => [ + 'accountId' => 'baz', + ], + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([]); + } + + public function it_formats_inline_schema_type_correctly(RecordRegistryInterface $recordRegistry): void + { + $schemaName = 'foo.bar.baz'; + $inlinedSchemaName = 'account'; + + $inlinedSchema = [ + 'type' => 'record', + 'name' => $inlinedSchemaName, + 'fields' => [ + [ + 'name' => 'accountId', + 'type' => 'string', + ], + ], + ]; + + $recordRegistry->getRecord($schemaName)->willReturn([ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'account', + 'type' => $inlinedSchema, + ], + ], + ]); + $recordRegistry->getRecord($inlinedSchemaName)->shouldBeCalled()->willReturn(null, $inlinedSchema); + $recordRegistry->addRecord($inlinedSchema)->shouldBeCalled(); + + $invalidValue = [ + 'invalidField' => 'foobar', + ]; + + $payload = [ + 'account' => $invalidValue, + ]; + + $this->validate( + $this->encodePayload($payload), + $schemaName + )->shouldBe([ + [ + 'path' => '$.account', + 'type' => 'wrongType', + 'message' => 'Field value was expected to be of type "account", but was "array"', + 'value' => $invalidValue, + ] + ]); + } + + private function getSampleSchema(): array + { + return [ + 'type' => 'record', + 'name' => 'baz', + 'namespace' => 'foo.bar', + 'fields' => [ + [ + 'name' => 'stringTest', + 'type' => 'string', + ], + [ + 'name' => 'intTest', + 'type' => 'int', + ], + [ + 'name' => 'longTest', + 'type' => 'long', + ], + [ + 'name' => 'doubleTest', + 'type' => 'double', + ], + [ + 'name' => 'arrayTest', + 'type' => [ + 'type' => 'array', + 'items' => 'string', + ], + ], + [ + 'name' => 'multipleTypeTest', + 'type' => ['null', 'array'], + ], + ], + ]; + } + + private function encodePayload(array $payload): string + { + return json_encode($payload, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION); + } +} diff --git a/src/Command/Formatter/FormatterInterface.php b/src/Command/Formatter/FormatterInterface.php new file mode 100644 index 0000000..89c6aff --- /dev/null +++ b/src/Command/Formatter/FormatterInterface.php @@ -0,0 +1,20 @@ +> $result + * @return void + */ + public function formatSuccess(array $result): void; + + /** + * @param array> $result + * @return void + */ + public function formatFail(array $result): void; +} diff --git a/src/Command/Formatter/JsonFormatter.php b/src/Command/Formatter/JsonFormatter.php new file mode 100644 index 0000000..277a7b6 --- /dev/null +++ b/src/Command/Formatter/JsonFormatter.php @@ -0,0 +1,47 @@ +output = $output; + } + + /** + * @param array> $result + * @return void + */ + public function formatSuccess(array $result): void + { + $this->output->writeln($this->encodeResult($result)); + } + + /** + * @param array> $result + * @return void + */ + public function formatFail(array $result): void + { + $this->output->writeln($this->encodeResult($result)); + } + + /** + * @param array> $result + * @return string + */ + private function encodeResult(array $result): string + { + return json_encode($result, JSON_THROW_ON_ERROR); + } +} diff --git a/src/Command/Formatter/PrettyFormatter.php b/src/Command/Formatter/PrettyFormatter.php new file mode 100644 index 0000000..1a8ef8c --- /dev/null +++ b/src/Command/Formatter/PrettyFormatter.php @@ -0,0 +1,95 @@ +output = $output; + $this->schemaNamespace = $schemaNamespace; + $this->schemaPath = $schemaPath; + $this->payloadPath = $payloadPath; + } + + /** + * @param array> $result + * @return void + */ + public function formatSuccess(array $result): void + { + $this->output->writeln(sprintf( + 'Validation of payload was successful against schema with namespace %s.', + $this->schemaNamespace + )); + } + + /** + * @param array> $result + * @return void + */ + public function formatFail(array $result): void + { + $this->output->writeln(sprintf( + 'There were %d errors during validation of payload against ' . + 'schema with namespace %s:', + count($result), + $this->schemaNamespace + )); + + foreach ($result as $error) { + $this->output->writeln(''); + $this->output->writeln(sprintf(' - Field: %s', $error['path'])); + $this->output->writeln(sprintf(' Message: %s', $error['message'])); + $this->output->writeln(sprintf( + ' Value: %s', + $this->formatValue($error['value']) + )); + } + } + + /** + * @param mixed $value + * @return string + */ + private function formatValue($value): string + { + if (is_string($value)) { + return sprintf('"%s"', $value); + } + + if (is_scalar($value)) { + return (string) $value; + } + + return json_encode($value, JSON_THROW_ON_ERROR); + } +} diff --git a/src/Command/ValidateCommand.php b/src/Command/ValidateCommand.php new file mode 100644 index 0000000..29ec11c --- /dev/null +++ b/src/Command/ValidateCommand.php @@ -0,0 +1,125 @@ +setName('validate') + ->setDescription('Validates a payload against a schema') + ->addArgument(self::ARG_SCHEMA, InputArgument::REQUIRED, 'Path to the schema file') + ->addArgument(self::ARG_NAMESPACE, InputArgument::REQUIRED, 'Schema namespace') + ->addArgument(self::ARG_PAYLOAD, InputArgument::OPTIONAL, 'Path to the payload file') + ->addOption( + self::OPTION_FORMAT, + 'f', + InputOption::VALUE_REQUIRED, + 'Output format of the result', + self::FORMAT_PRETTY + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + /** @var string $schemaFilePath */ + $schemaFilePath = $input->getArgument(self::ARG_SCHEMA); + /** @var string $schemaNamespace */ + $schemaNamespace = $input->getArgument(self::ARG_NAMESPACE); + + $readStreams = [STDIN]; + $writeStreams = []; + $exceptStreams = []; + $hasPayloadFromStdin = 1 === stream_select($readStreams, $writeStreams, $exceptStreams, 0); + $hasPayloadFromFile = $input->hasArgument(self::ARG_PAYLOAD); + + if ($hasPayloadFromStdin && $hasPayloadFromFile) { + $output->writeln('You cannot provide payload through both, stdin and as argument.'); + return 2; + } + + $payloadFilePath = null; + + if ($hasPayloadFromStdin) { + $payloadFilePath = 'php://stdin'; + } elseif ($hasPayloadFromFile) { + /** @var string $payloadFilePath */ + $payloadFilePath = $input->getArgument(self::ARG_PAYLOAD); + } + + if (null === $payloadFilePath) { + $output->writeln('No payload provided. Provide it either over stdin or as argument.'); + return 3; + } + + if (false === $schemaData = file_get_contents($schemaFilePath)) { + $output->writeln(sprintf('Could not read schema from file "%s".', $schemaFilePath)); + return 5; + } + + $recordRegistry = RecordRegistry::fromSchema($schemaData); + $validator = new Validator($recordRegistry); + + /** @var string $outputFormat */ + $outputFormat = $input->getOption(self::OPTION_FORMAT); + + if (self::FORMAT_PRETTY === $outputFormat) { + $formatter = new PrettyFormatter($output, $schemaNamespace, $schemaFilePath, $payloadFilePath); + } elseif (self::FORMAT_JSON === $outputFormat) { + $formatter = new JsonFormatter($output); + } else { + $output->writeln(sprintf( + 'Unsupported format: %s. Supported formats are: %s.', + $outputFormat, + implode(', ', self::SUPPORTED_FORMATS) + )); + return 4; + } + + if (false === $payloadData = file_get_contents($payloadFilePath)) { + $output->writeln('Could not read payload data from source.'); + return 6; + } + + $result = $validator->validate($payloadData, $schemaNamespace); + + if (0 === count($result)) { + $formatter->formatSuccess($result); + + return 0; + } + + $formatter->formatFail($result); + + return 1; + } +} diff --git a/src/Exception/InvalidSchemaException.php b/src/Exception/InvalidSchemaException.php new file mode 100644 index 0000000..4f3a6b3 --- /dev/null +++ b/src/Exception/InvalidSchemaException.php @@ -0,0 +1,9 @@ +> */ private $records; /** - * @param array $recordTypes + * @param array> $recordTypes */ private function __construct(array $recordTypes) { $this->records = []; foreach ($recordTypes as $recordType) { - $this->records[$recordType['namespace'] . '.' . $recordType['name']] = $recordType; + $this->addRecord($recordType); } } /** * @param string $schema - * @return static + * @return self */ public static function fromSchema(string $schema): self { - return new self(json_decode($schema, true)); + return new self([json_decode($schema, true)]); } /** - * @param string $type - * @return array|null + * @param string $identifier + * @return array|null */ - public function getRecord(string $type): ?array + public function getRecord(string $identifier): ?array { - if (isset($this->records[$type])) { - return $this->records[$type]; + if (isset($this->records[$identifier])) { + return $this->records[$identifier]; } return null; } + + /** + * @param array $record + * @throws RecordRegistryException + */ + public function addRecord(array $record): void + { + $this->records[$this->determineRecordIdentifier($record)] = $record; + } + + /** + * @param array $record + * @return string + * @throws RecordRegistryException + */ + private function determineRecordIdentifier(array $record): string + { + $identifier = ''; + + if (isset($record['namespace'])) { + $identifier .= sprintf('%s.', $record['namespace']); + } + + if (!isset($record['name'])) { + throw new RecordRegistryException('Provided schema does not have a name'); + } + + $identifier .= $record['name']; + + return $identifier; + } } diff --git a/src/RecordRegistryInterface.php b/src/RecordRegistryInterface.php index ac47e5d..8980284 100644 --- a/src/RecordRegistryInterface.php +++ b/src/RecordRegistryInterface.php @@ -1,10 +1,22 @@ |null + */ + public function getRecord(string $identifier): ?array; + + /** + * @param array $record + * @throws RecordRegistryException + */ + public function addRecord(array $record): void; } diff --git a/src/Validator.php b/src/Validator.php index 02a496f..ef1d6ce 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -4,18 +4,53 @@ namespace Jobcloud\Avro\Validator; +use Jobcloud\Avro\Validator\Exception\InvalidSchemaException; +use Jobcloud\Avro\Validator\Exception\MissingSchemaException; +use Jobcloud\Avro\Validator\Exception\RecordRegistryException; +use Jobcloud\Avro\Validator\Exception\UnsupportedTypeException; +use Jobcloud\Avro\Validator\Exception\ValidatorException; + final class Validator implements ValidatorInterface { + /** + * @var string + */ + public const ERROR_TYPE_MISSING_FIELD = 'missingField'; + + /** + * @var string + */ + public const ERROR_TYPE_WRONG_TYPE = 'wrongType'; + + /** + * @var int lower bound of integer values: -(1 << 31) + */ + private const INT_MIN_VALUE = -2147483648; + + /** + * @var int upper bound of integer values: (1 << 31) - 1 + */ + private const INT_MAX_VALUE = 2147483647; + + /** + * @var int lower bound of long values: -(1 << 63) + */ + private const LONG_MIN_VALUE = -9223372036854775808; /** - * @var RecordRegistry + * @var int upper bound of long values: (1 << 63) - 1 + */ + private const LONG_MAX_VALUE = 9223372036854775807; + + /** + * @var RecordRegistryInterface */ private $recordRegistry; /** - * @param RecordRegistry $recordRegistry + * @param RecordRegistryInterface $recordRegistry */ - public function __construct(RecordRegistry $recordRegistry) + public function __construct(RecordRegistryInterface $recordRegistry) { $this->recordRegistry = $recordRegistry; } @@ -23,14 +58,20 @@ public function __construct(RecordRegistry $recordRegistry) /** * @param string $payload * @param string $recordType - * @return array + * @return array> + * @throws ValidatorException + * @throws RecordRegistryException */ public function validate(string $payload, string $recordType): array { $decodedPayload = json_decode($payload, true); if (null === $recordSchema = $this->recordRegistry->getRecord($recordType)) { - throw new \RuntimeException(sprintf('Could not find record of type %s', $recordType)); + throw new MissingSchemaException(sprintf('Could not find record of type "%s"', $recordType)); + } + + if (!array_key_exists('fields', $recordSchema) || !is_array($recordSchema['fields'])) { + throw new InvalidSchemaException('Schema does not have any fields defined'); } $validationErrors = []; @@ -39,11 +80,13 @@ public function validate(string $payload, string $recordType): array } /** - * @param array $schemaFields - * @param array $payload + * @param array> $schemaFields + * @param array $payload * @param string $path - * @param array $validationErrors - * @return array + * @param array> $validationErrors + * @return array> + * @throws UnsupportedTypeException + * @throws RecordRegistryException */ private function validateFields(array $schemaFields, array $payload, string $path, array &$validationErrors): array { @@ -51,10 +94,10 @@ private function validateFields(array $schemaFields, array $payload, string $pat $fieldName = $rule['name']; if (false === array_key_exists($fieldName, $payload)) { - echo 'something is missing'; $validationErrors[] = [ 'path' => $path, - 'message' => sprintf('Field "%s" missing in payload', $fieldName), + 'type' => self::ERROR_TYPE_MISSING_FIELD, + 'message' => sprintf('Field "%s" is missing in payload', $fieldName), ]; continue; } @@ -64,11 +107,12 @@ private function validateFields(array $schemaFields, array $payload, string $pat $currentPath = $path . '.' . $fieldName; if (false === $this->checkFieldValueBeOneOf($types, $fieldValue, $currentPath, $validationErrors)) { - $validationErrors[] = [ - 'path' => $currentPath, - 'message' => sprintf('Field value is not any of: %s', implode(', ', $types)), - 'value' => $fieldValue, - ]; + $validationErrors[] = $this->createValidationError( + $currentPath, + self::ERROR_TYPE_WRONG_TYPE, + $types, + $fieldValue + ); continue; } } @@ -77,11 +121,30 @@ private function validateFields(array $schemaFields, array $payload, string $pat } /** - * @param array $types - * @param $fieldValue + * @param array> $types + * @return string + */ + private function formatTypeList(array $types): string + { + $normalizedTypes = array_map([$this, 'getTypeAsString'], $types); + + $lastEntry = array_pop($normalizedTypes); + + if (0 === count($normalizedTypes)) { + return sprintf('"%s"', $lastEntry); + } + + return sprintf('"%s" or "%s"', implode('", "', $normalizedTypes), $lastEntry); + } + + /** + * @param array> $types + * @param mixed $fieldValue * @param string $currentPath - * @param array $validationErrors + * @param array> $validationErrors * @return bool + * @throws UnsupportedTypeException + * @throws RecordRegistryException */ private function checkFieldValueBeOneOf( array $types, @@ -89,43 +152,163 @@ private function checkFieldValueBeOneOf( string $currentPath, array &$validationErrors ): bool { + $scalarTypes = [ + 'null' => 'is_null', + 'int' => static function ($value): bool { + return is_int($value) && self::INT_MIN_VALUE <= $value && $value <= self::INT_MAX_VALUE; + }, + 'long' => static function ($value): bool { + return is_int($value) && self::LONG_MIN_VALUE <= $value && $value <= self::LONG_MAX_VALUE; + }, + 'string' => 'is_string', + 'boolean' => 'is_bool', + 'float' => 'is_float', + 'double' => 'is_double', + ]; + foreach ($types as $type) { - if ('null' === $type && null === $fieldValue) { - return true; - } + if (is_string($type) && isset($scalarTypes[$type])) { + if ($scalarTypes[$type]($fieldValue)) { + return true; + } - if ('double' === $type && is_double($fieldValue)) { - return true; + continue; } - if ('int' === $type && is_int($fieldValue)) { - return true; + if (in_array($type, ['enum', 'map', 'bytes'], true)) { + throw new UnsupportedTypeException(sprintf( + 'The type "%d" is currently not supported by this validator', + $type + )); } - if ('string' === $type && is_string($fieldValue)) { - return true; - } + if (is_array($type)) { + if ('array' === $type['type'] && is_array($fieldValue)) { + $types = (array) $type['items']; + + foreach ($fieldValue as $key => $value) { + $itemPath = sprintf('%s[%s]', $currentPath, $key); + if (false === $this->checkFieldValueBeOneOf($types, $value, $itemPath, $validationErrors)) { + $validationErrors[] = $this->createValidationError( + $itemPath, + self::ERROR_TYPE_WRONG_TYPE, + $types, + $value + ); + } + } + + return true; + } elseif ('record' === $type['type'] && isset($type['fields'])) { + // Inlined schema + if (null === $subRecord = $this->recordRegistry->getRecord($type['name'])) { + $subRecord = $type; + $this->recordRegistry->addRecord($type); + } + + $validationErrorsSub = []; + $this->validateFields($subRecord['fields'], $fieldValue, $currentPath, $validationErrorsSub); - if (is_array($type) && 'array' === $type['type'] && is_array($fieldValue)) { - $recordSchema = $this->recordRegistry->getRecord($type['items']); + if ($this->hasOnlyMissingFields($validationErrorsSub)) { + continue; + } - foreach ($fieldValue as $key => $value) { - $this->validateFields( - $recordSchema['fields'], - $value, - $currentPath . '['.$key.']', - $validationErrors - ); + $validationErrors = array_merge($validationErrors, $validationErrorsSub); + + return true; } - return true; } if (is_string($type) && null !== $recordSchema = $this->recordRegistry->getRecord($type)) { - $this->validateFields($recordSchema['fields'], $fieldValue, $currentPath, $validationErrors); + $validationErrorsSub = []; + $this->validateFields($recordSchema['fields'], $fieldValue, $currentPath, $validationErrorsSub); + + if ($this->hasOnlyMissingFields($validationErrorsSub)) { + continue; + } + + $validationErrors = array_merge($validationErrors, $validationErrorsSub); + return true; } } return false; } + + /** + * @param array> $validationErrors + * @return bool + */ + private function hasOnlyMissingFields(array $validationErrors): bool + { + if (0 === count($validationErrors)) { + return false; + } + + foreach ($validationErrors as $validationError) { + if (self::ERROR_TYPE_MISSING_FIELD !== $validationError['type']) { + return false; + } + } + + return true; + } + + /** + * @param string $path + * @param string $errorType + * @param array $types + * @param mixed $value + * @return array + */ + private function createValidationError(string $path, string $errorType, array $types, $value): array + { + return [ + 'path' => $path, + 'type' => $errorType, + 'message' => sprintf( + 'Field value was expected to be of type %s, but was "%s"', + $this->formatTypeList($types), + $this->getType($value) + ), + 'value' => $value, + ]; + } + + /** + * @param mixed $value + * @return string + */ + private function getType($value): string + { + $type = gettype($value); + + if ('integer' === $type) { + return ($value > self::INT_MAX_VALUE || $value < self::INT_MIN_VALUE) ? 'long' : 'int'; + } + + return $type; + } + + /** + * @param mixed $type + * @return string + */ + private function getTypeAsString($type): string + { + if (!is_array($type)) { + return $type; + } + + if ('array' === $type['type']) { + return sprintf('%s<%s>', $type['type'], $this->getTypeAsString($type['items'])); + } + + if ('record' === $type['type']) { + return $type['name']; + } + + throw new \InvalidArgumentException('Could not determine name for type'); + } } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index f237943..5f81dcd 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -1,8 +1,15 @@ > + */ public function validate(string $payload, string $recordType): array; }