diff --git a/.gitattributes b/.gitattributes index 135a51b..aa6a4bd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,12 +3,9 @@ /.coveralls.yml export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore /CODE_OF_CONDUCT.md export-ignore /CONTRIBUTING.md export-ignore /composer export-ignore /composer.lock export-ignore -/phpcs.xml export-ignore -/phpstan.neon export-ignore /phpunit.xml.dist export-ignore /tests/ export-ignore diff --git a/.github/workflows/php-build.yml b/.github/workflows/php-build.yml new file mode 100644 index 0000000..0194048 --- /dev/null +++ b/.github/workflows/php-build.yml @@ -0,0 +1,43 @@ +name: build + +on: + push: + branches: [ 'master', 'feature/*' ] + pull_request: + branches: [ 'master' ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '8.2', '8.3', '8.4', '8.5' ] + phpunit-versions: [ 'latest' ] + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: xdebug + tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }} + + - name: Validate composer.json and composer.lock + run: composer validate --strict --no-interaction + + - name: Install Dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Launch test unit in coverage mode + run: composer test-coverage + + - name: Publish coveralls + if: matrix.php-versions == '8.2' + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + composer global require php-coveralls/php-coveralls + php-coveralls -v diff --git a/.gitignore b/.gitignore index b2355d0..38855e2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ clover.xml .project .idea .phpunit.result.cache +clover.xml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6636995..0000000 --- a/.travis.yml +++ /dev/null @@ -1,66 +0,0 @@ -sudo: false - -language: php - -cache: - directories: - - $HOME/.composer/cache - -env: - global: - - COMPOSER_ARGS="--no-interaction" - - COVERAGE_DEPS="php-coveralls/php-coveralls" - -matrix: - include: - - php: 7.2 - env: - - DEPS=lowest - - php: 7.2 - env: - - DEPS=locked - - php: 7.2 - env: - - DEPS=latest - - php: 7.3 - env: - - DEPS=lowest - - php: 7.3 - env: - - DEPS=locked - - CS_CHECK=true - - TEST_COVERAGE=true - - php: 7.3 - env: - - DEPS=latest - - php: 7.4 - env: - - DEPS=lowest - - php: 7.4 - env: - - DEPS=locked - - php: 7.4 - env: - - DEPS=latest - -before_install: - - "if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi" - - 'travis_retry composer self-update' - -install: - - 'travis_retry composer install $COMPOSER_ARGS' - - "if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi" - - "if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi" - - "if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer require --dev $COMPOSER_ARGS $COVERAGE_DEPS ; fi" - - 'stty cols 120 && composer show' - -script: - - "if [[ $TEST_COVERAGE == 'true' ]]; then composer test-coverage ; else composer test ; fi" - - "if [[ $CS_CHECK == 'true' ]]; then composer cs-check ; fi" - -after_script: - - "if [[ $TEST_COVERAGE == 'true' ]]; then vendor/bin/php-coveralls -v ; fi" - -notifications: - email: - on_success: never diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c9c9e3..9d15c5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. -## V2.0.6 - TBD +## V3.0.0 - TBD ### Added @@ -10,7 +10,7 @@ All notable changes to this project will be documented in this file, in reverse ### Changed -- Nothing. +- [#31] Upgrade composer.json to require PHP 8.2 minimum compatibility. ### Deprecated diff --git a/README.md b/README.md index 1edbffb..616fe09 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Validator Project -[![Build Status](https://travis-ci.org/elie29/validator.svg?branch=master)](https://travis-ci.org/elie29/validator) +[![build](https://github.com/elie29/validator/actions/workflows/php-build.yml/badge.svg)](https://github.com/elie29/validator/actions/workflows/php-build.yml) [![Coverage Status](https://coveralls.io/repos/github/elie29/validator/badge.svg)](https://coveralls.io/github/elie29/validator) +[![PHP Version](https://img.shields.io/packagist/php-v/elie29/validator.svg)](https://packagist.org/packages/elie29/validator) ## Introduction -A library for validating a context (POST, GET etc...) by running given rules. +A library for validating a context (POST, GET, etc...) by running given rules. ## Installation @@ -17,7 +18,7 @@ composer require elie29/validator ## Getting Started -`Validator` requires one or several rules ([constraints](#available-rules)) in order to validate a given context. +`Validator` requires one or several rules ([constraints](#available-rules)) to validate a given context. A basic example with \$\_POST @@ -34,7 +35,7 @@ use Elie\Validator\Validator; /** * A key could have multiple rules * - name could not be empty (required and minimum 1 character length) - * - age could be empty (non existent, null or '') otherwise NumericRule is applied + * - age could be empty (non-existent, null or '') otherwise NumericRule is applied * - age could be empty or among several values * - email is required and should be a valid string email */ @@ -56,31 +57,32 @@ $validator->validate(); // bool depends on $_POST content ### Available rules -1. [All Rules](https://github.com/elie29/validator/blob/master/src/Rule/AbstractRule.php) accept `required`, `trim` and `messages` options. +1. [All Rules](https://github.com/elie29/validator/tree/main/src/Rule/AbstractRule.php) accept `required`, `trim` and `messages` options. `required` is false by default while `trim` is true. -1. [ArrayRule](https://github.com/elie29/validator/blob/master/src/Rule/ArrayRule.php) accepts `min` and `max` options. Empty value is cast to empty array []. -1. [BicRule](https://github.com/elie29/validator/blob/master/src/Rule/BicRule.php) -1. [BooleanRule](https://github.com/elie29/validator/blob/master/src/Rule/BooleanRule.php) accepts `cast` option. -1. [CallableRule](https://github.com/elie29/validator/blob/master/src/Rule/CallableRule.php) accepts `callable` function. -1. [ChoicesRule](https://github.com/elie29/validator/blob/master/src/Rule/ChoicesRule.php) accepts `list` option. -1. [CollectionRule](https://github.com/elie29/validator/blob/master/src/Rule/CollectionRule.php) accepts `list` and `json` options. -1. [CompareRule](https://github.com/elie29/validator/blob/master/src/Rule/CompareRule.php) accepts `sign` and `expected` options. `sign` is [CompareRule::EQ](https://github.com/elie29/validator/blob/master/src/Rule/CompareConstants.php) by default, `expected` is null by default. -1. [DateRule](https://github.com/elie29/validator/blob/master/src/Rule/DateRule.php) accepts `format` and `separator` options. -1. [EmailRule](https://github.com/elie29/validator/blob/master/src/Rule/EmailRule.php) -1. [IpRule](https://github.com/elie29/validator/blob/master/src/Rule/IpRule.php) accepts `flag` option. -1. [JsonRule](https://github.com/elie29/validator/blob/master/src/Rule/JsonRule.php) accepts `decode` option -1. [MatchRule](https://github.com/elie29/validator/blob/master/src/Rule/MatchRule.php) requires `pattern` option. -1. [MultipleAndRule](https://github.com/elie29/validator/blob/master/src/Rule/MultipleAndRule.php) requires `rules` option. -1. [MultipleOrRule](https://github.com/elie29/validator/blob/master/src/Rule/MultipleOrRule.php) requires `rules` option. -1. [NumericRule](https://github.com/elie29/validator/blob/master/src/Rule/NumericRule.php) accepts `min`, `max` and `cast` options. -1. [RangeRule](https://github.com/elie29/validator/blob/master/src/Rule/RangeRule.php) accepts `range` option. -1. [StringRule](https://github.com/elie29/validator/blob/master/src/Rule/StringRule.php) accepts `min` and `max` options. -1. [TimeRule](https://github.com/elie29/validator/blob/master/src/Rule/TimeRule.php) -1. [Your own rule](#how-to-add-a-new-rule) +2. [ArrayRule](https://github.com/elie29/validator/tree/main/src/Rule/ArrayRule.php) accepts `min` and `max` options. Empty value is cast to an empty array []. +3. [BicRule](https://github.com/elie29/validator/tree/main/src/Rule/BicRule.php) validates Bank Identifier Code (SWIFT-BIC). +4. [BooleanRule](https://github.com/elie29/validator/tree/main/src/Rule/BooleanRule.php) accepts `cast` option. +5. [CallableRule](https://github.com/elie29/validator/tree/main/src/Rule/CallableRule.php) accepts `callable` function. +6. [ChoicesRule](https://github.com/elie29/validator/tree/main/src/Rule/ChoicesRule.php) accepts `list` option. +7. [CollectionRule](https://github.com/elie29/validator/tree/main/src/Rule/CollectionRule.php) accepts `rules` and `json` options. +8. [CompareRule](https://github.com/elie29/validator/tree/main/src/Rule/CompareRule.php) accepts `sign` and `expected` options. `sign` is [CompareRule::EQ](https://github.com/elie29/validator/tree/main/src/Rule/CompareConstants.php) by default, `expected` is null by default. +9. [DateRule](https://github.com/elie29/validator/tree/main/src/Rule/DateRule.php) accepts `format` and `separator` options. +10. [EmailRule](https://github.com/elie29/validator/tree/main/src/Rule/EmailRule.php) validates email addresses. +11. [IpRule](https://github.com/elie29/validator/tree/main/src/Rule/IpRule.php) accepts `flag` option. +12. [JsonRule](https://github.com/elie29/validator/tree/main/src/Rule/JsonRule.php) accepts `decode` option. +13. [MatchRule](https://github.com/elie29/validator/tree/main/src/Rule/MatchRule.php) requires `pattern` option. +14. [MultipleAndRule](https://github.com/elie29/validator/tree/main/src/Rule/MultipleAndRule.php) requires `rules` option (all rules must pass). +15. [MultipleOrRule](https://github.com/elie29/validator/tree/main/src/Rule/MultipleOrRule.php) requires `rules` option (at least one rule must pass). +16. [NumericRule](https://github.com/elie29/validator/tree/main/src/Rule/NumericRule.php) accepts `min`, `max` and `cast` options. +17. [RangeRule](https://github.com/elie29/validator/tree/main/src/Rule/RangeRule.php) accepts `range` option. +18. [StringCleanerRule](https://github.com/elie29/validator/tree/main/src/Rule/StringCleanerRule.php) removes invisible characters from strings. +19. [StringRule](https://github.com/elie29/validator/tree/main/src/Rule/StringRule.php) accepts `min` and `max` options. +20. [TimeRule](https://github.com/elie29/validator/tree/main/src/Rule/TimeRule.php) validates time format. +21. [Your own rule](#how-to-add-a-new-rule) ### How to add a new rule -You need to implement [RuleInterface](https://github.com/elie29/validator/blob/master/src/Rule/RuleInterface.php) or to extend [AbstractRule](https://github.com/elie29/validator/blob/master/src/Rule/AbstractRule.php) +You need to implement [RuleInterface](https://github.com/elie29/validator/tree/main/src/Rule/RuleInterface.php) or to extend [AbstractRule](https://github.com/elie29/validator/tree/main/src/Rule/AbstractRule.php) ```php my_value = $params['my_value']; } - // + in order to add non existent key + // + to add a non-existent key $this->messages += [ $this::INVALID_MY_VALUE => '%key%: %value% my message %my_value%' ]; @@ -129,15 +130,15 @@ class MyValueRule extends AbstractRule ## Validated Context -Once validate is called, we can use validatedContext method in order to retrieve all validated values from the original +Once validate is called, we can use the validatedContext method to retrieve all validated values from the original context. -By default, all keys set in the rules array will be found in the validatedContext array. However, if we don't want to append -non existing keys, we should call appendExistingItemsOnly(true) before validation. +By default, all keys set in the 'rules' array will be found in the validatedContext array. However, if we don't want to append +non-existing keys, we should call appendExistingItemsOnly(true) before validation. ## Assertion Integration -Instead of using assertion key by key, you can validate the whole context and than use [Assertion](https://github.com/beberlei/assert) or [Assert](https://github.com/webmozart/assert) as follow: +Instead of using assertion key by key, you can validate the whole context and then use [Assertion](https://github.com/beberlei/assert) or [Assert](https://github.com/webmozart/assert) as follows: ```php validate(), $validator->getImplodedErrors()); -// OR - +// OR using beberlei/assert Assertion::true($validator->validate(), $validator->getImplodedErrors()); + +// OR using PHPUnit in tests +$this->assertSame(RuleInterface::VALID, $validator->validate(), $validator->getImplodedErrors()); ``` ### Partial Validation -Sometimes we need to validate the context partially, whenever we have a Json item or -keys that depend on each others. +Sometimes we need to validate the context partially, whenever we have a JSON item or +keys that depend on each other. -The following is an example when a context - eg. \$\_POST - should contains a Json user data: +The following is an example when a context - e.g., \$\_POST - should contain JSON user data: ```php +use Elie\Validator\Rule\JsonRule; +use Elie\Validator\Rule\MatchRule; +use Elie\Validator\Rule\NumericRule; +use Elie\Validator\Validator; + $rules = [ ['user', JsonRule::class, JsonRule::REQUIRED => true], ]; $validator = new Validator($_POST, $rules); -Assertion::true($validator->validate()); // this assertion validates that the user is in Json format +Assertion::true($validator->validate()); // this assertion validates that the user is in JSON format $validatedPost = $validator->getValidatedContext(); @@ -192,12 +202,12 @@ $validator->setRules($rules); // Decode user as it is a valid JSON $user = json_decode($validatedPost['user'], true); -$validator->setContext($user); // new context is now user data +$validator->setContext($user); // the new context is now user data Assertion::true($validator->validate()); // this assertion validates user data /* -Validate accepts a boolean argument - mergedValidatedContext - which is false by default. If set to true +Validate accepts a boolean argument - mergedValidatedContext - which is false by default. If set to true, $validator->getValidatedContext() would return: array:4 [▼ @@ -216,7 +226,7 @@ to [Partial Validation](#partial-validation) without merging data: ```php $rules = [ - // With Json decode, validated value will be decoded into array + // With json-decode, a validated value will be decoded into an array ['users', JsonRule::class, JsonRule::REQUIRED => true, JsonRule::DECODE => true], ]; @@ -224,7 +234,7 @@ $validator = new Validator([ 'users' => '[{"name":"John","age":25},{"name":"Brad","age":42}]' ], $rules); -Assertion::true($validator->validate()); // this validate that users is a valid Json format +Assertion::true($validator->validate()); // this validates that users is a valid JSON format // But we need to validate all user data as well (suppose it should contain name and age): $validator->setRules([ @@ -247,7 +257,7 @@ foreach ($users as $user) { ``` -A new [CollectionRule](https://github.com/elie29/validator/blob/master/src/Rule/CollectionRule.php) has been added in order to validate a collection data (array or json) as follow: +A new [CollectionRule](https://github.com/elie29/validator/tree/main/src/Rule/CollectionRule.php) has been added to validate collection data (array or JSON) as follows: ```php $rules = [ @@ -263,11 +273,11 @@ $data = [ $validator = new Validator($data, $rules); -assertThat($validator->validate(), is(true)); +$this->assertSame(RuleInterface::VALID, $validator->validate()); $users = $validator->getValidatedContext()['users']; -assertThat($users, arrayWithSize(2)); +$this->assertCount(2, $users); ``` ## Development Prerequisites @@ -276,16 +286,9 @@ assertThat($users, arrayWithSize(2)); - UTF-8 -### Code style formatter - -- Zend Framework coding standard - ### Composer commands -- `clean`: Cleans all generated files -- `test`: Launches unit test -- `test-coverage`: Launches unit test with clover.xml file generation -- `cs-check`: For code sniffer check -- `cs-fix`: For code sniffer fix -- `phpstan`: Launches PHP Static Analysis Tool -- `check`: Launches `clean`, `cs-check`, `test` and `phpstan` +- `composer test`: Runs unit tests without coverage +- `composer test-coverage`: Runs unit tests with code coverage (requires Xdebug) +- `composer cover`: Runs tests with coverage and starts a local server to view coverage report at +- `composer clean`: Cleans all generated files (build directory and clover.xml) diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 0000000..1c2475c --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,4 @@ + build/phpstan.xml || true", - "cs-check": "phpcs", - "cs-fix": "phpcbf", - "test": "phpunit", - "test-coverage": "phpunit --coverage-clover clover.xml", "clean": [ "rm -rf build/*", "rm -f clover.xml" diff --git a/composer.lock b/composer.lock index 876603c..d6feb71 100644 --- a/composer.lock +++ b/composer.lock @@ -4,81 +4,25 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "31079e5781c0ea072aeb463f641b9fde", + "content-hash": "a2018839dac773836e5c0b0f1348f366", "packages": [], "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "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": "2019-10-21T16:45:58+00:00" - }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.0", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -86,14 +30,13 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "^1.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -103,46 +46,52 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD" + "BSD-3-Clause" ], "description": "This is the PHP port of Hamcrest Matchers", "keywords": [ "test" ], - "time": "2016-01-20T08:20:44+00:00" + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" }, { "name": "mockery/mockery", - "version": "1.3.0", + "version": "1.6.12", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "5571962a4f733fbb57bede39778f71647fae8e66" + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/5571962a4f733fbb57bede39778f71647fae8e66", - "reference": "5571962a4f733fbb57bede39778f71647fae8e66", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "~2.0", + "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": ">=5.6.0", - "sebastian/comparator": "^1.2.4|^3.0" + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { - "psr-0": { - "Mockery": "library/" + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -153,12 +102,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -175,41 +132,50 @@ "test double", "testing" ], - "time": "2019-11-24T07:54:50+00:00" + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -223,29 +189,41 @@ "object", "object graph" ], - "time": "2019-08-09T12:45:53+00:00" + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v4.3.0", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { - "ircmaxell/php-yacc": "0.0.5", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -253,7 +231,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -275,32 +253,38 @@ "parser", "php" ], - "time": "2019-11-08T13:50:10+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -330,24 +314,34 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "2.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -377,403 +371,405 @@ } ], "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "name": "phpunit/php-code-coverage", + "version": "11.0.11", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "~6" + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "coverage", + "testing", + "xunit" ], - "time": "2018-08-07T13:53:10+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-08-27T14:37:49+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "4.3.2", + "name": "phpunit/php-file-iterator", + "version": "5.1.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", - "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", - "webmozart/assert": "^1.0" + "php": ">=8.2" }, "require-dev": { - "doctrine/instantiator": "^1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-09-12T14:27:41+00:00" + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.0.1", + "name": "phpunit/php-invoker", + "version": "5.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": "^7.1", - "phpdocumentor/reflection-common": "^2.0" + "php": ">=8.2" }, "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.9.0", + "name": "phpunit/php-text-template", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "php": ">=8.2" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" + "template" ], - "time": "2019-10-03T11:07:50+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "51a9bf6f5612a8a19f1e1e03385bd03cea62c7b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51a9bf6f5612a8a19f1e1e03385bd03cea62c7b1", - "reference": "51a9bf6f5612a8a19f1e1e03385bd03cea62c7b1", - "shasum": "" - }, - "require": { - "php": "~7.1" + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "time": "2019-11-21T20:52:51+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { - "name": "phpstan/phpstan", - "version": "0.12.0", + "name": "phpunit/php-timer", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "1cc06d8493917d16b1742f6c9fd2963c51a4e773" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1cc06d8493917d16b1742f6c9fd2963c51a4e773", - "reference": "1cc06d8493917d16b1742f6c9fd2963c51a4e773", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "nikic/php-parser": "^4.3.0", - "php": "^7.1" + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-main": "7.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "PHPStan - PHP Static Analysis Tool", - "time": "2019-12-04T00:09:47+00:00" - }, - { - "name": "phpstan/phpstan-mockery", - "version": "0.12.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-mockery.git", - "reference": "f83b96663e4a8d5b3ebc9308c194e599f174df8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-mockery/zipball/f83b96663e4a8d5b3ebc9308c194e599f174df8c", - "reference": "f83b96663e4a8d5b3ebc9308c194e599f174df8c", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.3", - "php": "~7.1", - "phpstan/phpdoc-parser": "^0.4", - "phpstan/phpstan": "^0.12" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", - "jakub-onderka/php-parallel-lint": "^1.0", - "mockery/mockery": "^1.2.4", - "phing/phing": "^2.16.0", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.2", - "slevomat/coding-standard": "^4.7.2", - "squizlabs/php_codesniffer": "^3.3.2" - }, - "type": "phpstan-extension", - "extra": { - "branch-alias": { - "dev-master": "0.12-dev" - }, - "phpstan": { - "includes": [ - "extension.neon" - ] + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "description": "PHPStan Mockery extension", - "time": "2019-11-24T10:30:54+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "6.1.4", + "name": "phpunit/phpunit", + "version": "11.5.46", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", "shasum": "" }, "require": { "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.11", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.2", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-main": "11.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -789,39 +785,66 @@ "role": "lead" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "coverage", + "phpunit", "testing", "xunit" ], - "time": "2018-10-31T16:06:48+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-12-06T08:01:15+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "2.0.2", + "name": "sebastian/cli-parser", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -840,79 +863,45 @@ "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" - ], - "time": "2018-09-13T20:33:42+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { - "name": "phpunit/php-timer", - "version": "2.1.2", + "name": "sebastian/code-unit", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -931,38 +920,45 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2019-06-07T04:22:29+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "3.1.1", + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -980,70 +976,52 @@ "email": "sebastian@phpunit.de" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2019-09-17T06:23:10+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { - "name": "phpunit/phpunit", - "version": "7.5.17", + "name": "sebastian/comparator", + "version": "6.3.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a", - "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", - "sebastian/version": "^2.0.1" - }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "ext-pdo": "*" + "phpunit/phpunit": "^11.4" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "ext-bcmath": "For comparing BcMath\\Number objects" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "7.5-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -1058,90 +1036,78 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ - "phpunit", - "testing", - "xunit" + "comparator", + "compare", + "equality" ], - "time": "2019-10-28T10:37:36+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { - "name": "sebastian/comparator", - "version": "3.0.2", + "name": "sebastian/complexity", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "php": "^7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1154,57 +1120,52 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2018-07-12T15:12:46+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "3.0.2", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1217,13 +1178,13 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", @@ -1234,27 +1195,38 @@ "unidiff", "unified diff" ], - "time": "2019-02-04T06:01:07+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "4.2.3", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -1262,7 +1234,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -1281,40 +1253,63 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", "hhvm" ], - "time": "2019-11-20T08:46:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.2", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -1349,40 +1344,63 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], - "time": "2019-09-14T09:02:43+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-uopz": "*" + "ext-dom": "*", + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1401,38 +1419,107 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.3", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1452,32 +1539,43 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1497,32 +1595,43 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1535,44 +1644,70 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "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": "2017-03-03T06:23:57+00:00" + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" }, { - "name": "sebastian/resource-operations", - "version": "2.0.1", + "name": "sebastian/type", + "version": "5.1.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1587,34 +1722,58 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04T04:07:39+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1635,118 +1794,101 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "2.9.2", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", - "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "ext-simplexml": "*", "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.1.2" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, - "bin": [ - "scripts/phpcs", - "scripts/phpcbf" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "classmap": [ - "CodeSniffer.php", - "CodeSniffer/CLI.php", - "CodeSniffer/Exception.php", - "CodeSniffer/File.php", - "CodeSniffer/Fixer.php", - "CodeSniffer/Report.php", - "CodeSniffer/Reporting.php", - "CodeSniffer/Sniff.php", - "CodeSniffer/Tokens.php", - "CodeSniffer/Reports/", - "CodeSniffer/Tokenizers/", - "CodeSniffer/DocGenerators/", - "CodeSniffer/Standards/AbstractPatternSniff.php", - "CodeSniffer/Standards/AbstractScopeSniff.php", - "CodeSniffer/Standards/AbstractVariableSniff.php", - "CodeSniffer/Standards/IncorrectPatternException.php", - "CodeSniffer/Standards/Generic/Sniffs/", - "CodeSniffer/Standards/MySource/Sniffs/", - "CodeSniffer/Standards/PEAR/Sniffs/", - "CodeSniffer/Standards/PSR1/Sniffs/", - "CodeSniffer/Standards/PSR2/Sniffs/", - "CodeSniffer/Standards/Squiz/Sniffs/", - "CodeSniffer/Standards/Zend/Sniffs/" + "lib/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "authors": [ + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ { - "name": "Greg Sherwood", - "role": "lead" + "url": "https://github.com/staabm", + "type": "github" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2018-11-07T22:31:41+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.13.1", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": ">=8.1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "1.13-dev" + "dev-main": "3.6-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ - "bootstrap.php" + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1755,93 +1897,164 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.3", + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-06-13T22:48:21+00:00" + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" }, { - "name": "webmozart/assert", - "version": "1.6.0", + "name": "symfony/var-dumper", + "version": "v6.4.26", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "cfae1497a2f1eaad78dbc0590311c599c7178d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", - "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cfae1497a2f1eaad78dbc0590311c599c7178d4a", + "reference": "cfae1497a2f1eaad78dbc0590311c599c7178d4a", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0", - "symfony/polyfill-ctype": "^1.8" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "symfony/console": "<5.4" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "autoload": { + "files": [ + "Resources/functions/dump.php" + ], "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1849,56 +2062,107 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Assertions to validate method input/output with nice error messages.", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", "keywords": [ - "assert", - "check", - "validate" + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.4.26" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2019-11-24T13:36:37+00:00" + "time": "2025-09-25T15:37:27+00:00" }, { - "name": "zendframework/zend-coding-standard", - "version": "1.0.0", + "name": "theseer/tokenizer", + "version": "1.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-coding-standard.git", - "reference": "893316d2904e93f1c74c1384b6d7d57778299cb6" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-coding-standard/zipball/893316d2904e93f1c74c1384b6d7d57778299cb6", - "reference": "893316d2904e93f1c74c1384b6d7d57778299cb6", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { - "squizlabs/php_codesniffer": "^2.7" + "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" ], - "description": "Zend Framework coding standard", - "keywords": [ - "Coding Standard", - "zf" + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } ], - "time": "2016-11-09T21:30:43+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.1", - "ext-json": "*" + "php": "^8.2", + "ext-json": "*", + "ext-ctype": "*" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.2" }, - "platform-dev": [] + "plugin-api-version": "2.9.0" } diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index fc7ec4e..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - src - tests - diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index 3a20d3d..0000000 --- a/phpstan.neon +++ /dev/null @@ -1,11 +0,0 @@ -# https://ne-on.org/ - -parameters: - level: max - ignoreErrors: - - '#^Call to an undefined method Mockery\\Mock#' - - '#function preg_replace#' - - '#Text::removeInvisibleChars#' - -includes: - - vendor/phpstan/phpstan-mockery/extension.neon diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b3a2f0c..8d3f881 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,33 +1,34 @@ - - - - - ./tests - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd" + bootstrap="bootstrap.php" + colors="true" + cacheResult="false" +> + + + + tests + + - - - + + + + src + + - - - ./src - - + + + + + + + + + + + diff --git a/src/Helper/Text.php b/src/Helper/Text.php index b730c9f..1fc171c 100644 --- a/src/Helper/Text.php +++ b/src/Helper/Text.php @@ -1,6 +1,6 @@ 'Code message %code% is undefined', self::EMPTY_KEY => '%key% is required and should not be empty: %value%', ]; /** - * If key's context is required or not. - * If required, value should not be empty. + * If the key's context is required or not. + * If required, the value should not be empty. * Default sets to false. - * @var bool */ - protected $required = false; + protected bool $required = false; /** * Default constructor to set common params. @@ -52,17 +45,17 @@ abstract class AbstractRule implements RuleInterface * @param mixed $value Value to be validated. * @param array $params Default params. */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { $this->key = $key; if (isset($params[$this::REQUIRED])) { - $this->required = (bool) $params[$this::REQUIRED]; + $this->required = (bool)$params[$this::REQUIRED]; } // trim constant is not available in all rules if (isset($params['trim'])) { - $this->trim = (bool) $params['trim']; + $this->trim = (bool)$params['trim']; } if (isset($params[$this::MESSAGES])) { @@ -73,16 +66,25 @@ public function __construct($key, $value, array $params = []) $this->setValue($value); } - public function getKey() + public function getKey(): int|string { return $this->key; } - public function getValue() + public function getValue(): mixed { return $this->value; } + public function setValue(mixed $value): void + { + if (is_string($value) && $this->trim) { + $value = trim($value); + } + + $this->value = $value; + } + public function getError(): string { return $this->error; @@ -92,8 +94,8 @@ public function validate(): int { $this->error = ''; - if (! $this->isEmpty()) { - // Value is not empty so keep checking + if (!$this->isEmpty()) { + // Value is not empty, so keep checking return $this::CHECK; } @@ -106,17 +108,8 @@ public function validate(): int return $this::VALID; } - public function setValue($value): void - { - if (is_string($value) && $this->trim) { - $value = trim($value); - } - - $this->value = $value; - } - /** - * If value is required, it should not be empty. + * If a value is required, it should not be empty. * {@link error} is set to blank. * * @return bool True means that value is required and empty. @@ -143,7 +136,7 @@ protected function setAndReturnError(string $errorCode, array $replace = []): in { $message = $this->messages[$errorCode] ?? $this->messages[$this::UNDEFINED_CODE]; - // + is used to add non existent keys + // + is used to add non-existent keys $replace += [ '%key%' => $this->key, '%value%' => $this->stringify($this->value), @@ -157,7 +150,7 @@ protected function setAndReturnError(string $errorCode, array $replace = []): in protected function stringify($value): string { - if (is_object($value) && ! in_array('__toString', get_class_methods($value))) { + if (is_object($value) && !in_array('__toString', get_class_methods($value))) { return get_class($value) . ' object'; } @@ -178,6 +171,6 @@ protected function fromScalar($value): string return ''; } - return (string) $value; + return (string)$value; } } diff --git a/src/Rule/ArrayRule.php b/src/Rule/ArrayRule.php index a59a1f4..fb60f8a 100644 --- a/src/Rule/ArrayRule.php +++ b/src/Rule/ArrayRule.php @@ -1,12 +1,12 @@ * [ * 'required' => {bool:optional:false by default}, * 'messages' => {array:optional:key/value message patterns}, * 'min' => {int:optional:0 by default}, * 'max' => {int:optional:value count by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); if (isset($params[$this::MIN])) { - $this->min = (int) $params[$this::MIN]; + $this->min = (int)$params[$this::MIN]; } if (isset($params[$this::MAX])) { - $this->max = (int) $params[$this::MAX]; + $this->max = (int)$params[$this::MAX]; } // + won't replace existing keys set by users - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_ARRAY => '%key% does not have an array value: %value%', $this::INVALID_ARRAY_LENGTH => '%key%: The length of %value% is not between %min% and %max%', ]; } - public function getValue() + public function getValue(): mixed { - // don't change value on error or if it is not empty + // don't change the value on error or if it is not empty if ($this->value || $this->error) { return $this->value; } @@ -80,7 +82,7 @@ public function validate(): int return $run; } - if (! is_array($this->value)) { + if (!is_array($this->value)) { return $this->setAndReturnError($this::INVALID_ARRAY); } diff --git a/src/Rule/BicRule.php b/src/Rule/BicRule.php index 6e122d1..a44ea55 100644 --- a/src/Rule/BicRule.php +++ b/src/Rule/BicRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_BIC_LIMIT => '%key%: %value% has an invalid length', $this::INVALID_BIC_UPPER => '%key%: %value% should be uppercase', $this::INVALID_BIC_ALNUM => '%key%: %value% should be alphanumeric', @@ -62,15 +64,15 @@ public function validate(): int protected function setErrorCode(): int { $methodsValidation = [ - 'invalidLimit', - 'invalidUppercase', - 'invalidAlphaNumeric', - 'invalidBankCode', - 'invalidCountryCode', + fn() => $this->invalidLimit(), + fn() => $this->invalidUppercase(), + fn() => $this->invalidAlphaNumeric(), + fn() => $this->invalidBankCode(), + fn() => $this->invalidCountryCode(), ]; foreach ($methodsValidation as $method) { - $codeError = $this->$method(); + $codeError = $method(); if ($codeError !== null) { return $this->setAndReturnError($codeError); } diff --git a/src/Rule/BooleanRule.php b/src/Rule/BooleanRule.php index 5266d36..5a78cb3 100644 --- a/src/Rule/BooleanRule.php +++ b/src/Rule/BooleanRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default:only if value is string}, * 'messages' => {array:optional:key/value message patterns}, * 'cast' => {bool:optional:cast the value into bool:false by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); if (isset($params[$this::CAST])) { - $this->cast = (bool) $params[$this::CAST]; + $this->cast = (bool)$params[$this::CAST]; } - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_BOOLEAN => '%key%: %value% is not a valid boolean', ]; } - public function getValue() + public function getValue(): mixed { - if ($this->cast && ! $this->error) { - return (bool) $this->value; + if ($this->cast && !$this->error) { + return (bool)$this->value; } return $this->value; } @@ -65,7 +67,7 @@ public function validate(): int return $run; } - if (! $this->isBool()) { + if (!$this->isBool()) { return $this->setAndReturnError($this::INVALID_BOOLEAN); } diff --git a/src/Rule/CallableRule.php b/src/Rule/CallableRule.php index fc072db..86df235 100644 --- a/src/Rule/CallableRule.php +++ b/src/Rule/CallableRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default:only if value is string}, * 'messages' => {array:optional:key/value message patterns}, * 'callable' => {callable:required:should receive key/value/CallableRule and return boolean} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); $this->callable = $params[self::CALLABLE]; - $this->messages = $this->messages + [ + $this->messages += [ self::INVALID_CALLABLE_CHECK => '%key%: %value% did not pass the callable check', ]; } @@ -53,7 +55,7 @@ public function validate(): int $callable = $this->callable; - if (! $callable($this->key, $this->value, $this)) { + if (!$callable($this->key, $this->value, $this)) { return $this->setAndReturnError($this::INVALID_CALLABLE_CHECK); } diff --git a/src/Rule/ChoicesRule.php b/src/Rule/ChoicesRule.php index 9505ec7..a3d33f7 100644 --- a/src/Rule/ChoicesRule.php +++ b/src/Rule/ChoicesRule.php @@ -1,12 +1,12 @@ * [ * 'required' => {bool:optional}, * 'messages' => {array:optional:key/value message patterns}, * 'list' => {array:optional:empty array by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); @@ -44,8 +45,8 @@ public function __construct($key, $value, array $params = []) $this->list = $params[self::LIST]; } - $this->messages = $this->messages + [ - self::INVALID_ITEM => _('%key%: %item% is not in the given list : %list%'), + $this->messages += [ + self::INVALID_ITEM => '%key%: %item% is not in the given list : %list%', ]; } @@ -58,7 +59,7 @@ public function validate(): int } foreach ($this->value as $item) { - if (! in_array($item, $this->list, true)) { + if (!in_array($item, $this->list, true)) { return $this->setAndReturnError(self::INVALID_ITEM, [ '%item%' => $item, '%list%' => $this->stringify($this->list), diff --git a/src/Rule/CollectionRule.php b/src/Rule/CollectionRule.php index f431a54..f422c09 100644 --- a/src/Rule/CollectionRule.php +++ b/src/Rule/CollectionRule.php @@ -1,11 +1,11 @@ * [ * 'required' => {bool:optional:false by default}, * 'messages' => {array:optional:key/value message patterns}, * 'rules' => {array:optional:list of rules with their params}, * 'json' => {boolean:optional:false by default}, * ] + * * - * $params = [
- * 'required' => true,
- * 'rules' => [
- * ['code', StringRule::class, 'min' => 1, 'max' => 255],
- * ['email', EmailRule::class],
+ * $params = [ + * 'required' => true, + * 'rules' => [ + * ['code', StringRule::class, 'min' => 1, 'max' => 255], + * ['email', EmailRule::class], * ] * ] *
* - * Value is considered valid if 'rules' is empty + * Value is considered valid if 'rules' are empty */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); @@ -55,11 +57,11 @@ public function __construct($key, $value, array $params = []) } if (isset($params[self::JSON])) { - $this->decode = (bool) $params[self::JSON]; + $this->decode = (bool)$params[self::JSON]; } - $this->messages = $this->messages + [ - self::INVALID_VALUE => _('%key%: %value% is not in a collection'), + $this->messages += [ + self::INVALID_VALUE => '%key%: %value% is not in a collection', ]; } @@ -74,13 +76,23 @@ public function validate(): int return $this->rules === [] ? self::VALID : $this->isValid(); } + public function getValue(): mixed + { + // don't change the value on error or if it is not empty + if ($this->value || $this->error) { + return $this->value; + } + + return []; + } + protected function isValid(): int { $this->error = ''; $collection = $this->decode ? json_decode($this->value, true) : $this->value; - if (! is_array($collection)) { + if (!is_array($collection)) { return $this->setAndReturnError(self::INVALID_VALUE); } @@ -113,16 +125,6 @@ protected function resolve(array $rule): RuleInterface return new $class($key, null, $rule); } - public function getValue() - { - // don't change value on error or if it is not empty - if ($this->value || $this->error) { - return $this->value; - } - - return []; - } - /** * Empty value is null or [] only. * diff --git a/src/Rule/CompareConstants.php b/src/Rule/CompareConstants.php index 300e3b1..f5c7bdc 100644 --- a/src/Rule/CompareConstants.php +++ b/src/Rule/CompareConstants.php @@ -1,6 +1,6 @@ = - public const LT = 'lt'; // < - public const GT = 'gt'; // > + public const LTE = 'lte'; // <= + public const GTE = 'gte'; // >= + public const LT = 'lt'; // < + public const GT = 'gt'; // > /**#@-*/ /** * Supported signs */ public const SIGNS = [ - self::EQ => 'equal to', - self::SEQ => 'same as', - self::NEQ => 'not equal to', + self::EQ => 'equal to', + self::SEQ => 'same as', + self::NEQ => 'not equal to', self::NSEQ => 'not same as', - self::LTE => 'less or equal to', - self::GTE => 'greater or equal to', - self::LT => 'less than', - self::GT => 'greater than', + self::LTE => 'less or equal to', + self::GTE => 'greater or equal to', + self::LT => 'less than', + self::GT => 'greater than', ]; /** diff --git a/src/Rule/CompareRule.php b/src/Rule/CompareRule.php index aa74328..c5ad47b 100644 --- a/src/Rule/CompareRule.php +++ b/src/Rule/CompareRule.php @@ -1,6 +1,6 @@ =, <, >. * Default sets to ==. - * @var string */ - protected $sign = self::EQ; + protected string $sign = self::EQ; /** * Expected value. * Default sets to null. - * @var mixed */ - protected $expected = null; + protected mixed $expected = null; /** * Params could have the following structure: + * * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, @@ -33,8 +32,9 @@ class CompareRule extends AbstractRule implements CompareConstants * 'sign' => {string:optional:EQ by default}, * 'expected' => {mixed:optional:null by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); @@ -46,7 +46,7 @@ public function __construct($key, $value, array $params = []) $this->expected = $params['expected']; } - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_COMPARE => '%key%: %value% is not %label% %expected%', ]; } @@ -60,7 +60,7 @@ public function validate(): int } $method = $this->sign; - if (! $this->$method()) { + if (!$this->$method()) { return $this->setAndReturnError($this::INVALID_COMPARE, [ '%label%' => $this::SIGNS[$this->sign], '%expected%' => $this->stringify($this->expected), diff --git a/src/Rule/DateRule.php b/src/Rule/DateRule.php index 60d2d3f..9d970d7 100644 --- a/src/Rule/DateRule.php +++ b/src/Rule/DateRule.php @@ -1,6 +1,6 @@ 2/9/71 + * Simple: d/m/y => 2/9/71 * full: dd/mm/yyyy => 02/09/1971 * We can provide one or more formats to validate a date. - * format tokens are : + * + * Format tokens are: * d: day from 1 to 31 * dd: day from 01 to 31 * m: month from 1 to 12 @@ -44,31 +45,32 @@ class DateRule extends AbstractRule * 69 => 2069 * 72 => 1972 * yyyy: Year with four digits, we can omit all zeros on the left - * 2 => 0002 + * 2 => 0002 * 69 => 0069 * 72 => 0072 - * @var array + * */ - protected $format = ['dd/mm/yyyy']; + protected array $format = ['dd/mm/yyyy']; /** * Accepted separators for a date. - * comma, dash, dot and slash. - * @var string + * Comma, dash, dot and slash. */ - protected $separator = '[,-./]'; + protected string $separator = '[,-./]'; /** * Params could have the following structure: + * * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns}, - * 'format'=>{string|array:optional:dd/mm/yyyy by default}, - * 'separator'=>{string:optional:(,-./) by default} + * 'format' => {string|array:optional:dd/mm/yyyy by default}, + * 'separator' => {string:optional:[,-./] by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); @@ -80,70 +82,21 @@ public function __construct($key, $value, array $params = []) $this->setSeparator($params[$this::SEPARATOR]); } - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_DATE => '%key%: %value% is not a valid date', $this::INVALID_DATE_FORMAT => '%key%: %value% does not have a valid format: ' . '%format% or separator: %separator%', ]; } - public function getFormat(): array - { - return $this->format; - } - - public function getSeparator(): string - { - return $this->separator; - } - - /** - * @param array|string $format - */ - public function setFormat($format): self - { - $this->format = (array) $format; - return $this; - } - - public function setSeparator(string $separator): self - { - $this->separator = $separator; - return $this; - } - - public function validate(): int - { - $run = parent::validate(); - - if ($run !== $this::CHECK) { - return $run; - } - - // We try to check value over all format - $codeError = $this::INVALID_DATE; - foreach ($this->getFormat() as $format) { - $codeError = static::checkDate($this->separator, $format, $this->value); - if ($codeError === null) { - // The first valid format - return $this::VALID; - } - } - - return $this->setAndReturnError($codeError, [ - '%format%' => $this->stringify($this->format), - '%separator%' => $this->separator, - ]); - } - /** * Validate a Gregorian date according to separator and format options. * - * DateRule::checkDate('[/]', 'dd/mm/yyyy', '27/02/2017'); // returns null
- * DateRule::checkDate('[/]', 'dd/mm/yyyy', '29/02/2017'); // returns INVALID_DATE
+ * DateRule::checkDate('[/]', 'dd/mm/yyyy', '27/02/2017'); // returns null + * DateRule::checkDate('[/]', 'dd/mm/yyyy', '29/02/2017'); // returns INVALID_DATE *
* - * @return string|NULL Null if date is valid otherwise INVALID_DATE|INVALID_DATE_FORMAT + * @return string|NULL Null if the date is valid otherwise INVALID_DATE|INVALID_DATE_FORMAT */ public static function checkDate(string $separator, string $format, string $date): ?string { @@ -167,17 +120,8 @@ public static function checkDate(string $separator, string $format, string $date protected static function invalidTokens(array $tokens): bool { - $seen = []; - foreach ($tokens as $token) { - // token already seen - if (isset($seen[$token])) { - return true; - } - $seen[$token] = true; - } - - // We must have 3 tokens - return count($seen) !== 3; + // We must have 3 unique tokens + return count(array_unique($tokens, SORT_REGULAR)) !== 3; } protected static function checkDatesWithTokens(array $tokens, array $dates): ?string @@ -189,18 +133,18 @@ protected static function checkDatesWithTokens(array $tokens, array $dates): ?st switch ($tokens[$t]) { case 'd': case 'dd': - $d = (int) $dates[$t]; + $d = (int)$dates[$t]; break; case 'm': case 'mm': - $m = (int) $dates[$t]; + $m = (int)$dates[$t]; break; case 'yy': - $y = (int) $dates[$t]; + $y = (int)$dates[$t]; $y = ($y >= 70) ? $y + 1900 : $y + 2000; break; case 'yyyy': - $y = (int) $dates[$t]; + $y = (int)$dates[$t]; break; default: // No valid format @@ -210,4 +154,54 @@ protected static function checkDatesWithTokens(array $tokens, array $dates): ?st return checkdate($m, $d, $y) ? null : self::INVALID_DATE; } + + public function getFormat(): array + { + return $this->format; + } + + /** + * @param array|string $format + * @return DateRule + */ + public function setFormat(array|string $format): static + { + $this->format = (array)$format; + return $this; + } + + public function getSeparator(): string + { + return $this->separator; + } + + public function setSeparator(string $separator): static + { + $this->separator = $separator; + return $this; + } + + public function validate(): int + { + $run = parent::validate(); + + if ($run !== $this::CHECK) { + return $run; + } + + // We try to check value over all format + $codeError = $this::INVALID_DATE; + foreach ($this->getFormat() as $format) { + $codeError = static::checkDate($this->separator, $format, $this->value); + if ($codeError === null) { + // The first valid format + return $this::VALID; + } + } + + return $this->setAndReturnError($codeError, [ + '%format%' => $this->stringify($this->format), + '%separator%' => $this->separator, + ]); + } } diff --git a/src/Rule/EmailRule.php b/src/Rule/EmailRule.php index c7f0b41..530ad5f 100644 --- a/src/Rule/EmailRule.php +++ b/src/Rule/EmailRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); - $this->messages = $this->messages + [ + $this->messages += [ self::INVALID_EMAIL => '%key%: %value% is not a valid email', ]; } @@ -45,7 +47,7 @@ public function validate(): int return $run; } - if (! $this->isValid()) { + if (!$this->isValid()) { return $this->setAndReturnError(self::INVALID_EMAIL); } diff --git a/src/Rule/IpRule.php b/src/Rule/IpRule.php index 5a8d861..8481057 100644 --- a/src/Rule/IpRule.php +++ b/src/Rule/IpRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns}, * 'flag' => {int:optional:FILTER_FLAG_IPV4 by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); if (isset($params[$this::FLAG])) { - $this->flag = (int) $params[$this::FLAG]; + $this->flag = (int)$params[$this::FLAG]; } - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_IP => '%key%: %value% is not a valid IP', $this::INVALID_IP_FLAG => 'Filter IP flag: %flag% is not valid', ]; diff --git a/src/Rule/JsonRule.php b/src/Rule/JsonRule.php index c80ea23..e341bb5 100644 --- a/src/Rule/JsonRule.php +++ b/src/Rule/JsonRule.php @@ -1,11 +1,11 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns}, * 'decode' => {bool:optional:false by default:decode the value} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); if (isset($params[$this::DECODE])) { - $this->decode = (bool) $params[$this::DECODE]; + $this->decode = (bool)$params[$this::DECODE]; } - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_JSON => '%key%: %value% is not a valid json format', ]; } - public function getValue() + public function getValue(): mixed { - if ($this->error || $this->value || ! $this->decode) { + if ($this->error || $this->value || !$this->decode) { return $this->value; } - // only if no error and decoded is requested for empty value + // only if no error and decoded is requested for an empty value return []; } @@ -66,7 +68,7 @@ public function validate(): int return $run; } - if (! $this->isValid()) { + if (!$this->isValid()) { return $this->setAndReturnError($this::INVALID_JSON); } diff --git a/src/Rule/MatchRule.php b/src/Rule/MatchRule.php index 36a31f7..2110ca0 100644 --- a/src/Rule/MatchRule.php +++ b/src/Rule/MatchRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns}, * 'pattern' => {string:required} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); $this->pattern = $params[$this::PATTERN]; - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_PATTERN => '%key%: %value% does not match %pattern%', ]; } @@ -57,7 +58,7 @@ public function validate(): int return $run; } - if (! preg_match($this->pattern, $this->value)) { + if (!preg_match($this->pattern, $this->value)) { return $this->setAndReturnError($this::INVALID_PATTERN, [ '%pattern%' => $this->pattern, ]); diff --git a/src/Rule/MultipleAndRule.php b/src/Rule/MultipleAndRule.php index 631b56e..803f759 100644 --- a/src/Rule/MultipleAndRule.php +++ b/src/Rule/MultipleAndRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'messages' => {array:optional:key/value message patterns}, * 'rules' => {array:optional:list of rules with their params}, * ] + * + * Should be string and email: * - * should be string and email - * $params = [
- * 'required' => true,
- * 'rules' => [
- * [StringRule::class, 'min' => 1, 'max' => 255],
- * [EmailRule::class],
+ * $params = [ + * 'required' => true, + * 'rules' => [ + * [StringRule::class, 'min' => 1, 'max' => 255], + * [EmailRule::class], * ] * ] *
* - * Value is considered valid if 'rules' is empty + * Value is considered valid if 'rules' are empty */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); diff --git a/src/Rule/MultipleOrRule.php b/src/Rule/MultipleOrRule.php index 10ed069..9462a83 100644 --- a/src/Rule/MultipleOrRule.php +++ b/src/Rule/MultipleOrRule.php @@ -1,6 +1,6 @@ getError(); } diff --git a/src/Rule/NumericRule.php b/src/Rule/NumericRule.php index f656dd3..d0c2388 100644 --- a/src/Rule/NumericRule.php +++ b/src/Rule/NumericRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default:only if value is string}, @@ -52,33 +53,34 @@ class NumericRule extends AbstractRule * 'max' => {int:optional:null by default}, * 'cast' => {bool:optional:cast the value into numeric:false by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); if (isset($params[$this::MIN])) { - $this->min = (int) $params[$this::MIN]; + $this->min = (int)$params[$this::MIN]; } if (isset($params[$this::MAX])) { - $this->max = (int) $params[$this::MAX]; + $this->max = (int)$params[$this::MAX]; } if (isset($params[$this::CAST])) { - $this->cast = (bool) $params[$this::CAST]; + $this->cast = (bool)$params[$this::CAST]; } - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_NUMERIC => '%key%: %value% is not numeric', $this::INVALID_NUMERIC_LT => '%key%: %value% is less than %min%', $this::INVALID_NUMERIC_GT => '%key%: %value% is greater than %max%', ]; } - public function getValue() + public function getValue(): mixed { - if ($this->cast && ! $this->error) { + if ($this->cast && !$this->error) { // float or int and empty value return $this->value ? 0 + $this->value : 0; } @@ -93,7 +95,7 @@ public function validate(): int return $run; } - if (! is_numeric($this->value)) { + if (!is_numeric($this->value)) { return $this->setAndReturnError($this::INVALID_NUMERIC); } diff --git a/src/Rule/RangeRule.php b/src/Rule/RangeRule.php index e50c87b..5e62c70 100644 --- a/src/Rule/RangeRule.php +++ b/src/Rule/RangeRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default:only if value is string}, * 'messages' => {array:optional:key/value message patterns}, * 'range' => {array:optional:empty array by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); - if (isset($params[$this::RANGE])) { - $this->range = $params[$this::RANGE]; + if (isset($params[self::RANGE])) { + $this->range = $params[self::RANGE]; } - $this->messages = $this->messages + [ - $this::INVALID_RANGE => '%key%: %value% is out of range %range%', + $this->messages += [ + self::INVALID_RANGE => '%key%: %value% is out of range %range%', ]; } @@ -59,8 +60,8 @@ public function validate(): int return $run; } - if (! in_array($this->value, $this->range, true)) { - return $this->setAndReturnError($this::INVALID_RANGE, [ + if (!in_array($this->value, $this->range, true)) { + return $this->setAndReturnError(self::INVALID_RANGE, [ '%range%' => $this->stringify($this->range), ]); } diff --git a/src/Rule/RuleInterface.php b/src/Rule/RuleInterface.php index 2444675..718b394 100644 --- a/src/Rule/RuleInterface.php +++ b/src/Rule/RuleInterface.php @@ -1,6 +1,6 @@ {array:optional:key/value message patterns} * ] */ - public function __construct($key, $value, array $params = []); + public function __construct(int|string $key, mixed $value, array $params = []); /** * Runs the rule and returns the result. * * @return int RuleInterface::CHECK to continue checking, - * RuleInterface::VALID if value respect the rule and - * RuleInterface::ERROR if error occurs. + * RuleInterface::VALID if the value respects the rule and + * RuleInterface::ERROR if an error occurs. */ public function validate(): int; @@ -66,7 +66,7 @@ public function getError(): string; * * @return int|string */ - public function getKey(); + public function getKey(): int|string; /** * Retrieve the value. Value will be trimmed @@ -74,7 +74,7 @@ public function getKey(); * * @return mixed */ - public function getValue(); + public function getValue(): mixed; /** * Set value properly. @@ -83,5 +83,5 @@ public function getValue(); * * @return void */ - public function setValue($value): void; + public function setValue(mixed $value): void; } diff --git a/src/Rule/StringCleanerRule.php b/src/Rule/StringCleanerRule.php index 589aac5..f390801 100644 --- a/src/Rule/StringCleanerRule.php +++ b/src/Rule/StringCleanerRule.php @@ -1,6 +1,6 @@ error && is_string($value)) { + // better in case called before validating + if (!$this->error && is_string($value)) { $value = Text::removeInvisibleChars($value); } diff --git a/src/Rule/StringRule.php b/src/Rule/StringRule.php index 7dc6aa8..208bfaf 100644 --- a/src/Rule/StringRule.php +++ b/src/Rule/StringRule.php @@ -1,6 +1,6 @@ * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, @@ -44,22 +45,23 @@ class StringRule extends AbstractRule * 'min' => {int:optional:0 by default}, * 'max' => {int:optional:value length by default} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); if (isset($params[$this::MIN])) { - $this->min = (int) $params[$this::MIN]; + $this->min = (int)$params[$this::MIN]; } if (isset($params[$this::MAX])) { - $this->max = (int) $params[$this::MAX]; + $this->max = (int)$params[$this::MAX]; } $this->messages = $this->messages + [ - $this::INVALID_STRING => '%key% does not have a string value: %value%', - $this::INVALID_STRING_LENGTH => '%key%: The length of %value% is not between %min% and %max%', - ]; + $this::INVALID_STRING => '%key% does not have a string value: %value%', + $this::INVALID_STRING_LENGTH => '%key%: The length of %value% is not between %min% and %max%', + ]; } public function validate(): int @@ -70,7 +72,7 @@ public function validate(): int return $run; } - if (! is_string($this->value)) { + if (!is_string($this->value)) { return $this->setAndReturnError($this::INVALID_STRING); } diff --git a/src/Rule/TimeRule.php b/src/Rule/TimeRule.php index f2266a6..b1d731d 100644 --- a/src/Rule/TimeRule.php +++ b/src/Rule/TimeRule.php @@ -1,13 +1,13 @@ 02:49 and 0 to the right of the - * minutes ans seconds so 3:4 => 03:40:00 + * minutes and seconds so 3:4 => 03:40:00 */ class TimeRule extends AbstractRule { @@ -22,40 +22,27 @@ class TimeRule extends AbstractRule */ public const TRIM = 'trim'; - protected const TIME_REGEX = '/^([0-1]{1}[0-9]{1}|[2]{1}[0-3]{1}):[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}$/'; + protected const TIME_REGEX = '/^([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/'; /** * Params could have the following structure: + * * [ * 'required' => {bool:optional:false by default}, * 'trim' => {bool:optional:true by default}, * 'messages' => {array:optional:key/value message patterns} * ] + * */ - public function __construct($key, $value, array $params = []) + public function __construct(int|string $key, mixed $value, array $params = []) { parent::__construct($key, $value, $params); - $this->messages = $this->messages + [ + $this->messages += [ $this::INVALID_TIME => '%key%: %value% is not a valid time', ]; } - public function validate(): int - { - $run = parent::validate(); - - if ($run !== $this::CHECK) { - return $run; - } - - if (! static::checkTime($this->value)) { - return $this->setAndReturnError($this::INVALID_TIME); - } - - return $this::VALID; - } - /** * Check if a given time has the following format: * hh:mm:ss or hh:mm @@ -79,10 +66,25 @@ public static function checkTime(string $time): bool // We put time in hh:mm:ss format with padding $time = str_pad($tokens[0], 2, '0', STR_PAD_LEFT) . ':' - . str_pad($tokens[1], 2, '0', STR_PAD_RIGHT) . ':' - . str_pad($second, 2, '0', STR_PAD_RIGHT); + . str_pad($tokens[1], 2, '0') . ':' + . str_pad($second, 2, '0'); // we test hh:mm:ss from 00:00:00 => 23:59:59 - return (bool) preg_match(self::TIME_REGEX, $time); + return (bool)preg_match(self::TIME_REGEX, $time); + } + + public function validate(): int + { + $run = parent::validate(); + + if ($run !== $this::CHECK) { + return $run; + } + + if (!static::checkTime($this->value)) { + return $this->setAndReturnError($this::INVALID_TIME); + } + + return $this::VALID; } } diff --git a/src/Validator.php b/src/Validator.php index c81986a..e2ef4e7 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -1,6 +1,6 @@ setStopOnError($stopOnError); } - public function setContext(array $context): self + public function setContext(array $context): static { $this->context = $context; return $this; @@ -66,21 +60,21 @@ public function getValidatedContext(): array return $this->validatedContext; } - public function setRules(array $rules): self + public function getRules(): array { - $this->rules = $rules; - // Keep chaining - return $this; + return $this->rules; } - public function getRules(): array + public function setRules(array $rules): static { - return $this->rules; + $this->rules = $rules; + // Keep chaining + return $this; } - public function appendExistingItemsOnly(bool $choice): self + public function appendExistingItemsOnly(bool $value): static { - $this->appendExistingItemOnly = $choice; + $this->appendExistingItemOnly = $value; // Keep chaining return $this; } @@ -95,7 +89,7 @@ public function getImplodedErrors(string $separator = '
'): string return implode($separator, $this->errors); } - public function get($key) + public function get(int|string $key): mixed { return $this->context[$key] ?? null; } @@ -105,7 +99,7 @@ public function shouldStopOnError(): bool return $this->stopOnError; } - public function setStopOnError(bool $stopOnError): self + public function setStopOnError(bool $stopOnError): static { $this->stopOnError = $stopOnError; @@ -132,7 +126,7 @@ public function validate(bool $mergeValidatedContext = false): bool $res = false; if ($this->stopOnError) { - return $res; + return false; } } @@ -153,7 +147,7 @@ protected function addKeyToValidatedContext(RuleInterface $rule): void { $key = $rule->getKey(); - if ($this->appendExistingItemOnly && ! array_key_exists($key, $this->context)) { + if ($this->appendExistingItemOnly && !array_key_exists($key, $this->context)) { return; } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index de3b4b1..2bc89b9 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -1,6 +1,6 @@ - * $rules = [
- * ['age', NumericRule::class, 'min' => 1, 'max' => 99],
- * ['role', RangeRule::class, 'range' => ['M', 'U', 'E']],
+ * $rules = [ + * ['age', NumericRule::class, 'min' => 1, 'max' => 99], + * ['role', RangeRule::class, 'range' => ['M', 'U', 'E']], * ] * * @@ -61,7 +58,7 @@ public function getValidatedContext(): array; * * @return ValidatorInterface */ - public function setRules(array $rules); + public function setRules(array $rules): ValidatorInterface; /** * Return all set rules. @@ -76,7 +73,7 @@ public function getRules(): array; * * @return ValidatorInterface */ - public function appendExistingItemsOnly(bool $value); + public function appendExistingItemsOnly(bool $value): ValidatorInterface; /** * Return all errors found. @@ -88,20 +85,20 @@ public function getErrors(): array; /** * Return all errors found. * - * @param string $separator Default separator is
. + * @param string $separator Default separator is . * * @return string */ - public function getImplodedErrors(string $separator = '
'): string; + public function getImplodedErrors(string $separator = ''): string; /** * Retrieves the value of a requested key from the {@link context}. * - * @param string|int $key Key to be get. + * @param int|string $key Key to be got. * - * @return mixed|null The retrieved value. + * @return mixed The retrieved value. */ - public function get($key); + public function get(int|string $key): mixed; /** * Whether to stop on error or not. @@ -113,17 +110,17 @@ public function shouldStopOnError(): bool; /** * Change stopOnError value. * - * @param bool $stopOnError Stop validation when a first error occurs. + * @param bool $stopOnError Stop validation when the first error occurs. * * @return ValidatorInterface */ - public function setStopOnError(bool $stopOnError); + public function setStopOnError(bool $stopOnError): ValidatorInterface; /** - * Each rule is executed. If error is found and stop on error + * Each rule is executed. If an error is found and stop on error * is set to true, we stop the process and we return false. * - * keys context will be ignored if they don't have any rule. + * Keys context will be ignored if they don't have any rule. * * @param bool $mergeValidatedContext Merge validated values with existing one. * Useful with partial validation diff --git a/tests/Helper/TextTest.php b/tests/Helper/TextTest.php new file mode 100644 index 0000000..722b5ae --- /dev/null +++ b/tests/Helper/TextTest.php @@ -0,0 +1,177 @@ +assertSame($expected, Text::removeInvisibleChars($input)); + } + + public function testRemoveInvisibleCharsRemovesUrlEncodedControlChars(): void + { + // %00 (null) et %1F (unit separator) doivent disparaître + $input = 'Test%00String%1F'; + $expected = 'TestString'; + + $this->assertSame($expected, Text::removeInvisibleChars($input)); + } + + public function testRemoveInvisibleCharsDoesNotStripNewlineAndCarriageReturn(): void + { + // \n (0x0A) et \r (0x0D) doivent être conservés + $input = "Line1\nLine2\rLine3"; + $expected = "Line1\nLine2\rLine3"; + + $this->assertSame($expected, Text::removeInvisibleChars($input)); + } + + public function testRemoveInvisibleCharsDoubleEncodedNull(): void + { + // Double-encoded null byte %2500 + $input = 'Test%2500String'; + + $result = Text::removeInvisibleChars($input); + + // removeInvisibleChars only removes direct %00, double encoding survives + $this->assertSame('Test%2500String', $result); + } + + public function testRemoveInvisibleCharsTripleEncoded(): void + { + // Triple-encoded null: %25%32%35%30%30 + $input = 'Test%25%32%35%30%30String'; + $result = Text::removeInvisibleChars($input); + + // Triple encoding is not decoded by removeInvisibleChars + $this->assertSame('Test%25%32%35%30%30String', $result); + } + + public function testFilterCRLFInjection(): void + { + // CRLF injection: %0D%0A + $input = "Header:%0D%0AX-Injected: true"; + $result = Text::removeInvisibleChars($input); + + // URL-encoded CRLF should be removed + $this->assertStringNotContainsString('%0D', $result); + $this->assertStringNotContainsString('%0A', $result); + } + + public function testFilterNullByteInjection(): void + { + // Null byte to truncate string + $input = "file.php%00.jpg"; + $result = Text::removeInvisibleChars($input); + + // %00 should be removed + $this->assertSame('file.php.jpg', $result); + } + + public function testRemoveInvisibleCharsMixedEncoding(): void + { + // Mix of literal and encoded control chars + $input = "Test\x00%00String"; + $result = Text::removeInvisibleChars($input); + + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsUTF8NullByte(): void + { + // UTF-8 encoded null (C0 80 is overlong encoding) + // Now properly filtered to prevent security bypass + $input = "Test\xC0\x80String"; + $result = Text::removeInvisibleChars($input); + + // Overlong UTF-8 sequence should be removed + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsUnicodeZeroWidthSpace(): void + { + // Unicode zero-width space U+200B (E2 80 8B in UTF-8) + $input = "Test\xE2\x80\x8BString"; + $result = Text::removeInvisibleChars($input); + + // Zero-width space should now be filtered + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsUnicodeRightToLeftOverride(): void + { + // U+202E (RLO - Right-to-Left Override) + $input = "Test\xE2\x80\xAEString"; + $result = Text::removeInvisibleChars($input); + + // RLO is now filtered to prevent phishing attacks + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsTabAndNewline(): void + { + // Tab (\t = 0x09) should be removed, but newline (\n = 0x0A) preserved + $input = "Line1\tTab\nLine2"; + $result = Text::removeInvisibleChars($input); + + // Tab removed, newline preserved + $this->assertStringNotContainsString("\t", $result); + $this->assertStringContainsString("\n", $result); + $this->assertSame("Line1Tab\nLine2", $result); + } + + public function testRemoveInvisibleCharsVerticalTab(): void + { + // Vertical tab (0x0B) should be removed + $input = "Test\x0BString"; + $result = Text::removeInvisibleChars($input); + + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsFormFeed(): void + { + // Form feed (0x0C) should be removed + $input = "Test\x0CString"; + $result = Text::removeInvisibleChars($input); + + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsDEL(): void + { + // DEL character (0x7F) should be removed + $input = "Test\x7FString"; + $result = Text::removeInvisibleChars($input); + + $this->assertSame('TestString', $result); + } + + public function testRemoveInvisibleCharsEmptyString(): void + { + $result = Text::removeInvisibleChars(''); + + $this->assertSame('', $result); + } + + public function testRemoveInvisibleCharsLongString(): void + { + // Test with a long string containing scattered control chars + $input = str_repeat( + 'a', 1000) . "\x00" . str_repeat('b', 1000) . '%00' + . str_repeat('c', 1000); + $result = Text::removeInvisibleChars($input); + + $this->assertStringNotContainsString("\x00", $result); + $this->assertStringNotContainsString('%00', $result); + $this->assertEquals(3000, strlen($result)); + } +} \ No newline at end of file diff --git a/tests/Rule/AbstractRuleTest.php b/tests/Rule/AbstractRuleTest.php index a75e4be..3f3d02e 100644 --- a/tests/Rule/AbstractRuleTest.php +++ b/tests/Rule/AbstractRuleTest.php @@ -1,30 +1,30 @@ validate(); - assertThat($res, identicalTo($expectedResult)); + $this->assertSame($expectedResult, $res); $error = $rule->getError(); - assertThat($error, identicalTo($expectedError)); + $this->assertSame($expectedError, $error); - assertThat('key', identicalTo($rule->getKey())); + $this->assertSame('key', $rule->getKey()); } public function testValidatorValue(): void @@ -33,53 +33,45 @@ public function testValidatorValue(): void $rule = new StubAbstractRule('key', ' ', []); $res = $rule->validate(); - assertThat($res, identicalTo(RuleInterface::VALID)); + $this->assertSame(RuleInterface::VALID, $res); - assertThat('', equalTo($rule->getValue())); + $this->assertSame('', $rule->getValue()); } - public function getValueProvider(): \Generator + public function testSetValue(): void { - yield 'Trim is true but required is false by default' => [ - ' ', - [], - '', - RuleInterface::VALID, - ]; - - yield 'Value could be empty if not required' => [ - ' ', - [StringRule::TRIM => true, RuleInterface::REQUIRED => false], - '', - RuleInterface::VALID, - ]; - - yield 'Required value could be one character space' => [ - ' ', - [StringRule::TRIM => false, RuleInterface::REQUIRED => true], - '', - RuleInterface::CHECK, - ]; - - yield 'Required value could be false' => [ - false, - [RuleInterface::REQUIRED => true], - '', - RuleInterface::CHECK, - ]; - - yield 'Required value should not be an empty string' => [ - '', - [RuleInterface::REQUIRED => true], - 'key is required and should not be empty: ', - RuleInterface::ERROR, - ]; - - yield 'Required value should not be null' => [ - null, - [RuleInterface::REQUIRED => true], - 'key is required and should not be empty: ', - RuleInterface::ERROR, - ]; + /** @var RuleInterface $rule */ + $rule = new StubAbstractRule('key', 'initial', []); + + $this->assertSame('initial', $rule->getValue()); + + $rule->setValue('updated'); + + $this->assertSame('updated', $rule->getValue()); + } + + public function testGetKey(): void + { + /** @var RuleInterface $rule */ + $rule = new StubAbstractRule('myKey', 'value', []); + + $this->assertSame('myKey', $rule->getKey()); + } + + public function testGetKeyWithIntegerKey(): void + { + /** @var RuleInterface $rule */ + $rule = new StubAbstractRule(123, 'value', []); + + $this->assertSame(123, $rule->getKey()); + } + + public function testGetError(): void + { + /** @var RuleInterface $rule */ + $rule = new StubAbstractRule('key', 'value', [RuleInterface::REQUIRED => true]); + + // Before validation, error should be empty + $this->assertSame('', $rule->getError()); } } diff --git a/tests/Rule/ArrayRuleTest.php b/tests/Rule/ArrayRuleTest.php index bfa82dd..10cb0f4 100644 --- a/tests/Rule/ArrayRuleTest.php +++ b/tests/Rule/ArrayRuleTest.php @@ -1,9 +1,11 @@ validate(); - assertThat($res, identicalTo(ArrayRule::VALID)); - assertThat($rule->getValue(), emptyArray()); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame([], $rule->getValue()); $rule = new ArrayRule('name', ['foo' => 'bar']); $res = $rule->validate(); - assertThat($res, identicalTo(ArrayRule::VALID)); - assertThat($rule->getValue(), hasEntry('foo', 'bar')); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('bar', $rule->getValue()['foo']); - // with error : value remains unchanged + // with error: value remains unchanged $rule = new ArrayRule('name', 15); $res = $rule->validate(); - assertThat($res, identicalTo(ArrayRule::ERROR)); - assertThat($rule->getValue(), is(15)); + $this->assertSame(RuleInterface::ERROR, $res); + $this->assertSame(15, $rule->getValue()); } - /** - * @dataProvider getArrayValueProvider - */ + #[DataProviderExternal(StubDataProvider::class, 'getArrayValueProvider')] public function testValidate($value, $params, $expectedResult, $expectedError): void { $rule = new ArrayRule('name', $value, $params); $res = $rule->validate(); - assertThat($res, identicalTo($expectedResult)); + $this->assertSame($expectedResult, $res); - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getArrayValueProvider(): \Generator - { - yield 'Given value could be empty' => [ - [], - [], - ArrayRule::VALID, - '', - ]; - - yield 'Given value between 4 and 8' => [ - ['Peter', 'Ben', 'Harold'], - [ArrayRule::MIN => 3, ArrayRule::MAX => 8], - ArrayRule::VALID, - '', - ]; - - yield 'Given value should be more than 3' => [ - ['Peter', 'Ben', 'Harold'], - [ArrayRule::MIN => 3], - ArrayRule::VALID, - '', - ]; - - yield 'Given value should be an array' => [ - new \stdClass(), - [], - ArrayRule::ERROR, - 'name does not have an array value: stdClass object', - ]; - - yield 'Given value is not between 4 and 8' => [ - ['Peter', 'Ben', 'Harold'], - [ArrayRule::MIN => 4, ArrayRule::MAX => 8], - ArrayRule::ERROR, - "name: The length of array ( 0 => 'Peter', 1 => 'Ben', 2 => 'Harold',) is not between 4 and 8", - ]; - - yield 'Required value should not be an empty array' => [ - [], - [RuleInterface::REQUIRED => true], - RuleInterface::ERROR, - 'name is required and should not be empty: array ()', - ]; - - yield 'Required value should not be an empty array, with specific message' => [ - [], - [RuleInterface::REQUIRED => true, 'messages' => [ - RuleInterface::EMPTY_KEY => '%key% is required. `%value%` is empty!', - ]], - RuleInterface::ERROR, - 'name is required. `array ()` is empty!', - ]; + $this->assertSame($expectedError, $rule->getError()); } } + diff --git a/tests/Rule/BicRuleTest.php b/tests/Rule/BicRuleTest.php index de78d54..aba70ec 100644 --- a/tests/Rule/BicRuleTest.php +++ b/tests/Rule/BicRuleTest.php @@ -1,118 +1,118 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getBicValueProvider(): \Generator + public static function getBicValueProvider(): Generator { yield 'Given value could be empty' => [ '', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value ASPKAT2LXXX should be valid' => [ 'ASPKAT2LXXX', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value ASPKAT2L should be valid' => [ 'ASPKAT2L', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value DSBACNBXSHA should be valid' => [ 'DSBACNBXSHA', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value UNCRIT2B912 should be valid' => [ 'UNCRIT2B912', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value DABADKKK should be valid' => [ 'DABADKKK', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value RZOOAT2L303 should be valid' => [ 'RZOOAT2L303', - BicRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value ASPKAT2LXX should not be valid' => [ 'ASPKAT2LXX', - BicRule::ERROR, + RuleInterface::ERROR, 'name: ASPKAT2LXX has an invalid length', ]; yield 'Given value ASPKAT2LX should not be valid' => [ 'ASPKAT2LX', - BicRule::ERROR, + RuleInterface::ERROR, 'name: ASPKAT2LX has an invalid length', ]; yield 'Given value ASPKAT2LXXX1 should not be valid' => [ 'ASPKAT2LXXX1', - BicRule::ERROR, + RuleInterface::ERROR, 'name: ASPKAT2LXXX1 has an invalid length', ]; yield 'Given value DABADKK should not be valid' => [ 'DABADKK', - BicRule::ERROR, + RuleInterface::ERROR, 'name: DABADKK has an invalid length', ]; yield 'Given value RZ00AT2L303 should not be valid' => [ 'RZ00AT2L303', - BicRule::ERROR, + RuleInterface::ERROR, 'name: RZ00AT2L303 has an invalid bank code', ]; yield 'Given value 1SBACNBXSHA should not be valid' => [ '1SBACNBXSHA', - BicRule::ERROR, + RuleInterface::ERROR, 'name: 1SBACNBXSHA has an invalid bank code', ]; yield 'Given value DSBA5NBXSHA should not be valid' => [ 'DSBA5NBXSHA', - BicRule::ERROR, + RuleInterface::ERROR, 'name: DSBA5NBXSHA has an invalid country code', ]; yield 'Given value Dsba5nbxshA should not be valid' => [ 'Dsba5nbxshA', - BicRule::ERROR, + RuleInterface::ERROR, 'name: Dsba5nbxshA should be uppercase', ]; } + + #[DataProvider('getBicValueProvider')] + public function testValidate($value, $expectedResult, $expectedError): void + { + $rule = new BicRule('name', $value); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/BooleanRuleTest.php b/tests/Rule/BooleanRuleTest.php index 4d94f1e..799a100 100644 --- a/tests/Rule/BooleanRuleTest.php +++ b/tests/Rule/BooleanRuleTest.php @@ -1,14 +1,43 @@ [ + null, + RuleInterface::VALID, + '', + ]; + + yield 'Given value could be 1' => [ + '1 ', + RuleInterface::VALID, + '', + ]; + + yield 'Given value could be true' => [ + true, + RuleInterface::VALID, + '', + ]; + + yield 'Given value could not be a string' => [ + 'test', + RuleInterface::ERROR, + 'name: test is not a valid boolean', + ]; + } + public function testValidateEmptyValue(): void { // Value is not required by default! @@ -16,8 +45,8 @@ public function testValidateEmptyValue(): void $res = $rule->validate(); - assertThat($res, identicalTo(BooleanRule::VALID)); - assertThat($rule->getValue(), nullValue()); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertNull($rule->getValue()); $rule = new BooleanRule('name', '', [ BooleanRule::CAST => true, @@ -25,48 +54,19 @@ public function testValidateEmptyValue(): void $res = $rule->validate(); - assertThat($res, identicalTo(BooleanRule::VALID)); - assertThat($rule->getValue(), is(false)); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertFalse($rule->getValue()); } - /** - * @dataProvider getBooleanValueProvider - */ + #[DataProvider('getBooleanValueProvider')] public function testValidate($value, $expectedResult, $expectedError): void { $rule = new BooleanRule('name', $value); $res = $rule->validate(); - assertThat($res, identicalTo($expectedResult)); + $this->assertSame($expectedResult, $res); - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getBooleanValueProvider(): \Generator - { - yield 'Given value could be empty' => [ - null, - BooleanRule::VALID, - '', - ]; - - yield 'Given value could be 1' => [ - '1 ', - BooleanRule::VALID, - '', - ]; - - yield 'Given value could be true' => [ - true, - BooleanRule::VALID, - '', - ]; - - yield 'Given value could not be a string' => [ - 'test', - BooleanRule::ERROR, - 'name: test is not a valid boolean', - ]; + $this->assertSame($expectedError, $rule->getError()); } } diff --git a/tests/Rule/CallableRuleTest.php b/tests/Rule/CallableRuleTest.php index 11045cf..82abd9f 100644 --- a/tests/Rule/CallableRuleTest.php +++ b/tests/Rule/CallableRuleTest.php @@ -1,34 +1,22 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getCallableRuleValueProvider(): \Generator + public static function getCallableRuleValueProvider(): Generator { yield 'Given value could be empty when not required' => [ null, [CallableRule::CALLABLE => function ($key, $value): bool { return $value === '102'; - }], CallableRule::VALID, '' + }], RuleInterface::VALID, '' ]; yield 'Given value should be validated through a callable' => [ @@ -38,13 +26,25 @@ public function getCallableRuleValueProvider(): \Generator return true; } return false; - }], CallableRule::VALID, '' + }], RuleInterface::VALID, '' ]; yield 'Given value should not be validated through a callable' => [ false, [CallableRule::CALLABLE => function ($key, $value): bool { return $value === null; - }], CallableRule::ERROR, 'name: did not pass the callable check' + }], RuleInterface::ERROR, 'name: did not pass the callable check' ]; } + + #[DataProvider('getCallableRuleValueProvider')] + public function testValidate($value, array $params, int $expectedResult, string $expectedError): void + { + $rule = new CallableRule('name', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/ChoicesRuleTest.php b/tests/Rule/ChoicesRuleTest.php index 0d3406f..107bbc5 100644 --- a/tests/Rule/ChoicesRuleTest.php +++ b/tests/Rule/ChoicesRuleTest.php @@ -1,29 +1,17 @@ $list]); - - assertThat($rule->validate(), is(equalTo(ChoicesRule::VALID))); - assertThat($rule->getValue(), is(equalTo($expectedResult))); - } - - public function validDataProvider(): Generator + public static function validDataProvider(): Generator { yield 'Test null value' => [ null, @@ -43,6 +31,17 @@ public function validDataProvider(): Generator ]; } + #[DataProvider('validDataProvider')] + public function testValidateEmptyValues($selection, $expectedResult): void + { + $list = ['foo', 'bar']; + + $rule = new ChoicesRule('key1', $selection, [ChoicesRule::LIST => $list]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + $this->assertSame($expectedResult, $rule->getValue()); + } + public function testValidateError(): void { $list = ['foo', 'bar']; @@ -50,8 +49,8 @@ public function testValidateError(): void $rule = new ChoicesRule('key2', $selection, [ChoicesRule::LIST => $list]); - assertThat($rule->validate(), is(equalTo(ChoicesRule::ERROR))); - assertThat($rule->getError(), is(stringValue())); + $this->assertSame(RuleInterface::ERROR, $rule->validate()); + $this->assertIsString($rule->getError()); } public function testValidateRequiredError(): void @@ -61,10 +60,10 @@ public function testValidateRequiredError(): void $rule = new ChoicesRule('foo', $selection, [ ChoicesRule::LIST => $list, - ChoicesRule::REQUIRED => true, + RuleInterface::REQUIRED => true, ]); - assertThat($rule->validate(), is(equalTo(ChoicesRule::ERROR))); - assertThat($rule->getError(), is(stringValue())); + $this->assertSame(RuleInterface::ERROR, $rule->validate()); + $this->assertIsString($rule->getError()); } } diff --git a/tests/Rule/CollectionRuleTest.php b/tests/Rule/CollectionRuleTest.php index adcdd23..4530fd1 100644 --- a/tests/Rule/CollectionRuleTest.php +++ b/tests/Rule/CollectionRuleTest.php @@ -1,6 +1,6 @@ $rules]); - assertThat($rule->validate(), is(true)); + $this->assertEquals(RuleInterface::VALID, $rule->validate()); $tags = $rule->getValue(); - assertThat($tags, arrayWithSize(3)); + $this->assertSame(3, count($tags)); } public function testJsonValidate(): void @@ -46,11 +46,11 @@ public function testJsonValidate(): void $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules, CollectionRule::JSON => true]); - assertThat($rule->validate(), is(true)); + $this->assertEquals(RuleInterface::VALID, $rule->validate()); $tags = $rule->getValue(); - assertThat($tags, arrayWithSize(3)); + $this->assertSame(3, count($tags)); } public function testValidateEmptyData(): void @@ -63,8 +63,9 @@ public function testValidateEmptyData(): void $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); - assertThat($rule->validate(), is(true)); - assertThat($rule->getValue(), arrayValue()); // value cast to array + $this->assertEquals(RuleInterface::VALID, $rule->validate()); + + $this->assertTrue(is_array($rule->getValue())); // value cast to array } public function testValidateOnError(): void @@ -82,8 +83,9 @@ public function testValidateOnError(): void $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); - assertThat($rule->validate(), is(false)); - assertThat($rule->getError(), containsString('slug: three does not match /^[a-z]{1,3}$/i')); + $this->assertEquals(RuleInterface::ERROR, $rule->validate()); + + $this->assertStringContainsString('slug: three does not match /^[a-z]{1,3}$/i', $rule->getError()); } public function testValidateErrorFormat(): void @@ -95,7 +97,55 @@ public function testValidateErrorFormat(): void $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); - assertThat($rule->validate(), is(false)); - assertThat($rule->getError(), containsString('tags: {email: "elie29@gmail.com"} is not in a collection')); + $this->assertEquals(RuleInterface::ERROR, $rule->validate()); + $this->assertStringContainsString('tags: {email: "elie29@gmail.com"} is not in a collection', $rule->getError()); + } + + public function testValidateWithEmptyRules(): void + { + $data = [ + ['code' => 12, 'slug' => 'one'], + ['code' => 13, 'slug' => 'two'], + ]; + + // Empty rules should make the rule valid + $rule = new CollectionRule('tags', $data, []); + + $this->assertEquals(RuleInterface::VALID, $rule->validate()); + } + + public function testValidateMalformedJson(): void + { + $data = '{"invalid": json without closing brace'; + $rules = [ + ['code', NumericRule::class], + ]; + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules, CollectionRule::JSON => true]); + + $this->assertEquals(RuleInterface::ERROR, $rule->validate()); + $this->assertStringContainsString('is not in a collection', $rule->getError()); + } + + public function testValidateNonJsonStringWithJsonFlag(): void + { + $data = 'plain text not json'; + $rules = [ + ['code', NumericRule::class], + ]; + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules, CollectionRule::JSON => true]); + + $this->assertEquals(RuleInterface::ERROR, $rule->validate()); + $this->assertStringContainsString('is not in a collection', $rule->getError()); + } + + public function testGetValueReturnsEmptyArrayWhenValueIsNullAndNoError(): void + { + $rule = new CollectionRule('tags', null, []); + + $rule->validate(); + + $this->assertSame([], $rule->getValue()); } } diff --git a/tests/Rule/CompareRuleTest.php b/tests/Rule/CompareRuleTest.php index 4730f2a..ac6c454 100644 --- a/tests/Rule/CompareRuleTest.php +++ b/tests/Rule/CompareRuleTest.php @@ -1,112 +1,206 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getCompareValueProvider(): \Generator + public static function getCompareValueProvider(): Generator { yield 'Given value could be empty' => [ - '', - [], - CompareRule::VALID, - '', + '', // value + [], // params + RuleInterface::VALID, // expectedResult + '', // expectedError ]; yield 'Given value should be equal to 5' => [ '5', - [CompareRule::SIGN => CompareRule::EQ, CompareRule::EXPECTED => 5], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::EQ, CompareConstants::EXPECTED => 5], + RuleInterface::VALID, '', ]; yield 'Given value should be same as 5' => [ '5', - [CompareRule::SIGN => CompareRule::SEQ, CompareRule::EXPECTED => '5'], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::SEQ, CompareConstants::EXPECTED => '5'], + RuleInterface::VALID, '', ]; yield 'Given value should be same as [5, 4, false]' => [ [5, 4, false], - [CompareRule::SIGN => CompareRule::SEQ, - CompareRule::EXPECTED => [5, 4, false]], CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::SEQ, + CompareConstants::EXPECTED => [5, 4, false]], RuleInterface::VALID, '', ]; yield 'Given value should not be equal to 5' => [ '15', - [CompareRule::SIGN => CompareRule::NEQ, CompareRule::EXPECTED => 5], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::NEQ, CompareConstants::EXPECTED => 5], + RuleInterface::VALID, '', ]; yield 'Given value should not be same as [5, 4, 0]' => [ [5, 4, false], - [CompareRule::SIGN => CompareRule::NSEQ, - CompareRule::EXPECTED => [5, 4, 0]], CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::NSEQ, + CompareConstants::EXPECTED => [5, 4, 0]], RuleInterface::VALID, '', ]; yield 'Given value should not be same as 5' => [ '25', - [CompareRule::SIGN => CompareRule::NSEQ, CompareRule::EXPECTED => '5'], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::NSEQ, CompareConstants::EXPECTED => '5'], + RuleInterface::VALID, '', ]; yield 'Given value should be less than5' => [ '4', - [CompareRule::SIGN => CompareRule::LT, CompareRule::EXPECTED => 5], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::LT, CompareConstants::EXPECTED => 5], + RuleInterface::VALID, '', ]; yield 'Given value should be less or equal to 5' => [ '5', - [CompareRule::SIGN => CompareRule::LTE, CompareRule::EXPECTED => '5'], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::LTE, CompareConstants::EXPECTED => '5'], + RuleInterface::VALID, '', ]; yield 'Given value should be greater than 5' => [ '6', - [CompareRule::SIGN => CompareRule::GT, CompareRule::EXPECTED => 5], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::GT, CompareConstants::EXPECTED => 5], + RuleInterface::VALID, '', ]; - yield 'Given value should be geater or equal to 5' => [ + yield 'Given value should be greater or equal to 5' => [ '5', - [CompareRule::SIGN => CompareRule::GTE, CompareRule::EXPECTED => '5'], - CompareRule::VALID, + [CompareConstants::SIGN => CompareConstants::GTE, CompareConstants::EXPECTED => '5'], + RuleInterface::VALID, '', ]; yield 'Given value should not be less than 5' => [ '2', - [CompareRule::SIGN => CompareRule::GTE, CompareRule::EXPECTED => '5'], - CompareRule::ERROR, + [CompareConstants::SIGN => CompareConstants::GTE, CompareConstants::EXPECTED => '5'], + RuleInterface::ERROR, 'name: 2 is not greater or equal to 5', ]; } + + #[DataProvider('getCompareValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new CompareRule('name', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } + + public function testTypeComparisonStringVsInt(): void + { + // String '5' == int 5 (loose comparison) + $rule = new CompareRule('value', '5', [ + CompareConstants::SIGN => CompareConstants::EQ, + CompareConstants::EXPECTED => 5, + ]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + + // String '5' !== int 5 (strict comparison) + $rule = new CompareRule('value', '5', [ + CompareConstants::SIGN => CompareConstants::SEQ, + CompareConstants::EXPECTED => 5, + ]); + + $this->assertSame(RuleInterface::ERROR, $rule->validate()); + } + + public function testTypeComparisonArrays(): void + { + // Arrays with the same values and order should be strictly equal + $rule = new CompareRule('data', [1, 2, 3], [ + CompareConstants::SIGN => CompareConstants::SEQ, + CompareConstants::EXPECTED => [1, 2, 3], + ]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + + // Arrays with different order should not be equal + $rule = new CompareRule('data', [1, 2, 3], [ + CompareConstants::SIGN => CompareConstants::SEQ, + CompareConstants::EXPECTED => [3, 2, 1], + ]); + + $this->assertSame(RuleInterface::ERROR, $rule->validate()); + } + + public function testTypeComparisonBooleanVsInt(): void + { + // true == 1 (loose comparison) + $rule = new CompareRule('flag', true, [ + CompareConstants::SIGN => CompareConstants::EQ, + CompareConstants::EXPECTED => 1, + ]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + + // true !== 1 (strict comparison) + $rule = new CompareRule('flag', true, [ + CompareConstants::SIGN => CompareConstants::SEQ, + CompareConstants::EXPECTED => 1, + ]); + + $this->assertSame(RuleInterface::ERROR, $rule->validate()); + } + + public function testNotStrictlyEqualComparison(): void + { + // Test NSEQ - not strictly equal + $rule = new CompareRule('value', '5', [ + CompareConstants::SIGN => CompareConstants::NSEQ, + CompareConstants::EXPECTED => 5, + ]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + + $rule = new CompareRule('value', 5, [ + CompareConstants::SIGN => CompareConstants::NSEQ, + CompareConstants::EXPECTED => 5, + ]); + + $this->assertSame(RuleInterface::ERROR, $rule->validate()); + } + + public function testComparisonWithNull(): void + { + // null == false (loose comparison) + $rule = new CompareRule('value', null, [ + CompareConstants::SIGN => CompareConstants::EQ, + CompareConstants::EXPECTED => false, + ]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + + // null !== false, but null is considered empty + $rule = new CompareRule('value', null, [ + CompareConstants::SIGN => CompareConstants::SEQ, + CompareConstants::EXPECTED => false, + ]); + + $this->assertSame(RuleInterface::VALID, $rule->validate()); + } } diff --git a/tests/Rule/DateRuleTest.php b/tests/Rule/DateRuleTest.php index 3bc058c..39b8006 100644 --- a/tests/Rule/DateRuleTest.php +++ b/tests/Rule/DateRuleTest.php @@ -1,116 +1,155 @@ 'yyyy/dd/mm', - DateRule::SEPARATOR => '[,/]', - ]); - - $res = $rule->validate(); - - assertThat($res, identicalTo(1)); - - assertThat($rule->getError(), identicalTo('')); - - assertThat($rule->getSeparator(), equalTo('[,/]')); - } - - /** - * @dataProvider getDateValueProvider - */ - public function testValidate($value, $params, $expectedResult, $expectedError): void - { - $rule = new DateRule('date', $value, $params); - - $res = $rule->validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getDateValueProvider(): \Generator + public static function getDateValueProvider(): Generator { yield 'Given value could be empty' => [ '', [], - DateRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value 12/03/2017 should be valid' => [ '12/03/2017', [DateRule::FORMAT => 'dd/mm/yyyy'], - DateRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value 12/03/17 should be valid' => [ '12/03/17', [DateRule::FORMAT => 'dd/mm/yy'], - DateRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given value 29/02/2017 should not be valid' => [ '29/02/2017', [DateRule::FORMAT => 'dd/mm/yyyy'], - DateRule::ERROR, + RuleInterface::ERROR, 'date: 29/02/2017 is not a valid date', ]; yield 'Given value 0/02/2017 should not be valid' => [ '0/02/2017', [DateRule::FORMAT => 'dd/mm/yyyy'], - DateRule::ERROR, + RuleInterface::ERROR, 'date: 0/02/2017 is not a valid date', ]; yield 'Given value 0/02 should not be valid' => [ '0/02', [DateRule::FORMAT => 'dd/mm/yyyy'], - DateRule::ERROR, + RuleInterface::ERROR, 'date: 0/02 is not a valid date', ]; yield 'Given value separator . should not be valid' => [ '12/02/2017', ['separator' => '.'], - DateRule::ERROR, + RuleInterface::ERROR, "date: 12/02/2017 does not have a valid format: array ( 0 => 'dd/mm/yyyy',) or separator: .", ]; yield 'Given value format dd/dd/mm should not be valid' => [ '12/02/2017', [DateRule::FORMAT => 'dd/dd/mm'], - DateRule::ERROR, + RuleInterface::ERROR, "date: 12/02/2017 does not have a valid format: array ( 0 => 'dd/dd/mm',) or separator: [,-./]", ]; yield 'Given value format dd/ss/mm should not be valid' => [ '12/02/2017', [DateRule::FORMAT => 'dd/ss/mm'], - DateRule::ERROR, + RuleInterface::ERROR, "date: 12/02/2017 does not have a valid format: array ( 0 => 'dd/ss/mm',) or separator: [,-./]", ]; yield 'Given value format dd/ss/mm should not be valid, with specific message' => [ '12/02/2017', - [DateRule::FORMAT => 'dd/ss/mm', DateRule::MESSAGES => [ + [DateRule::FORMAT => 'dd/ss/mm', RuleInterface::MESSAGES => [ DateRule::INVALID_DATE_FORMAT => '%key% has invalid format: %format%', ]], - DateRule::ERROR, + RuleInterface::ERROR, "date has invalid format: array ( 0 => 'dd/ss/mm',)", ]; } + + public function testValidateWithSeparator(): void + { + $rule = new DateRule('date', '2017,22,03', [ + DateRule::FORMAT => 'yyyy/dd/mm', + DateRule::SEPARATOR => '[,/]', + ]); + + $res = $rule->validate(); + + $this->assertSame(1, $res); + + $this->assertSame('', $rule->getError()); + + $this->assertSame('[,/]', $rule->getSeparator()); + } + + #[DataProvider('getDateValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new DateRule('date', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } + + public function testGetFormat(): void + { + $rule = new DateRule('date', '12/03/2017', []); + + // Default format + $this->assertSame(['dd/mm/yyyy'], $rule->getFormat()); + } + + public function testSetFormatWithString(): void + { + $rule = new DateRule('date', '12/03/2017', []); + $rule->setFormat('dd-mm-yyyy'); + + $this->assertSame(['dd-mm-yyyy'], $rule->getFormat()); + } + + public function testSetFormatWithArray(): void + { + $rule = new DateRule('date', '12/03/2017', []); + $rule->setFormat(['dd/mm/yyyy', 'yyyy-mm-dd']); + + $this->assertSame(['dd/mm/yyyy', 'yyyy-mm-dd'], $rule->getFormat()); + } + + public function testSetSeparator(): void + { + $rule = new DateRule('date', '12.03.2017', []); + $rule->setSeparator('[.]'); + + $this->assertSame('[.]', $rule->getSeparator()); + + // Validate with a new separator + $rule->setFormat('dd.mm.yyyy'); + $res = $rule->validate(); + + $this->assertSame(RuleInterface::VALID, $res); + } } + diff --git a/tests/Rule/EmailRuleTest.php b/tests/Rule/EmailRuleTest.php index d340769..ba63ab0 100644 --- a/tests/Rule/EmailRuleTest.php +++ b/tests/Rule/EmailRuleTest.php @@ -1,29 +1,17 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getEmailValueProvider(): \Generator + public static function getEmailValueProvider(): Generator { yield 'Given value could be empty' => [ '', @@ -43,4 +31,16 @@ public function getEmailValueProvider(): \Generator 'email: elie.com is not a valid email', ]; } + + #[DataProvider('getEmailValueProvider')] + public function testValidate($value, $expectedResult, $expectedError): void + { + $rule = new EmailRule('email', $value); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/IpRuleTest.php b/tests/Rule/IpRuleTest.php index 6fcdd24..eff4f13 100644 --- a/tests/Rule/IpRuleTest.php +++ b/tests/Rule/IpRuleTest.php @@ -1,126 +1,126 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getIpValueProvider(): \Generator + public static function getIpValueProvider(): Generator { yield 'Given value could be empty' => [ '', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv4 0.0.0.0 should be valid' => [ '0.0.0.0', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv4 10.0.0.0 should be valid' => [ '10.0.0.0', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv4 172.16.10.0 should be valid' => [ '172.16.10.0', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv4 192.168.2.10 should be valid' => [ '192.168.2.10', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv4 255.255.255.255 should be valid' => [ '255.255.255.255', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv4 255.255.253.0 should be valid' => [ '255.255.253.0', [], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv6 0:0:0:0:0:0:0:0 should be valid' => [ '0:0:0:0:0:0:0:0', [IpRule::FLAG => FILTER_FLAG_IPV6], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv6 ::255.255.255.255 should be valid' => [ '::255.255.255.255', [IpRule::FLAG => FILTER_FLAG_IPV6], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv6 fe80:0000:0000:0000:0202:b3ff:fe1e:8329 should be valid' => [ 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', [IpRule::FLAG => FILTER_FLAG_IPV6], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given ipv6 2001:0db8:85a3:0000:0000:8a2e:0.0.0.0 should be valid' => [ '2001:0db8:85a3:0000:0000:8a2e:0.0.0.0', [IpRule::FLAG => FILTER_FLAG_IPV6], - IpRule::VALID, + RuleInterface::VALID, '', ]; yield 'Given 0 should not be valid' => [ '0', [], - IpRule::ERROR, + RuleInterface::ERROR, 'IP: 0 is not a valid IP', ]; yield 'Given 0.0.foo should not be valid' => [ '0.0.foo', [], - IpRule::ERROR, + RuleInterface::ERROR, 'IP: 0.0.foo is not a valid IP', ]; yield 'Given 255.255.255.255 should not be valid' => [ '255.255.255.255', [IpRule::FLAG => 5], - IpRule::ERROR, + RuleInterface::ERROR, 'Filter IP flag: 5 is not valid', ]; } + + #[DataProvider('getIpValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new IpRule('IP', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/JsonRuleTest.php b/tests/Rule/JsonRuleTest.php index f49753f..b4e2022 100644 --- a/tests/Rule/JsonRuleTest.php +++ b/tests/Rule/JsonRuleTest.php @@ -1,71 +1,71 @@ [ + '', + RuleInterface::VALID, + '', + ]; + + yield 'Given value {"a":1,"b":2,"c":3,"d":4,"e":5} should be valid' => [ + '{"a":1,"b":2,"c":3,"d":4,"e":5}', + RuleInterface::VALID, + '', + ]; + + yield 'Given value elie.com should not be valid' => [ + 'elie.com', + RuleInterface::ERROR, + 'json: elie.com is not a valid json format', + ]; + } + public function testValidateJsonDecode(): void { // Empty string value. Value is not required by default! $rule = new JsonRule('name', ''); $res = $rule->validate(); - assertThat($res, identicalTo(JsonRule::VALID)); - assertThat($rule->getValue(), emptyString()); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getValue()); $rule = new JsonRule('name', '', [JsonRule::DECODE => true]); $res = $rule->validate(); - assertThat($res, identicalTo(JsonRule::VALID)); - assertThat($rule->getValue(), emptyArray()); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame([], $rule->getValue()); $rule = new JsonRule('name', '[{"name":"John Doe","age":25}]', [JsonRule::DECODE => true]); $res = $rule->validate(); - assertThat($res, identicalTo(JsonRule::VALID)); - assertThat($rule->getValue(), arrayWithSize(1)); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame(1, count($rule->getValue())); // With error $rule = new JsonRule('name', 'aaa', [JsonRule::DECODE => true]); $res = $rule->validate(); - assertThat($res, identicalTo(JsonRule::ERROR)); - assertThat($rule->getValue(), is('aaa')); + $this->assertSame(RuleInterface::ERROR, $res); + $this->assertSame('aaa', $rule->getValue()); } - /** - * @dataProvider getJsonValueProvider - */ + #[DataProvider('getJsonValueProvider')] public function testValidate($value, $expectedResult, $expectedError): void { $rule = new JsonRule('json', $value); $res = $rule->validate(); - assertThat($res, identicalTo($expectedResult)); + $this->assertSame($expectedResult, $res); - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getJsonValueProvider(): \Generator - { - yield 'Given value could be empty' => [ - '', - RuleInterface::VALID, - '', - ]; - - yield 'Given value {"a":1,"b":2,"c":3,"d":4,"e":5} should be valid' => [ - '{"a":1,"b":2,"c":3,"d":4,"e":5}', - RuleInterface::VALID, - '', - ]; - - yield 'Given value elie.com should not be valid' => [ - 'elie.com', - RuleInterface::ERROR, - 'json: elie.com is not a valid json format', - ]; + $this->assertSame($expectedError, $rule->getError()); } } diff --git a/tests/Rule/MatchRuleTest.php b/tests/Rule/MatchRuleTest.php index 9659b3d..399d24b 100644 --- a/tests/Rule/MatchRuleTest.php +++ b/tests/Rule/MatchRuleTest.php @@ -1,29 +1,17 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getMatchValueProvider(): \Generator + public static function getMatchValueProvider(): Generator { yield 'Given value could be empty' => [ '', @@ -46,4 +34,16 @@ public function getMatchValueProvider(): \Generator 'value: test does not match /^[0-9]+$/', ]; } + + #[DataProvider('getMatchValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new MatchRule('value', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/MultipleAndRuleTest.php b/tests/Rule/MultipleAndRuleTest.php index 2449ce4..7957063 100644 --- a/tests/Rule/MultipleAndRuleTest.php +++ b/tests/Rule/MultipleAndRuleTest.php @@ -1,6 +1,6 @@ [ - [StringRule::class, StringRule::REQUIRED => true, StringRule::MIN => 1], + [StringRule::class, RuleInterface::REQUIRED => true, StringRule::MIN => 1], [EmailRule::class], ], ]); $res = $rule->validate(); - assertThat($res, identicalTo(RuleInterface::VALID)); + $this->assertSame(RuleInterface::VALID, $res); - assertThat($rule->getError(), is(emptyString())); + $this->assertSame('', $rule->getError()); } - public function testValidateCouldBeEmpry(): void + public function testValidateCouldBeEmpty(): void { // Could be empty or a valid email string $rule = new MultipleAndRule('name', '', [ MultipleAndRule::RULES => [ - [StringRule::class, StringRule::REQUIRED => true, StringRule::MIN => 1], + [StringRule::class, RuleInterface::REQUIRED => true, StringRule::MIN => 1], [EmailRule::class], ], ]); $res = $rule->validate(); - assertThat($res, identicalTo(RuleInterface::VALID)); + $this->assertSame(RuleInterface::VALID, $res); - assertThat($rule->getError(), is(emptyString())); + $this->assertSame('', $rule->getError()); } public function testValidateError(): void @@ -48,15 +48,64 @@ public function testValidateError(): void // Should be string and email $rule = new MultipleAndRule('name', 'foo', [ MultipleAndRule::RULES => [ - [StringRule::class, StringRule::REQUIRED => true, StringRule::MIN => 1], + [StringRule::class, RuleInterface::REQUIRED => true, StringRule::MIN => 1], [EmailRule::class], ], ]); $res = $rule->validate(); - assertThat($res, identicalTo(RuleInterface::ERROR)); + $this->assertSame(RuleInterface::ERROR, $res); - assertThat($rule->getError(), is(identicalTo('name: foo is not a valid email'))); + $this->assertSame('name: foo is not a valid email', $rule->getError()); + } + + public function testValidateWithNumericAndRangeRules(): void + { + // Value must be numeric and within range + $rule = new MultipleAndRule('score', '75', [ + MultipleAndRule::RULES => [ + [NumericRule::class, RuleInterface::REQUIRED => true], + [RangeRule::class, RangeRule::RANGE => ['50', '75']], + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getError()); + } + + public function testValidateMultipleRulesWithFirstFailure(): void + { + // When the first rule fails, validation should stop at the first error + $rule = new MultipleAndRule('value', 'abc', [ + MultipleAndRule::RULES => [ + [NumericRule::class, RuleInterface::REQUIRED => true], + [RangeRule::class, RangeRule::RANGE => [50, 75]], + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::ERROR, $res); + $this->assertStringContainsString('is not numeric', $rule->getError()); + } + + public function testValidateWithThreeRules(): void + { + // Test with three rules all passing + $rule = new MultipleAndRule('email', 'test@example.com', [ + MultipleAndRule::RULES => [ + [StringRule::class, RuleInterface::REQUIRED => true, StringRule::MIN => 5], + [StringRule::class, StringRule::MAX => 50], + [EmailRule::class], + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getError()); } } diff --git a/tests/Rule/MultipleOrRuleTest.php b/tests/Rule/MultipleOrRuleTest.php index 333af0b..3626c01 100644 --- a/tests/Rule/MultipleOrRuleTest.php +++ b/tests/Rule/MultipleOrRuleTest.php @@ -1,6 +1,6 @@ [ - [StringRule::class, StringRule::REQUIRED => true, StringRule::MAX => 2], + [StringRule::class, RuleInterface::REQUIRED => true, StringRule::MAX => 2], [NumericRule::class], ], ]); $res = $rule->validate(); - assertThat($res, identicalTo(RuleInterface::VALID)); + $this->assertSame(RuleInterface::VALID, $res); - assertThat($rule->getError(), is(emptyString())); + $this->assertSame('', $rule->getError()); } public function testValidateErrors(): void { $rule = new MultipleOrRule('name', 'abc', [ MultipleAndRule::RULES => [ - [StringRule::class, StringRule::REQUIRED => true, StringRule::MAX => 2], + [StringRule::class, RuleInterface::REQUIRED => true, StringRule::MAX => 2], [NumericRule::class], ], ]); $res = $rule->validate(); - assertThat($res, identicalTo(RuleInterface::ERROR)); + $this->assertSame(RuleInterface::ERROR, $res); - assertThat($rule->getError(), equalTo( - "name: The length of abc is not between 0 and 2\nname: abc is not numeric" - )); + $this->assertSame( + "name: The length of abc is not between 0 and 2\nname: abc is not numeric", + $rule->getError() + ); + } + + public function testValidateWithFirstRulePassing(): void + { + // First rule passes, should be valid immediately + $rule = new MultipleOrRule('value', '15', [ + MultipleAndRule::RULES => [ + [NumericRule::class], + [EmailRule::class], // Won't be checked since first rule passes + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getError()); + } + + public function testValidateWithSecondRulePassing(): void + { + // First rule fails, second rule passes + $rule = new MultipleOrRule('contact', 'user@example.com', [ + MultipleAndRule::RULES => [ + [NumericRule::class], // Fails + [EmailRule::class], // Passes + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getError()); + } + + public function testValidateWithEmptyValue(): void + { + // Empty value should be valid (not required) + $rule = new MultipleOrRule('value', '', [ + MultipleAndRule::RULES => [ + [NumericRule::class], + [EmailRule::class], + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getError()); + } + + public function testValidateMultipleErrorsAccumulated(): void + { + // All rules fail, should accumulate all errors + $rule = new MultipleOrRule('data', 'invalid', [ + MultipleAndRule::RULES => [ + [NumericRule::class], + [EmailRule::class], + [BooleanRule::class], + ], + ]); + + $res = $rule->validate(); + + $this->assertSame(RuleInterface::ERROR, $res); + + $error = $rule->getError(); + $this->assertStringContainsString('is not numeric', $error); + $this->assertStringContainsString('is not a valid email', $error); + $this->assertStringContainsString('is not a valid boolean', $error); } } diff --git a/tests/Rule/NumericRuleTest.php b/tests/Rule/NumericRuleTest.php index 16c8629..756762d 100644 --- a/tests/Rule/NumericRuleTest.php +++ b/tests/Rule/NumericRuleTest.php @@ -1,62 +1,17 @@ validate(); - assertThat($res, identicalTo(NumericRule::VALID)); - assertThat($rule->getValue(), emptyString()); - - // Empty string value with cast - $rule = new NumericRule('name', '', [ - NumericRule::CAST => true, - ]); - $res = $rule->validate(); - assertThat($res, identicalTo(NumericRule::VALID)); - assertThat($rule->getValue(), identicalTo(0)); - - // int - $rule = new NumericRule('name', '12', [ - NumericRule::CAST => true, - ]); - $res = $rule->validate(); - assertThat($res, identicalTo(NumericRule::VALID)); - assertThat($rule->getValue(), identicalTo(12)); - - // float - $rule = new NumericRule('name', '12.2', [ - NumericRule::CAST => true, - ]); - $res = $rule->validate(); - assertThat($res, identicalTo(NumericRule::VALID)); - assertThat($rule->getValue(), identicalTo(12.2)); - } - - /** - * @dataProvider getNumericValueProvider - */ - public function testValidate($value, $params, $expectedResult, $expectedError): void - { - $rule = new NumericRule('age', $value, $params); - - $res = $rule->validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getNumericValueProvider(): \Generator + public static function getNumericValueProvider(): Generator { yield 'Given value could be empty' => [ '', @@ -100,4 +55,49 @@ public function getNumericValueProvider(): \Generator 'age is required and should not be empty: ', ]; } + + public function testValidateEmptyValue(): void + { + // Empty string value. Value is not required by default! + $rule = new NumericRule('name', ''); + $res = $rule->validate(); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame('', $rule->getValue()); + + // Empty string value with cast + $rule = new NumericRule('name', '', [ + NumericRule::CAST => true, + ]); + $res = $rule->validate(); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame(0, $rule->getValue()); + + // int + $rule = new NumericRule('name', '12', [ + NumericRule::CAST => true, + ]); + $res = $rule->validate(); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame(12, $rule->getValue()); + + // float + $rule = new NumericRule('name', '12.2', [ + NumericRule::CAST => true, + ]); + $res = $rule->validate(); + $this->assertSame(RuleInterface::VALID, $res); + $this->assertSame(12.2, $rule->getValue()); + } + + #[DataProvider('getNumericValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new NumericRule('age', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/RangeRuleTest.php b/tests/Rule/RangeRuleTest.php index 1e56ab5..b95cc3e 100644 --- a/tests/Rule/RangeRuleTest.php +++ b/tests/Rule/RangeRuleTest.php @@ -1,29 +1,17 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getRangeValueProvider(): \Generator + public static function getRangeValueProvider(): Generator { yield 'Given value could be empty' => [ '', @@ -53,4 +41,16 @@ public function getRangeValueProvider(): \Generator 'value: 0 is out of range array ( 0 => 0, 1 => \'false\',)', ]; } + + #[DataProvider('getRangeValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new RangeRule('value', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/StringCleanerRuleTest.php b/tests/Rule/StringCleanerRuleTest.php index beada54..ba91429 100644 --- a/tests/Rule/StringCleanerRuleTest.php +++ b/tests/Rule/StringCleanerRuleTest.php @@ -1,29 +1,17 @@ validate(); - - assertThat($rule->getValue(), identicalTo($expectedValue)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getStringValueProvider(): \Generator + public static function getStringValueProvider(): Generator { yield 'Given value is cleaned by trim and would be valid' => [ "\x00", @@ -34,30 +22,42 @@ public function getStringValueProvider(): \Generator yield 'Given value is not cleaned by trim and would be valid even when cleaned is empty' => [ "\x00", - [StringCleanerRule::TRIM => false, StringCleanerRule::REQUIRED => true], + [StringRule::TRIM => false, RuleInterface::REQUIRED => true], '', '', ]; yield 'Given value should be cleaned' => [ "f\x00f", - [StringCleanerRule::REQUIRED => true], + [RuleInterface::REQUIRED => true], 'ff', '', ]; yield 'Given value between 4 and 8 characters' => [ '%7FPeter ', - [StringCleanerRule::MIN => 4, StringCleanerRule::MAX => 8], + [StringRule::MIN => 4, StringRule::MAX => 8], 'Peter', '', ]; yield 'Given value should not be cleaned before validation' => [ "f\x00f", - [StringCleanerRule::REQUIRED => true, StringCleanerRule::MAX => 2], + [RuleInterface::REQUIRED => true, StringRule::MAX => 2], "f\x00f", "name: The length of f\x00f is not between 0 and 2", ]; } + + #[DataProvider('getStringValueProvider')] + public function testValidate($value, $params, $expectedValue, $expectedError): void + { + $rule = new StringCleanerRule('name', $value, $params); + + $rule->validate(); + + $this->assertSame($expectedValue, $rule->getValue()); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/StringRuleTest.php b/tests/Rule/StringRuleTest.php index e4e507a..fec2d81 100644 --- a/tests/Rule/StringRuleTest.php +++ b/tests/Rule/StringRuleTest.php @@ -1,29 +1,17 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getStringValueProvider(): \Generator + public static function getStringValueProvider(): Generator { yield 'Given value could be empty' => [ '', @@ -39,6 +27,13 @@ public function getStringValueProvider(): \Generator '', ]; + yield 'Given value should not be cleaned' => [ + "f\x00f", + [RuleInterface::REQUIRED => true], + RuleInterface::VALID, + '', + ]; + yield 'Given value should be more than 3 characters' => [ 'Simon ', [StringRule::MIN => 3], @@ -67,4 +62,16 @@ public function getStringValueProvider(): \Generator 'name: The length of Ben is not between 4 and 8', ]; } + + #[DataProvider('getStringValueProvider')] + public function testValidate($value, $params, $expectedResult, $expectedError): void + { + $rule = new StringRule('name', $value, $params); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Rule/TimeRuleTest.php b/tests/Rule/TimeRuleTest.php index 34da122..2bfe1e1 100644 --- a/tests/Rule/TimeRuleTest.php +++ b/tests/Rule/TimeRuleTest.php @@ -1,29 +1,17 @@ validate(); - - assertThat($res, identicalTo($expectedResult)); - - assertThat($rule->getError(), identicalTo($expectedError)); - } - - public function getTimeValueProvider(): \Generator + public static function getTimeValueProvider(): Generator { yield 'Given value could be empty' => [ '', @@ -67,4 +55,16 @@ public function getTimeValueProvider(): \Generator 'time: 20:2:3:6 is not a valid time', ]; } + + #[DataProvider('getTimeValueProvider')] + public function testValidate($value, $expectedResult, $expectedError): void + { + $rule = new TimeRule('time', $value); + + $res = $rule->validate(); + + $this->assertSame($expectedResult, $res); + + $this->assertSame($expectedError, $rule->getError()); + } } diff --git a/tests/Stub/DataProvider.php b/tests/Stub/DataProvider.php new file mode 100644 index 0000000..94f5da3 --- /dev/null +++ b/tests/Stub/DataProvider.php @@ -0,0 +1,198 @@ + [ + // data + [ + 'age' => 25, + 'name' => 'Ben', + ], + // rules + [ + ['age', NumericRule::class, 'min' => 5, 'max' => 65], + ['name', StringRule::class, 'min' => 3, 'max' => 30], + ], + // expectedResult + true, + // errorsSize + 0, + ]; + + yield 'Validate with multiple and rule' => [ + [ + 'age' => 25, + ], + [ + ['age', MultipleAndRule::class, MultipleAndRule::REQUIRED => true, MultipleAndRule::RULES => [ + [NumericRule::class, NumericRule::MIN => 14], + [RangeRule::class, RangeRule::RANGE => [25, 26]], + ]], + ], + true, + 0, + ]; + + yield 'Validate with multiple or rule' => [ + [ + 'foo' => 'bar', + ], + [ + ['foo', MultipleOrRule::class, MultipleOrRule::REQUIRED => true, MultipleOrRule::RULES => [ + [NumericRule::class, NumericRule::MIN => 14], + [StringRule::class, StringRule::MIN => 1], + ]], + ], + true, + 0, + ]; + + yield 'Age is not valid' => [ + [ + 'age' => 25, + ], + [ + ['age', NumericRule::class, 'min' => 26, 'max' => 65], + ], + false, + 1, + ]; + yield 'Key with numeric value' => [ + [ + 0 => 25, + 1 => 'Test', + ], + [ + [0, NumericRule::class, 'min' => 22, 'max' => 65], + [1, StringRule::class, 'min' => 0, 'max' => 10], + ], + true, + 0, + ]; + yield 'Key with index context' => [ + [ + 28, + 'Test2', + ], + [ + [0, NumericRule::class, 'min' => 22, 'max' => 65], + [1, StringRule::class, 'min' => 0, 'max' => 10], + ], + true, + 0, + ]; + } + + public static function getValueProvider(): Generator + { + yield 'Trim is true but required is false by default' => [ + ' ', // data + [], // rules + RuleInterface::VALID, // expectedResult + '', // expectedError + ]; + + yield 'Value could be empty if not required' => [ + ' ', + [StringRule::TRIM => true, RuleInterface::REQUIRED => false], + RuleInterface::VALID, + '', + ]; + + yield 'Required value could be one character space' => [ + ' ', + [StringRule::TRIM => false, RuleInterface::REQUIRED => true], + RuleInterface::CHECK, + '', + ]; + + yield 'Required value could be false' => [ + false, + [RuleInterface::REQUIRED => true], + RuleInterface::CHECK, + '', + ]; + + yield 'Required value should not be an empty string' => [ + '', + [RuleInterface::REQUIRED => true], + RuleInterface::ERROR, + 'key is required and should not be empty: ', + ]; + + yield 'Required value should not be null' => [ + null, + [RuleInterface::REQUIRED => true], + RuleInterface::ERROR, + 'key is required and should not be empty: ', + ]; + } + + public static function getArrayValueProvider(): Generator + { + yield 'Given value could be empty' => [ + [], // data + [], // rules + RuleInterface::VALID, // expectedResult + '', // error + ]; + + yield 'Given value between 4 and 8' => [ + ['Peter', 'Ben', 'Harold'], + [ArrayRule::MIN => 3, ArrayRule::MAX => 8], + RuleInterface::VALID, + '', + ]; + + yield 'Given value should be more than 3' => [ + ['Peter', 'Ben', 'Harold'], + [ArrayRule::MIN => 3], + RuleInterface::VALID, + '', + ]; + + yield 'Given value should be an array' => [ + new stdClass(), + [], + RuleInterface::ERROR, + 'name does not have an array value: stdClass object', + ]; + + yield 'Given value is not between 4 and 8' => [ + ['Peter', 'Ben', 'Harold'], + [ArrayRule::MIN => 4, ArrayRule::MAX => 8], + RuleInterface::ERROR, + "name: The length of array ( 0 => 'Peter', 1 => 'Ben', 2 => 'Harold',) is not between 4 and 8", + ]; + + yield 'Required value should not be an empty array' => [ + [], + [RuleInterface::REQUIRED => true], + RuleInterface::ERROR, + 'name is required and should not be empty: array ()', + ]; + + yield 'Required value should not be an empty array, with specific message' => [ + [], + [RuleInterface::REQUIRED => true, 'messages' => [ + RuleInterface::EMPTY_KEY => '%key% is required. `%value%` is empty!', + ]], + RuleInterface::ERROR, + 'name is required. `array ()` is empty!', + ]; + } +} \ No newline at end of file diff --git a/tests/Rule/Stub/StubAbstractRule.php b/tests/Stub/StubAbstractRule.php similarity index 60% rename from tests/Rule/Stub/StubAbstractRule.php rename to tests/Stub/StubAbstractRule.php index 58eda2d..38a35bb 100644 --- a/tests/Rule/Stub/StubAbstractRule.php +++ b/tests/Stub/StubAbstractRule.php @@ -1,8 +1,8 @@ setRules([$rule]); $res = $validator->validate(); - assertThat($res, is(true)); + $this->assertTrue($res); $validatedContext = $validator->getValidatedContext(); - assertThat($validatedContext, anArray(['name' => 'Ben'])); + $this->assertArrayHasKey('name', $validatedContext); + $this->assertContains('Ben', $validatedContext); $value = $validator->get('name'); - assertThat($value, identicalTo('Ben ')); + $this->assertSame('Ben ', $value); $value = $validator->get('age'); - assertThat($value, nullValue()); + $this->assertNull($value); $rules = $validator->getRules(); - assertThat($rules[0], anArray($rule)); + $this->assertSame($rule, $rules[0]); - assertThat($validator->shouldStopOnError(), is(false)); + $this->assertFalse($validator->shouldStopOnError()); } public function testValidatorStopOnError(): void @@ -55,20 +56,20 @@ public function testValidatorStopOnError(): void ]); $res = $validator->validate(); - assertThat($res, is(false)); + $this->assertFalse($res); // value should not exist on error $validatedContext = $validator->getValidatedContext(); - assertThat($validatedContext, emptyArray()); + $this->assertSame([], $validatedContext); - assertThat($validator->shouldStopOnError(), is(true)); + $this->assertTrue($validator->shouldStopOnError()); } public function testValidatorWithJsonPartialValidation(): void { $rules = [ - ['email', EmailRule::class, EmailRule::REQUIRED => true], - ['user', JsonRule::class, JsonRule::REQUIRED => true, JsonRule::DECODE => true], + ['email', EmailRule::class, RuleInterface::REQUIRED => true], + ['user', JsonRule::class, RuleInterface::REQUIRED => true, JsonRule::DECODE => true], ]; $data = [ @@ -78,7 +79,7 @@ public function testValidatorWithJsonPartialValidation(): void $validator = new Validator($data, $rules); - assertThat($validator->validate(), is(true)); + $this->assertTrue($validator->validate()); // In order to validate users json context $validator->setRules([ @@ -90,18 +91,18 @@ public function testValidatorWithJsonPartialValidation(): void $validator->setContext($user); // Validate and merge context - assertThat($validator->validate(true), is(true)); + $this->assertTrue($validator->validate(true)); $data = $validator->getValidatedContext(); - assertThat($data, hasEntry('age', 25)); - assertThat($data, hasKey('user')); + $this->assertSame(25, $data['age']); + $this->assertArrayHasKey('user', $data); } public function testValidatorWithRawPartialValidation(): void { $rules = [ - ['email', EmailRule::class, EmailRule::REQUIRED => true], - ['tags', ArrayRule::class, ArrayRule::REQUIRED => true], + ['email', EmailRule::class, RuleInterface::REQUIRED => true], + ['tags', ArrayRule::class, RuleInterface::REQUIRED => true], ]; $data = [ @@ -115,7 +116,7 @@ public function testValidatorWithRawPartialValidation(): void $validator = new Validator($data, $rules); - assertThat($validator->validate(), is(true)); + $this->assertTrue($validator->validate()); // In order to validate tags array context $validator->setRules([ @@ -127,17 +128,17 @@ public function testValidatorWithRawPartialValidation(): void $data = []; foreach ($tags as $tag) { $validator->setContext($tag); - assertThat($validator->validate(), is(true)); + $this->assertTrue($validator->validate()); $data[] = $validator->getValidatedContext(); } - assertThat($data, arrayWithSize(3)); + $this->assertSame(3, count($data)); } public function testValidatorWithCollection(): void { $rules = [ - ['email', EmailRule::class, EmailRule::REQUIRED => true], + ['email', EmailRule::class, RuleInterface::REQUIRED => true], ['tags', CollectionRule::class, CollectionRule::RULES => [ ['code', NumericRule::class, NumericRule::MAX => 80], ['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'], @@ -155,11 +156,11 @@ public function testValidatorWithCollection(): void $validator = new Validator($data, $rules); - assertThat($validator->validate(), is(true)); + $this->assertTrue($validator->validate()); $tags = $validator->getValidatedContext()['tags']; - assertThat($tags, arrayWithSize(3)); + $this->assertSame(3, count($tags)); } public function testValidatorGetImplodedErrors(): void @@ -171,20 +172,20 @@ public function testValidatorGetImplodedErrors(): void ]); $res = $validator->validate(); - assertThat($res, is(false)); + $this->assertFalse($res); // value should not exist on error $validatedContext = $validator->getValidatedContext(); - assertThat($validatedContext, emptyArray()); + $this->assertSame([], $validatedContext); $expected = 'name: Ben is not numeric,name does not have an array value: Ben,name: Ben is not a valid boolean'; - assertThat($validator->getImplodedErrors(','), is($expected)); + $this->assertSame($expected, $validator->getImplodedErrors(',')); } public function testExistingKeysOnlyShouldBeAppendToTheValidatedContext(): void { $validator = new Validator( - // Context + // Context ['name' => 'John', 'address' => null], // Rules [ @@ -197,18 +198,16 @@ public function testExistingKeysOnlyShouldBeAppendToTheValidatedContext(): void $validator->appendExistingItemsOnly(true); $res = $validator->validate(); - assertThat($res, is(true)); + $this->assertTrue($res); $validatedContext = $validator->getValidatedContext(); - assertThat($validatedContext, hasKey('name')); - assertThat($validatedContext, hasKey('address')); - assertThat($validatedContext, not(hasKey('age'))); + $this->assertArrayHasKey('name', $validatedContext); + $this->assertArrayHasKey('address', $validatedContext); + $this->assertArrayNotHasKey('age', $validatedContext); } - /** - * @dataProvider getValidatorProvider - */ + #[DataProviderExternal(StubDataProvider::class, 'getValidatorProvider')] public function testValidate($context, $rules, $expectedResult, $errorsSize): void { $validator = new Validator($context); @@ -216,90 +215,63 @@ public function testValidate($context, $rules, $expectedResult, $errorsSize): vo $res = $validator->validate(); - assertThat($res, identicalTo($expectedResult)); - assertThat($validator->getErrors(), arrayWithSize($errorsSize)); + $this->assertSame($expectedResult, $res); + $this->assertSame($errorsSize, count($validator->getErrors())); } - public function getValidatorProvider(): \Generator + public function testSetContext(): void { - yield 'Age and name are valid' => [ - // context - [ - 'age' => 25, - 'name' => 'Ben', - ], - // rules - [ - ['age', NumericRule::class, 'min' => 5, 'max' => 65], - ['name', StringRule::class, 'min' => 3, 'max' => 30], - ], - // expectedResult - true, - // errorsSize - 0, - ]; - - yield 'Validate with multiple and rule' => [ - [ - 'age' => 25, - ], - [ - ['age', MultipleAndRule::class, MultipleAndRule::REQUIRED => true, MultipleAndRule::RULES => [ - [NumericRule::class, NumericRule::MIN => 14], - [RangeRule::class, RangeRule::RANGE => [25, 26]], - ]], - ], - true, - 0, - ]; + $validator = new Validator(['name' => 'John']); + + $this->assertSame('John', $validator->get('name')); + + $validator->setContext(['name' => 'Jane', 'age' => 25]); + + $this->assertSame('Jane', $validator->get('name')); + $this->assertSame(25, $validator->get('age')); + } - yield 'Validate with multiple or rule' => [ - [ - 'foo' => 'bar', - ], + public function testAppendExistingItemsOnlyDefaultBehavior(): void + { + // Default behavior: appendExistingItemOnly = false + // All validated keys should be added to validatedContext even if not in original context + $validator = new Validator( + ['name' => 'John'], [ - ['foo', MultipleOrRule::class, MultipleOrRule::REQUIRED => true, MultipleOrRule::RULES => [ - [NumericRule::class, NumericRule::MIN => 14], - [StringRule::class, StringRule::MIN => 1], - ]], - ], - true, - 0, - ]; + ['name', StringRule::class], + ['age', NumericRule::class], // Not in original context + ] + ); + + $res = $validator->validate(); + $this->assertTrue($res); + + $validatedContext = $validator->getValidatedContext(); + + $this->assertArrayHasKey('name', $validatedContext); + $this->assertArrayHasKey('age', $validatedContext); // Should be added even though not in original context + $this->assertNull($validatedContext['age']); // Value will be null + } - yield 'Age is not valid' => [ - [ - 'age' => 25, - ], - [ - ['age', NumericRule::class, 'min' => 26, 'max' => 65], - ], - false, - 1, - ]; - yield 'Key with numeric value' => [ - [ - 0 => 25, - 1 => 'Test', - ], - [ - [0, NumericRule::class, 'min' => 22, 'max' => 65], - [1, StringRule::class, 'min' => 0, 'max' => 10], - ], - true, - 0, - ]; - yield 'Key with index context' => [ - [ - 28, - 'Test2', - ], - [ - [0, NumericRule::class, 'min' => 22, 'max' => 65], - [1, StringRule::class, 'min' => 0, 'max' => 10], - ], - true, - 0, - ]; + public function testValidateMergeValidatedContext(): void + { + $validator = new Validator(['name' => 'John'], [ + ['name', StringRule::class], + ]); + + $this->assertTrue($validator->validate()); + $this->assertArrayHasKey('name', $validator->getValidatedContext()); + + // Change context and validate with merge = true + $validator->setContext(['age' => 25]); + $validator->setRules([['age', NumericRule::class]]); + + $this->assertTrue($validator->validate(true)); + + $validatedContext = $validator->getValidatedContext(); + + // Should have both name and age + $this->assertArrayHasKey('name', $validatedContext); + $this->assertArrayHasKey('age', $validatedContext); } }