├── .editorconfig ├── .github └── workflows │ ├── coding-standards.yml │ ├── kahlan-tests.yml │ └── psalm.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── composer ├── composerRequireChecker ├── kahlan ├── phpcs └── psalm ├── composer.json ├── composer.lock ├── crc-config.json ├── phpcs.xml.dist ├── psalm.xml ├── spec ├── ToBeEither.php ├── UseCaseValidationSpec.php ├── UserValidatorSpec.php ├── ValidationSpec.php └── ValidationTest.php └── src ├── Brand ├── ValidationBrand.php └── ValidationBrand2.php ├── Instances └── Validation │ ├── AllMonoid.php │ ├── AnyMonoid.php │ ├── ValidationAlt.php │ ├── ValidationAlternative.php │ ├── ValidationApplicative.php │ ├── ValidationApply.php │ ├── ValidationFunctor.php │ ├── ValidationPlus.php │ ├── ValidationProfunctor.php │ └── ValidationSemigroup.php └── Validation.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # PHP PSR-2 Coding Standards 5 | # http://www.php-fig.org/psr/psr-2/ 6 | 7 | root = true 8 | 9 | [*.php] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.json] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.github/workflows/coding-standards.yml: -------------------------------------------------------------------------------- 1 | name: "Check Coding Standards" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | coding-standards: 9 | name: "Check Coding Standards" 10 | 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | dependencies: 16 | - "locked" 17 | php-version: 18 | - "8.1" 19 | operating-system: 20 | - "ubuntu-latest" 21 | 22 | steps: 23 | - name: "Checkout" 24 | uses: "actions/checkout@v2" 25 | 26 | - name: "Install PHP" 27 | uses: "shivammathur/setup-php@v2" 28 | with: 29 | coverage: "pcov" 30 | php-version: "${{ matrix.php-version }}" 31 | ini-values: memory_limit=-1 32 | tools: composer:v2, cs2pr 33 | 34 | - name: "Cache dependencies" 35 | uses: "actions/cache@v2" 36 | with: 37 | path: | 38 | ~/.composer/cache 39 | vendor 40 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 41 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 42 | 43 | - name: "Install lowest dependencies" 44 | if: ${{ matrix.dependencies == 'lowest' }} 45 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" 46 | 47 | - name: "Install highest dependencies" 48 | if: ${{ matrix.dependencies == 'highest' }} 49 | run: "composer update --no-interaction --no-progress --no-suggest" 50 | 51 | - name: "Install locked dependencies" 52 | if: ${{ matrix.dependencies == 'locked' }} 53 | run: "composer install --no-interaction --no-progress --no-suggest" 54 | 55 | - name: "Coding Standard" 56 | run: "vendor/bin/phpcs -q --report=checkstyle | cs2pr" 57 | -------------------------------------------------------------------------------- /.github/workflows/kahlan-tests.yml: -------------------------------------------------------------------------------- 1 | name: "Kahlan tests" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | kahlan: 9 | name: "Kahlan tests" 10 | 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | dependencies: 16 | - "lowest" 17 | - "highest" 18 | - "locked" 19 | php-version: 20 | - "8.1" 21 | operating-system: 22 | - "ubuntu-latest" 23 | 24 | steps: 25 | - name: "Checkout" 26 | uses: "actions/checkout@v2" 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: "Install PHP" 31 | uses: "shivammathur/setup-php@v2" 32 | with: 33 | coverage: "pcov" 34 | php-version: "${{ matrix.php-version }}" 35 | ini-values: memory_limit=-1 36 | tools: composer:v2, cs2pr 37 | 38 | - name: "Cache dependencies" 39 | uses: "actions/cache@v2" 40 | with: 41 | path: | 42 | ~/.composer/cache 43 | vendor 44 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 45 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 46 | 47 | - name: "Install lowest dependencies" 48 | if: ${{ matrix.dependencies == 'lowest' }} 49 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" 50 | 51 | - name: "Install highest dependencies" 52 | if: ${{ matrix.dependencies == 'highest' }} 53 | run: "composer update --no-interaction --no-progress --no-suggest" 54 | 55 | - name: "Install locked dependencies" 56 | if: ${{ matrix.dependencies == 'locked' }} 57 | run: "composer install --no-interaction --no-progress --no-suggest" 58 | 59 | - name: "Tests" 60 | run: "vendor/bin/kahlan" -------------------------------------------------------------------------------- /.github/workflows/psalm.yml: -------------------------------------------------------------------------------- 1 | name: "Static Analysis by Psalm" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | static-analysis-psalm: 9 | name: "Static Analysis by Psalm" 10 | 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | dependencies: 16 | - "locked" 17 | php-version: 18 | - "8.1" 19 | operating-system: 20 | - "ubuntu-latest" 21 | 22 | steps: 23 | - name: "Checkout" 24 | uses: "actions/checkout@v2" 25 | 26 | - name: "Install PHP" 27 | uses: "shivammathur/setup-php@v2" 28 | with: 29 | coverage: "pcov" 30 | php-version: "${{ matrix.php-version }}" 31 | ini-values: memory_limit=-1 32 | tools: composer:v2, cs2pr 33 | 34 | - name: "Cache dependencies" 35 | uses: "actions/cache@v2" 36 | with: 37 | path: | 38 | ~/.composer/cache 39 | vendor 40 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 41 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 42 | 43 | - name: "Install lowest dependencies" 44 | if: ${{ matrix.dependencies == 'lowest' }} 45 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" 46 | 47 | - name: "Install highest dependencies" 48 | if: ${{ matrix.dependencies == 'highest' }} 49 | run: "composer update --no-interaction --no-progress --no-suggest" 50 | 51 | - name: "Install locked dependencies" 52 | if: ${{ matrix.dependencies == 'locked' }} 53 | run: "composer install --no-interaction --no-progress --no-suggest" 54 | 55 | - name: "psalm" 56 | run: "vendor/bin/psalm --output-format=github --shepherd --stats" 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .phpcs-cache 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Lamphpda Copyright (C) 2020 Marco Perone ("Licensor") 2 | 3 | Hippocratic License Version Number: 2.1. 4 | 5 | Purpose. The purpose of this License is for the Licensor named above to permit the Licensee (as defined below) broad permission, if consistent with Human Rights Laws and Human Rights Principles (as each is defined below), to use and work with the Software (as defined below) within the full scope of Licensor's copyright and patent rights, if any, in the Software, while ensuring attribution and protecting the Licensor from liability. 6 | 7 | Permission and Conditions. The Licensor grants permission by this license ("License"), free of charge, to the extent of Licensor's rights under applicable copyright and patent law, to any person or entity (the "Licensee") obtaining a copy of this software and associated documentation files (the "Software"), to do everything with the Software that would otherwise infringe (i) the Licensor's copyright in the Software or (ii) any patent claims to the Software that the Licensor can license or becomes able to license, subject to all of the following terms and conditions: 8 | 9 | * Acceptance. This License is automatically offered to every person and entity subject to its terms and conditions. Licensee accepts this License and agrees to its terms and conditions by taking any action with the Software that, absent this License, would infringe any intellectual property right held by Licensor. 10 | * Notice. Licensee must ensure that everyone who gets a copy of any part of this Software from Licensee, with or without changes, also receives the License and the above copyright notice (and if included by the Licensor, patent, trademark and attribution notice). Licensee must cause any modified versions of the Software to carry prominent notices stating that Licensee changed the Software. For clarity, although Licensee is free to create modifications of the Software and distribute only the modified portion created by Licensee with additional or different terms, the portion of the Software not modified must be distributed pursuant to this License. If anyone notifies Licensee in writing that Licensee has not complied with this Notice section, Licensee can keep this License by taking all practical steps to comply within 30 days after the notice. If Licensee does not do so, Licensee's License (and all rights licensed hereunder) shall end immediately. 11 | * Compliance with Human Rights Principles and Human Rights Laws. 12 | 1. Human Rights Principles. 13 | (a) Licensee is advised to consult the articles of the United Nations Universal Declaration of Human Rights and the United Nations Global Compact that define recognized principles of international human rights (the "Human Rights Principles"). Licensee shall use the Software in a manner consistent with Human Rights Principles. 14 | (b) Unless the Licensor and Licensee agree otherwise, any dispute, controversy, or claim arising out of or relating to (i) Section 1(a) regarding Human Rights Principles, including the breach of Section 1(a), termination of this License for breach of the Human Rights Principles, or invalidity of Section 1(a) or (ii) a determination of whether any Law is consistent or in conflict with Human Rights Principles pursuant to Section 2, below, shall be settled by arbitration in accordance with the Hague Rules on Business and Human Rights Arbitration (the "Rules"); provided, however, that Licensee may elect not to participate in such arbitration, in which event this License (and all rights licensed hereunder) shall end immediately. The number of arbitrators shall be one unless the Rules require otherwise. 15 | Unless both the Licensor and Licensee agree to the contrary: (1) All documents and information concerning the arbitration shall be public and may be disclosed by any party; (2) The repository referred to under Article 43 of the Rules shall make available to the public in a timely manner all documents concerning the arbitration which are communicated to it, including all submissions of the parties, all evidence admitted into the record of the proceedings, all transcripts or other recordings of hearings and all orders, decisions and awards of the arbitral tribunal, subject only to the arbitral tribunal's powers to take such measures as may be necessary to safeguard the integrity of the arbitral process pursuant to Articles 18, 33, 41 and 42 of the Rules; and (3) Article 26(6) of the Rules shall not apply. 16 | 2. Human Rights Laws. The Software shall not be used by any person or entity for any systems, activities, or other uses that violate any Human Rights Laws. "Human Rights Laws" means any applicable laws, regulations, or rules (collectively, "Laws") that protect human, civil, labor, privacy, political, environmental, security, economic, due process, or similar rights; provided, however, that such Laws are consistent and not in conflict with Human Rights Principles (a dispute over the consistency or a conflict between Laws and Human Rights Principles shall be determined by arbitration as stated above). Where the Human Rights Laws of more than one jurisdiction are applicable or in conflict with respect to the use of the Software, the Human Rights Laws that are most protective of the individuals or groups harmed shall apply. 17 | 3. Indemnity. Licensee shall hold harmless and indemnify Licensor (and any other contributor) against all losses, damages, liabilities, deficiencies, claims, actions, judgments, settlements, interest, awards, penalties, fines, costs, or expenses of whatever kind, including Licensor's reasonable attorneys' fees, arising out of or relating to Licensee's use of the Software in violation of Human Rights Laws or Human Rights Principles. 18 | * Failure to Comply. Any failure of Licensee to act according to the terms and conditions of this License is both a breach of the License and an infringement of the intellectual property rights of the Licensor (subject to exceptions under Laws, e.g., fair use). In the event of a breach or infringement, the terms and conditions of this License may be enforced by Licensor under the Laws of any jurisdiction to which Licensee is subject. Licensee also agrees that the Licensor may enforce the terms and conditions of this License against Licensee through specific performance (or similar remedy under Laws) to the extent permitted by Laws. For clarity, except in the event of a breach of this License, infringement, or as otherwise stated in this License, Licensor may not terminate this License with Licensee. 19 | * Enforceability and Interpretation. If any term or provision of this License is determined to be invalid, illegal, or unenforceable by a court of competent jurisdiction, then such invalidity, illegality, or unenforceability shall not affect any other term or provision of this License or invalidate or render unenforceable such term or provision in any other jurisdiction; provided, however, subject to a court modification pursuant to the immediately following sentence, if any term or provision of this License pertaining to Human Rights Laws or Human Rights Principles is deemed invalid, illegal, or unenforceable against Licensee by a court of competent jurisdiction, all rights in the Software granted to Licensee shall be deemed null and void as between Licensor and Licensee. Upon a determination that any term or provision is invalid, illegal, or unenforceable, to the extent permitted by Laws, the court may modify this License to affect the original purpose that the Software be used in compliance with Human Rights Principles and Human Rights Laws as closely as possible. The language in this License shall be interpreted as to its fair meaning and not strictly for or against any party. 20 | * Disclaimer. TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES "AS IS," WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR AND ANY OTHER CONTRIBUTOR SHALL NOT BE LIABLE TO ANYONE FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY KIND OF LEGAL CLAIM. 21 | 22 | This Hippocratic License is an Ethical Source license (https://ethicalsource.dev) and is offered for use by licensors and licensees at their own risk, on an "AS IS" basis, and with no warranties express or implied, to the maximum extent permitted by Laws. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lamphpda-validation 2 | 3 | A validation library using Either from [marcosh/lamphpda](https://github.com/marcosh/lamphpda) 4 | -------------------------------------------------------------------------------- /bin/composer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --rm -ti -u $(id -u):$(id -g) -v "$(pwd):/app:rw" -v ~/.ssh/id_rsa:/root/.ssh/id_rsa --env "COMPOSER_HOME=/tmp/composer" composer:2.1 "$@" 4 | -------------------------------------------------------------------------------- /bin/composerRequireChecker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --rm -u $(id -u):$(id -g) -v $(pwd):/app -w=/app php:8.1 sh -c "vendor/bin/composer-require-checker check --config-file /app/crc-config.json /app/composer.json" 4 | -------------------------------------------------------------------------------- /bin/kahlan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --rm -v $(pwd)/:/app -w=/app php:8.1 vendor/bin/kahlan --reporter=tree "$@" 4 | -------------------------------------------------------------------------------- /bin/phpcs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --rm -v $(pwd):/app -w=/app php:8.1 vendor/bin/phpcs 4 | -------------------------------------------------------------------------------- /bin/psalm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --rm -v $PWD:/app -w=/app php:8.1 vendor/bin/psalm "$@" 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marcosh/lamphpda-validation", 3 | "description": "A validation library using Either from marcosh/lamphpda", 4 | "version": "0.1.0", 5 | "type": "library", 6 | "license": "Hippocratic-2.1", 7 | "authors": [ 8 | { 9 | "name": "Marco Perone", 10 | "email": "pasafama@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "^8.1", 15 | "marcosh/lamphpda": "^3.1.1", 16 | "giorgiosironi/eris": "^1.0.0-rc2", 17 | "marcosh/lamphpda-optics": "^0.1" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Marcosh\\LamPHPda\\Validation\\": "src/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "Marcosh\\LamPHPda\\ValidationSpec\\": "spec/" 27 | } 28 | }, 29 | "require-dev": { 30 | "maglnet/composer-require-checker": "^3.8", 31 | "kahlan/kahlan": "^5.2", 32 | "squizlabs/php_codesniffer": "^3.10", 33 | "vimeo/psalm": "^4.30" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "6c5f1add315acdcbe537416cf4d2dd2d", 8 | "packages": [ 9 | { 10 | "name": "giorgiosironi/eris", 11 | "version": "1.0.0-rc2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/giorgiosironi/eris.git", 15 | "reference": "ac9afccb32fe791362c07ffc52faa3b477ca93a9" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/giorgiosironi/eris/zipball/ac9afccb32fe791362c07ffc52faa3b477ca93a9", 20 | "reference": "ac9afccb32fe791362c07ffc52faa3b477ca93a9", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^8.1" 25 | }, 26 | "require-dev": { 27 | "friendsofphp/php-cs-fixer": "^3.0", 28 | "ilario-pierbattista/reverse-regex": "^0.4.0", 29 | "phpstan/phpstan": "^1.10", 30 | "phpunit/phpunit": "^10.0.4", 31 | "psalm/phar": "^5.4", 32 | "rector/rector": "^0.18", 33 | "sebastian/comparator": ">=2.1.3" 34 | }, 35 | "suggest": { 36 | "icomefromthenet/reverse-regex": "v0.0.6.3 for the regex() Generator", 37 | "ilario-pierbattista/reverse-regex": "0.3.1 for the regex() Generator (alternative to icomefromthenet/reverse-regex) ", 38 | "phpunit/phpunit": "Standard way to run generated test cases" 39 | }, 40 | "type": "library", 41 | "autoload": { 42 | "files": [ 43 | "src/Generator/functions.php" 44 | ], 45 | "psr-4": { 46 | "Eris\\": "src/" 47 | } 48 | }, 49 | "notification-url": "https://packagist.org/downloads/", 50 | "license": [ 51 | "MIT" 52 | ], 53 | "authors": [ 54 | { 55 | "name": "Giorgio Sironi", 56 | "email": "info@giorgiosironi.com" 57 | }, 58 | { 59 | "name": "Mirko Bonadei", 60 | "email": "mirko.bonadei@gmail.com" 61 | }, 62 | { 63 | "name": "Gabriele Lana", 64 | "email": "gabriele.lana@gmail.com" 65 | } 66 | ], 67 | "description": "PHP library for property-based testing. Integrates with PHPUnit.", 68 | "support": { 69 | "issues": "https://github.com/giorgiosironi/eris/issues", 70 | "source": "https://github.com/giorgiosironi/eris/tree/1.0.0-rc2" 71 | }, 72 | "time": "2024-03-08T15:12:45+00:00" 73 | }, 74 | { 75 | "name": "marcosh/lamphpda", 76 | "version": "v3.1.1", 77 | "source": { 78 | "type": "git", 79 | "url": "https://github.com/marcosh/lamphpda.git", 80 | "reference": "6688060a883efa67d77bdafbac308c11af347384" 81 | }, 82 | "dist": { 83 | "type": "zip", 84 | "url": "https://api.github.com/repos/marcosh/lamphpda/zipball/6688060a883efa67d77bdafbac308c11af347384", 85 | "reference": "6688060a883efa67d77bdafbac308c11af347384", 86 | "shasum": "" 87 | }, 88 | "require": { 89 | "php": ">= 8.1" 90 | }, 91 | "require-dev": { 92 | "friendsofphp/php-cs-fixer": "^3.30", 93 | "kahlan/kahlan": "^5.2", 94 | "maglnet/composer-require-checker": "^4.7", 95 | "vimeo/psalm": "^5.15" 96 | }, 97 | "type": "library", 98 | "autoload": { 99 | "psr-4": { 100 | "Marcosh\\LamPHPda\\": "src/" 101 | } 102 | }, 103 | "notification-url": "https://packagist.org/downloads/", 104 | "license": [ 105 | "Hippocratic-2.1" 106 | ], 107 | "authors": [ 108 | { 109 | "name": "Marco Perone", 110 | "email": "pasafama@gmail.com" 111 | } 112 | ], 113 | "description": "A collection of functional programming data structures", 114 | "homepage": "https://github.com/marcosh/lamphpda", 115 | "keywords": [ 116 | "Applicative", 117 | "data structures", 118 | "functional programming", 119 | "functor", 120 | "monad", 121 | "monoid" 122 | ], 123 | "support": { 124 | "issues": "https://github.com/marcosh/lamphpda/issues", 125 | "source": "https://github.com/marcosh/lamphpda/tree/v3.1.1" 126 | }, 127 | "time": "2024-08-29T08:13:54+00:00" 128 | }, 129 | { 130 | "name": "marcosh/lamphpda-optics", 131 | "version": "v0.1", 132 | "source": { 133 | "type": "git", 134 | "url": "https://github.com/marcosh/lamphpda-optics.git", 135 | "reference": "448a35cfde23885d7ee7790addb57215d90ca6cb" 136 | }, 137 | "dist": { 138 | "type": "zip", 139 | "url": "https://api.github.com/repos/marcosh/lamphpda-optics/zipball/448a35cfde23885d7ee7790addb57215d90ca6cb", 140 | "reference": "448a35cfde23885d7ee7790addb57215d90ca6cb", 141 | "shasum": "" 142 | }, 143 | "require": { 144 | "marcosh/lamphpda": "^3.1.1", 145 | "php": "^8.1" 146 | }, 147 | "require-dev": { 148 | "kahlan/kahlan": "^5.2", 149 | "maglnet/composer-require-checker": "^3.8", 150 | "squizlabs/php_codesniffer": "^3.10", 151 | "vimeo/psalm": "^4.30" 152 | }, 153 | "type": "library", 154 | "autoload": { 155 | "psr-4": { 156 | "Marcosh\\LamPHPda\\Optics\\": "src/" 157 | } 158 | }, 159 | "notification-url": "https://packagist.org/downloads/", 160 | "license": [ 161 | "Hippocratic-2.1" 162 | ], 163 | "authors": [ 164 | { 165 | "name": "Marco Perone", 166 | "email": "pasafama@gmail.com" 167 | } 168 | ], 169 | "description": "A functional optics library for PHP", 170 | "support": { 171 | "issues": "https://github.com/marcosh/lamphpda-optics/issues", 172 | "source": "https://github.com/marcosh/lamphpda-optics/tree/v0.1" 173 | }, 174 | "time": "2024-08-29T08:31:16+00:00" 175 | } 176 | ], 177 | "packages-dev": [ 178 | { 179 | "name": "amphp/amp", 180 | "version": "v2.6.4", 181 | "source": { 182 | "type": "git", 183 | "url": "https://github.com/amphp/amp.git", 184 | "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" 185 | }, 186 | "dist": { 187 | "type": "zip", 188 | "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", 189 | "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", 190 | "shasum": "" 191 | }, 192 | "require": { 193 | "php": ">=7.1" 194 | }, 195 | "require-dev": { 196 | "amphp/php-cs-fixer-config": "dev-master", 197 | "amphp/phpunit-util": "^1", 198 | "ext-json": "*", 199 | "jetbrains/phpstorm-stubs": "^2019.3", 200 | "phpunit/phpunit": "^7 | ^8 | ^9", 201 | "react/promise": "^2", 202 | "vimeo/psalm": "^3.12" 203 | }, 204 | "type": "library", 205 | "extra": { 206 | "branch-alias": { 207 | "dev-master": "2.x-dev" 208 | } 209 | }, 210 | "autoload": { 211 | "files": [ 212 | "lib/functions.php", 213 | "lib/Internal/functions.php" 214 | ], 215 | "psr-4": { 216 | "Amp\\": "lib" 217 | } 218 | }, 219 | "notification-url": "https://packagist.org/downloads/", 220 | "license": [ 221 | "MIT" 222 | ], 223 | "authors": [ 224 | { 225 | "name": "Daniel Lowrey", 226 | "email": "rdlowrey@php.net" 227 | }, 228 | { 229 | "name": "Aaron Piotrowski", 230 | "email": "aaron@trowski.com" 231 | }, 232 | { 233 | "name": "Bob Weinand", 234 | "email": "bobwei9@hotmail.com" 235 | }, 236 | { 237 | "name": "Niklas Keller", 238 | "email": "me@kelunik.com" 239 | } 240 | ], 241 | "description": "A non-blocking concurrency framework for PHP applications.", 242 | "homepage": "https://amphp.org/amp", 243 | "keywords": [ 244 | "async", 245 | "asynchronous", 246 | "awaitable", 247 | "concurrency", 248 | "event", 249 | "event-loop", 250 | "future", 251 | "non-blocking", 252 | "promise" 253 | ], 254 | "support": { 255 | "irc": "irc://irc.freenode.org/amphp", 256 | "issues": "https://github.com/amphp/amp/issues", 257 | "source": "https://github.com/amphp/amp/tree/v2.6.4" 258 | }, 259 | "funding": [ 260 | { 261 | "url": "https://github.com/amphp", 262 | "type": "github" 263 | } 264 | ], 265 | "time": "2024-03-21T18:52:26+00:00" 266 | }, 267 | { 268 | "name": "amphp/byte-stream", 269 | "version": "v1.8.2", 270 | "source": { 271 | "type": "git", 272 | "url": "https://github.com/amphp/byte-stream.git", 273 | "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" 274 | }, 275 | "dist": { 276 | "type": "zip", 277 | "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", 278 | "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", 279 | "shasum": "" 280 | }, 281 | "require": { 282 | "amphp/amp": "^2", 283 | "php": ">=7.1" 284 | }, 285 | "require-dev": { 286 | "amphp/php-cs-fixer-config": "dev-master", 287 | "amphp/phpunit-util": "^1.4", 288 | "friendsofphp/php-cs-fixer": "^2.3", 289 | "jetbrains/phpstorm-stubs": "^2019.3", 290 | "phpunit/phpunit": "^6 || ^7 || ^8", 291 | "psalm/phar": "^3.11.4" 292 | }, 293 | "type": "library", 294 | "autoload": { 295 | "files": [ 296 | "lib/functions.php" 297 | ], 298 | "psr-4": { 299 | "Amp\\ByteStream\\": "lib" 300 | } 301 | }, 302 | "notification-url": "https://packagist.org/downloads/", 303 | "license": [ 304 | "MIT" 305 | ], 306 | "authors": [ 307 | { 308 | "name": "Aaron Piotrowski", 309 | "email": "aaron@trowski.com" 310 | }, 311 | { 312 | "name": "Niklas Keller", 313 | "email": "me@kelunik.com" 314 | } 315 | ], 316 | "description": "A stream abstraction to make working with non-blocking I/O simple.", 317 | "homepage": "https://amphp.org/byte-stream", 318 | "keywords": [ 319 | "amp", 320 | "amphp", 321 | "async", 322 | "io", 323 | "non-blocking", 324 | "stream" 325 | ], 326 | "support": { 327 | "issues": "https://github.com/amphp/byte-stream/issues", 328 | "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" 329 | }, 330 | "funding": [ 331 | { 332 | "url": "https://github.com/amphp", 333 | "type": "github" 334 | } 335 | ], 336 | "time": "2024-04-13T18:00:56+00:00" 337 | }, 338 | { 339 | "name": "composer/package-versions-deprecated", 340 | "version": "1.11.99.5", 341 | "source": { 342 | "type": "git", 343 | "url": "https://github.com/composer/package-versions-deprecated.git", 344 | "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" 345 | }, 346 | "dist": { 347 | "type": "zip", 348 | "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", 349 | "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", 350 | "shasum": "" 351 | }, 352 | "require": { 353 | "composer-plugin-api": "^1.1.0 || ^2.0", 354 | "php": "^7 || ^8" 355 | }, 356 | "replace": { 357 | "ocramius/package-versions": "1.11.99" 358 | }, 359 | "require-dev": { 360 | "composer/composer": "^1.9.3 || ^2.0@dev", 361 | "ext-zip": "^1.13", 362 | "phpunit/phpunit": "^6.5 || ^7" 363 | }, 364 | "type": "composer-plugin", 365 | "extra": { 366 | "class": "PackageVersions\\Installer", 367 | "branch-alias": { 368 | "dev-master": "1.x-dev" 369 | } 370 | }, 371 | "autoload": { 372 | "psr-4": { 373 | "PackageVersions\\": "src/PackageVersions" 374 | } 375 | }, 376 | "notification-url": "https://packagist.org/downloads/", 377 | "license": [ 378 | "MIT" 379 | ], 380 | "authors": [ 381 | { 382 | "name": "Marco Pivetta", 383 | "email": "ocramius@gmail.com" 384 | }, 385 | { 386 | "name": "Jordi Boggiano", 387 | "email": "j.boggiano@seld.be" 388 | } 389 | ], 390 | "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", 391 | "support": { 392 | "issues": "https://github.com/composer/package-versions-deprecated/issues", 393 | "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" 394 | }, 395 | "funding": [ 396 | { 397 | "url": "https://packagist.com", 398 | "type": "custom" 399 | }, 400 | { 401 | "url": "https://github.com/composer", 402 | "type": "github" 403 | }, 404 | { 405 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 406 | "type": "tidelift" 407 | } 408 | ], 409 | "time": "2022-01-17T14:14:24+00:00" 410 | }, 411 | { 412 | "name": "composer/pcre", 413 | "version": "3.3.1", 414 | "source": { 415 | "type": "git", 416 | "url": "https://github.com/composer/pcre.git", 417 | "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" 418 | }, 419 | "dist": { 420 | "type": "zip", 421 | "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", 422 | "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", 423 | "shasum": "" 424 | }, 425 | "require": { 426 | "php": "^7.4 || ^8.0" 427 | }, 428 | "conflict": { 429 | "phpstan/phpstan": "<1.11.10" 430 | }, 431 | "require-dev": { 432 | "phpstan/phpstan": "^1.11.10", 433 | "phpstan/phpstan-strict-rules": "^1.1", 434 | "phpunit/phpunit": "^8 || ^9" 435 | }, 436 | "type": "library", 437 | "extra": { 438 | "branch-alias": { 439 | "dev-main": "3.x-dev" 440 | }, 441 | "phpstan": { 442 | "includes": [ 443 | "extension.neon" 444 | ] 445 | } 446 | }, 447 | "autoload": { 448 | "psr-4": { 449 | "Composer\\Pcre\\": "src" 450 | } 451 | }, 452 | "notification-url": "https://packagist.org/downloads/", 453 | "license": [ 454 | "MIT" 455 | ], 456 | "authors": [ 457 | { 458 | "name": "Jordi Boggiano", 459 | "email": "j.boggiano@seld.be", 460 | "homepage": "http://seld.be" 461 | } 462 | ], 463 | "description": "PCRE wrapping library that offers type-safe preg_* replacements.", 464 | "keywords": [ 465 | "PCRE", 466 | "preg", 467 | "regex", 468 | "regular expression" 469 | ], 470 | "support": { 471 | "issues": "https://github.com/composer/pcre/issues", 472 | "source": "https://github.com/composer/pcre/tree/3.3.1" 473 | }, 474 | "funding": [ 475 | { 476 | "url": "https://packagist.com", 477 | "type": "custom" 478 | }, 479 | { 480 | "url": "https://github.com/composer", 481 | "type": "github" 482 | }, 483 | { 484 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 485 | "type": "tidelift" 486 | } 487 | ], 488 | "time": "2024-08-27T18:44:43+00:00" 489 | }, 490 | { 491 | "name": "composer/semver", 492 | "version": "3.4.2", 493 | "source": { 494 | "type": "git", 495 | "url": "https://github.com/composer/semver.git", 496 | "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" 497 | }, 498 | "dist": { 499 | "type": "zip", 500 | "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", 501 | "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", 502 | "shasum": "" 503 | }, 504 | "require": { 505 | "php": "^5.3.2 || ^7.0 || ^8.0" 506 | }, 507 | "require-dev": { 508 | "phpstan/phpstan": "^1.4", 509 | "symfony/phpunit-bridge": "^4.2 || ^5" 510 | }, 511 | "type": "library", 512 | "extra": { 513 | "branch-alias": { 514 | "dev-main": "3.x-dev" 515 | } 516 | }, 517 | "autoload": { 518 | "psr-4": { 519 | "Composer\\Semver\\": "src" 520 | } 521 | }, 522 | "notification-url": "https://packagist.org/downloads/", 523 | "license": [ 524 | "MIT" 525 | ], 526 | "authors": [ 527 | { 528 | "name": "Nils Adermann", 529 | "email": "naderman@naderman.de", 530 | "homepage": "http://www.naderman.de" 531 | }, 532 | { 533 | "name": "Jordi Boggiano", 534 | "email": "j.boggiano@seld.be", 535 | "homepage": "http://seld.be" 536 | }, 537 | { 538 | "name": "Rob Bast", 539 | "email": "rob.bast@gmail.com", 540 | "homepage": "http://robbast.nl" 541 | } 542 | ], 543 | "description": "Semver library that offers utilities, version constraint parsing and validation.", 544 | "keywords": [ 545 | "semantic", 546 | "semver", 547 | "validation", 548 | "versioning" 549 | ], 550 | "support": { 551 | "irc": "ircs://irc.libera.chat:6697/composer", 552 | "issues": "https://github.com/composer/semver/issues", 553 | "source": "https://github.com/composer/semver/tree/3.4.2" 554 | }, 555 | "funding": [ 556 | { 557 | "url": "https://packagist.com", 558 | "type": "custom" 559 | }, 560 | { 561 | "url": "https://github.com/composer", 562 | "type": "github" 563 | }, 564 | { 565 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 566 | "type": "tidelift" 567 | } 568 | ], 569 | "time": "2024-07-12T11:35:52+00:00" 570 | }, 571 | { 572 | "name": "composer/xdebug-handler", 573 | "version": "3.0.5", 574 | "source": { 575 | "type": "git", 576 | "url": "https://github.com/composer/xdebug-handler.git", 577 | "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" 578 | }, 579 | "dist": { 580 | "type": "zip", 581 | "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", 582 | "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", 583 | "shasum": "" 584 | }, 585 | "require": { 586 | "composer/pcre": "^1 || ^2 || ^3", 587 | "php": "^7.2.5 || ^8.0", 588 | "psr/log": "^1 || ^2 || ^3" 589 | }, 590 | "require-dev": { 591 | "phpstan/phpstan": "^1.0", 592 | "phpstan/phpstan-strict-rules": "^1.1", 593 | "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" 594 | }, 595 | "type": "library", 596 | "autoload": { 597 | "psr-4": { 598 | "Composer\\XdebugHandler\\": "src" 599 | } 600 | }, 601 | "notification-url": "https://packagist.org/downloads/", 602 | "license": [ 603 | "MIT" 604 | ], 605 | "authors": [ 606 | { 607 | "name": "John Stevenson", 608 | "email": "john-stevenson@blueyonder.co.uk" 609 | } 610 | ], 611 | "description": "Restarts a process without Xdebug.", 612 | "keywords": [ 613 | "Xdebug", 614 | "performance" 615 | ], 616 | "support": { 617 | "irc": "ircs://irc.libera.chat:6697/composer", 618 | "issues": "https://github.com/composer/xdebug-handler/issues", 619 | "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" 620 | }, 621 | "funding": [ 622 | { 623 | "url": "https://packagist.com", 624 | "type": "custom" 625 | }, 626 | { 627 | "url": "https://github.com/composer", 628 | "type": "github" 629 | }, 630 | { 631 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 632 | "type": "tidelift" 633 | } 634 | ], 635 | "time": "2024-05-06T16:37:16+00:00" 636 | }, 637 | { 638 | "name": "dnoegel/php-xdg-base-dir", 639 | "version": "v0.1.1", 640 | "source": { 641 | "type": "git", 642 | "url": "https://github.com/dnoegel/php-xdg-base-dir.git", 643 | "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" 644 | }, 645 | "dist": { 646 | "type": "zip", 647 | "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", 648 | "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", 649 | "shasum": "" 650 | }, 651 | "require": { 652 | "php": ">=5.3.2" 653 | }, 654 | "require-dev": { 655 | "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" 656 | }, 657 | "type": "library", 658 | "autoload": { 659 | "psr-4": { 660 | "XdgBaseDir\\": "src/" 661 | } 662 | }, 663 | "notification-url": "https://packagist.org/downloads/", 664 | "license": [ 665 | "MIT" 666 | ], 667 | "description": "implementation of xdg base directory specification for php", 668 | "support": { 669 | "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", 670 | "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" 671 | }, 672 | "time": "2019-12-04T15:06:13+00:00" 673 | }, 674 | { 675 | "name": "doctrine/deprecations", 676 | "version": "1.1.3", 677 | "source": { 678 | "type": "git", 679 | "url": "https://github.com/doctrine/deprecations.git", 680 | "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" 681 | }, 682 | "dist": { 683 | "type": "zip", 684 | "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", 685 | "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", 686 | "shasum": "" 687 | }, 688 | "require": { 689 | "php": "^7.1 || ^8.0" 690 | }, 691 | "require-dev": { 692 | "doctrine/coding-standard": "^9", 693 | "phpstan/phpstan": "1.4.10 || 1.10.15", 694 | "phpstan/phpstan-phpunit": "^1.0", 695 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 696 | "psalm/plugin-phpunit": "0.18.4", 697 | "psr/log": "^1 || ^2 || ^3", 698 | "vimeo/psalm": "4.30.0 || 5.12.0" 699 | }, 700 | "suggest": { 701 | "psr/log": "Allows logging deprecations via PSR-3 logger implementation" 702 | }, 703 | "type": "library", 704 | "autoload": { 705 | "psr-4": { 706 | "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" 707 | } 708 | }, 709 | "notification-url": "https://packagist.org/downloads/", 710 | "license": [ 711 | "MIT" 712 | ], 713 | "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", 714 | "homepage": "https://www.doctrine-project.org/", 715 | "support": { 716 | "issues": "https://github.com/doctrine/deprecations/issues", 717 | "source": "https://github.com/doctrine/deprecations/tree/1.1.3" 718 | }, 719 | "time": "2024-01-30T19:34:25+00:00" 720 | }, 721 | { 722 | "name": "felixfbecker/advanced-json-rpc", 723 | "version": "v3.2.1", 724 | "source": { 725 | "type": "git", 726 | "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", 727 | "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" 728 | }, 729 | "dist": { 730 | "type": "zip", 731 | "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", 732 | "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", 733 | "shasum": "" 734 | }, 735 | "require": { 736 | "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", 737 | "php": "^7.1 || ^8.0", 738 | "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" 739 | }, 740 | "require-dev": { 741 | "phpunit/phpunit": "^7.0 || ^8.0" 742 | }, 743 | "type": "library", 744 | "autoload": { 745 | "psr-4": { 746 | "AdvancedJsonRpc\\": "lib/" 747 | } 748 | }, 749 | "notification-url": "https://packagist.org/downloads/", 750 | "license": [ 751 | "ISC" 752 | ], 753 | "authors": [ 754 | { 755 | "name": "Felix Becker", 756 | "email": "felix.b@outlook.com" 757 | } 758 | ], 759 | "description": "A more advanced JSONRPC implementation", 760 | "support": { 761 | "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", 762 | "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" 763 | }, 764 | "time": "2021-06-11T22:34:44+00:00" 765 | }, 766 | { 767 | "name": "felixfbecker/language-server-protocol", 768 | "version": "v1.5.2", 769 | "source": { 770 | "type": "git", 771 | "url": "https://github.com/felixfbecker/php-language-server-protocol.git", 772 | "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" 773 | }, 774 | "dist": { 775 | "type": "zip", 776 | "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", 777 | "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", 778 | "shasum": "" 779 | }, 780 | "require": { 781 | "php": ">=7.1" 782 | }, 783 | "require-dev": { 784 | "phpstan/phpstan": "*", 785 | "squizlabs/php_codesniffer": "^3.1", 786 | "vimeo/psalm": "^4.0" 787 | }, 788 | "type": "library", 789 | "extra": { 790 | "branch-alias": { 791 | "dev-master": "1.x-dev" 792 | } 793 | }, 794 | "autoload": { 795 | "psr-4": { 796 | "LanguageServerProtocol\\": "src/" 797 | } 798 | }, 799 | "notification-url": "https://packagist.org/downloads/", 800 | "license": [ 801 | "ISC" 802 | ], 803 | "authors": [ 804 | { 805 | "name": "Felix Becker", 806 | "email": "felix.b@outlook.com" 807 | } 808 | ], 809 | "description": "PHP classes for the Language Server Protocol", 810 | "keywords": [ 811 | "language", 812 | "microsoft", 813 | "php", 814 | "server" 815 | ], 816 | "support": { 817 | "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", 818 | "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" 819 | }, 820 | "time": "2022-03-02T22:36:06+00:00" 821 | }, 822 | { 823 | "name": "kahlan/kahlan", 824 | "version": "5.2.7", 825 | "source": { 826 | "type": "git", 827 | "url": "https://github.com/kahlan/kahlan.git", 828 | "reference": "72e043f95ee253f54dfcbd5cd646dc3fe0738835" 829 | }, 830 | "dist": { 831 | "type": "zip", 832 | "url": "https://api.github.com/repos/kahlan/kahlan/zipball/72e043f95ee253f54dfcbd5cd646dc3fe0738835", 833 | "reference": "72e043f95ee253f54dfcbd5cd646dc3fe0738835", 834 | "shasum": "" 835 | }, 836 | "require": { 837 | "php": ">=7.2" 838 | }, 839 | "require-dev": { 840 | "rector/rector": "^1.0.5", 841 | "squizlabs/php_codesniffer": "3.7.2" 842 | }, 843 | "bin": [ 844 | "bin/kahlan" 845 | ], 846 | "type": "library", 847 | "autoload": { 848 | "files": [ 849 | "src/functions.php" 850 | ], 851 | "psr-4": { 852 | "Kahlan\\": "src/" 853 | } 854 | }, 855 | "notification-url": "https://packagist.org/downloads/", 856 | "license": [ 857 | "MIT" 858 | ], 859 | "authors": [ 860 | { 861 | "name": "CrysaLEAD" 862 | } 863 | ], 864 | "description": "The PHP Test Framework for Freedom, Truth and Justice.", 865 | "keywords": [ 866 | "BDD", 867 | "Behavior-Driven Development", 868 | "Monkey Patching", 869 | "TDD", 870 | "mock", 871 | "stub", 872 | "testing", 873 | "unit test" 874 | ], 875 | "support": { 876 | "issues": "https://github.com/kahlan/kahlan/issues", 877 | "source": "https://github.com/kahlan/kahlan/tree/5.2.7" 878 | }, 879 | "time": "2024-06-15T11:51:00+00:00" 880 | }, 881 | { 882 | "name": "maglnet/composer-require-checker", 883 | "version": "3.8.0", 884 | "source": { 885 | "type": "git", 886 | "url": "https://github.com/maglnet/ComposerRequireChecker.git", 887 | "reference": "537138b833ab0f9ad72b667a72bece2a765e88ab" 888 | }, 889 | "dist": { 890 | "type": "zip", 891 | "url": "https://api.github.com/repos/maglnet/ComposerRequireChecker/zipball/537138b833ab0f9ad72b667a72bece2a765e88ab", 892 | "reference": "537138b833ab0f9ad72b667a72bece2a765e88ab", 893 | "shasum": "" 894 | }, 895 | "require": { 896 | "composer-runtime-api": "^2.0.0", 897 | "ext-json": "*", 898 | "ext-phar": "*", 899 | "nikic/php-parser": "^4.13.0", 900 | "php": "^7.4 || ^8.0", 901 | "symfony/console": "^5.4.0", 902 | "webmozart/assert": "^1.9.1", 903 | "webmozart/glob": "^4.4.0" 904 | }, 905 | "require-dev": { 906 | "doctrine/coding-standard": "^9.0.0", 907 | "ext-zend-opcache": "*", 908 | "mikey179/vfsstream": "^1.6.10", 909 | "phing/phing": "^2.17.0", 910 | "phpstan/phpstan": "^1.2.0", 911 | "phpunit/phpunit": "^9.5.10", 912 | "vimeo/psalm": "^4.14.0" 913 | }, 914 | "bin": [ 915 | "bin/composer-require-checker" 916 | ], 917 | "type": "library", 918 | "extra": { 919 | "branch-alias": { 920 | "dev-master": "2.1-dev" 921 | } 922 | }, 923 | "autoload": { 924 | "psr-4": { 925 | "ComposerRequireChecker\\": "src/ComposerRequireChecker" 926 | } 927 | }, 928 | "notification-url": "https://packagist.org/downloads/", 929 | "license": [ 930 | "MIT" 931 | ], 932 | "authors": [ 933 | { 934 | "name": "Marco Pivetta", 935 | "email": "ocramius@gmail.com", 936 | "homepage": "http://ocramius.github.io/" 937 | }, 938 | { 939 | "name": "Matthias Glaub", 940 | "email": "magl@magl.net", 941 | "homepage": "http://magl.net" 942 | } 943 | ], 944 | "description": "CLI tool to analyze composer dependencies and verify that no unknown symbols are used in the sources of a package", 945 | "homepage": "https://github.com/maglnet/ComposerRequireChecker", 946 | "keywords": [ 947 | "analysis", 948 | "cli", 949 | "composer", 950 | "dependency", 951 | "imports", 952 | "require", 953 | "requirements" 954 | ], 955 | "support": { 956 | "issues": "https://github.com/maglnet/ComposerRequireChecker/issues", 957 | "source": "https://github.com/maglnet/ComposerRequireChecker/tree/3.8.0" 958 | }, 959 | "time": "2021-12-07T14:25:47+00:00" 960 | }, 961 | { 962 | "name": "netresearch/jsonmapper", 963 | "version": "v4.4.1", 964 | "source": { 965 | "type": "git", 966 | "url": "https://github.com/cweiske/jsonmapper.git", 967 | "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" 968 | }, 969 | "dist": { 970 | "type": "zip", 971 | "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", 972 | "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", 973 | "shasum": "" 974 | }, 975 | "require": { 976 | "ext-json": "*", 977 | "ext-pcre": "*", 978 | "ext-reflection": "*", 979 | "ext-spl": "*", 980 | "php": ">=7.1" 981 | }, 982 | "require-dev": { 983 | "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", 984 | "squizlabs/php_codesniffer": "~3.5" 985 | }, 986 | "type": "library", 987 | "autoload": { 988 | "psr-0": { 989 | "JsonMapper": "src/" 990 | } 991 | }, 992 | "notification-url": "https://packagist.org/downloads/", 993 | "license": [ 994 | "OSL-3.0" 995 | ], 996 | "authors": [ 997 | { 998 | "name": "Christian Weiske", 999 | "email": "cweiske@cweiske.de", 1000 | "homepage": "http://github.com/cweiske/jsonmapper/", 1001 | "role": "Developer" 1002 | } 1003 | ], 1004 | "description": "Map nested JSON structures onto PHP classes", 1005 | "support": { 1006 | "email": "cweiske@cweiske.de", 1007 | "issues": "https://github.com/cweiske/jsonmapper/issues", 1008 | "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" 1009 | }, 1010 | "time": "2024-01-31T06:18:54+00:00" 1011 | }, 1012 | { 1013 | "name": "nikic/php-parser", 1014 | "version": "v4.19.1", 1015 | "source": { 1016 | "type": "git", 1017 | "url": "https://github.com/nikic/PHP-Parser.git", 1018 | "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" 1019 | }, 1020 | "dist": { 1021 | "type": "zip", 1022 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", 1023 | "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", 1024 | "shasum": "" 1025 | }, 1026 | "require": { 1027 | "ext-tokenizer": "*", 1028 | "php": ">=7.1" 1029 | }, 1030 | "require-dev": { 1031 | "ircmaxell/php-yacc": "^0.0.7", 1032 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 1033 | }, 1034 | "bin": [ 1035 | "bin/php-parse" 1036 | ], 1037 | "type": "library", 1038 | "extra": { 1039 | "branch-alias": { 1040 | "dev-master": "4.9-dev" 1041 | } 1042 | }, 1043 | "autoload": { 1044 | "psr-4": { 1045 | "PhpParser\\": "lib/PhpParser" 1046 | } 1047 | }, 1048 | "notification-url": "https://packagist.org/downloads/", 1049 | "license": [ 1050 | "BSD-3-Clause" 1051 | ], 1052 | "authors": [ 1053 | { 1054 | "name": "Nikita Popov" 1055 | } 1056 | ], 1057 | "description": "A PHP parser written in PHP", 1058 | "keywords": [ 1059 | "parser", 1060 | "php" 1061 | ], 1062 | "support": { 1063 | "issues": "https://github.com/nikic/PHP-Parser/issues", 1064 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" 1065 | }, 1066 | "time": "2024-03-17T08:10:35+00:00" 1067 | }, 1068 | { 1069 | "name": "openlss/lib-array2xml", 1070 | "version": "1.0.0", 1071 | "source": { 1072 | "type": "git", 1073 | "url": "https://github.com/nullivex/lib-array2xml.git", 1074 | "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" 1075 | }, 1076 | "dist": { 1077 | "type": "zip", 1078 | "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", 1079 | "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", 1080 | "shasum": "" 1081 | }, 1082 | "require": { 1083 | "php": ">=5.3.2" 1084 | }, 1085 | "type": "library", 1086 | "autoload": { 1087 | "psr-0": { 1088 | "LSS": "" 1089 | } 1090 | }, 1091 | "notification-url": "https://packagist.org/downloads/", 1092 | "license": [ 1093 | "Apache-2.0" 1094 | ], 1095 | "authors": [ 1096 | { 1097 | "name": "Bryan Tong", 1098 | "email": "bryan@nullivex.com", 1099 | "homepage": "https://www.nullivex.com" 1100 | }, 1101 | { 1102 | "name": "Tony Butler", 1103 | "email": "spudz76@gmail.com", 1104 | "homepage": "https://www.nullivex.com" 1105 | } 1106 | ], 1107 | "description": "Array2XML conversion library credit to lalit.org", 1108 | "homepage": "https://www.nullivex.com", 1109 | "keywords": [ 1110 | "array", 1111 | "array conversion", 1112 | "xml", 1113 | "xml conversion" 1114 | ], 1115 | "support": { 1116 | "issues": "https://github.com/nullivex/lib-array2xml/issues", 1117 | "source": "https://github.com/nullivex/lib-array2xml/tree/master" 1118 | }, 1119 | "time": "2019-03-29T20:06:56+00:00" 1120 | }, 1121 | { 1122 | "name": "phpdocumentor/reflection-common", 1123 | "version": "2.2.0", 1124 | "source": { 1125 | "type": "git", 1126 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 1127 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 1128 | }, 1129 | "dist": { 1130 | "type": "zip", 1131 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 1132 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 1133 | "shasum": "" 1134 | }, 1135 | "require": { 1136 | "php": "^7.2 || ^8.0" 1137 | }, 1138 | "type": "library", 1139 | "extra": { 1140 | "branch-alias": { 1141 | "dev-2.x": "2.x-dev" 1142 | } 1143 | }, 1144 | "autoload": { 1145 | "psr-4": { 1146 | "phpDocumentor\\Reflection\\": "src/" 1147 | } 1148 | }, 1149 | "notification-url": "https://packagist.org/downloads/", 1150 | "license": [ 1151 | "MIT" 1152 | ], 1153 | "authors": [ 1154 | { 1155 | "name": "Jaap van Otterdijk", 1156 | "email": "opensource@ijaap.nl" 1157 | } 1158 | ], 1159 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 1160 | "homepage": "http://www.phpdoc.org", 1161 | "keywords": [ 1162 | "FQSEN", 1163 | "phpDocumentor", 1164 | "phpdoc", 1165 | "reflection", 1166 | "static analysis" 1167 | ], 1168 | "support": { 1169 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 1170 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 1171 | }, 1172 | "time": "2020-06-27T09:03:43+00:00" 1173 | }, 1174 | { 1175 | "name": "phpdocumentor/reflection-docblock", 1176 | "version": "5.4.1", 1177 | "source": { 1178 | "type": "git", 1179 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 1180 | "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" 1181 | }, 1182 | "dist": { 1183 | "type": "zip", 1184 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", 1185 | "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", 1186 | "shasum": "" 1187 | }, 1188 | "require": { 1189 | "doctrine/deprecations": "^1.1", 1190 | "ext-filter": "*", 1191 | "php": "^7.4 || ^8.0", 1192 | "phpdocumentor/reflection-common": "^2.2", 1193 | "phpdocumentor/type-resolver": "^1.7", 1194 | "phpstan/phpdoc-parser": "^1.7", 1195 | "webmozart/assert": "^1.9.1" 1196 | }, 1197 | "require-dev": { 1198 | "mockery/mockery": "~1.3.5", 1199 | "phpstan/extension-installer": "^1.1", 1200 | "phpstan/phpstan": "^1.8", 1201 | "phpstan/phpstan-mockery": "^1.1", 1202 | "phpstan/phpstan-webmozart-assert": "^1.2", 1203 | "phpunit/phpunit": "^9.5", 1204 | "vimeo/psalm": "^5.13" 1205 | }, 1206 | "type": "library", 1207 | "extra": { 1208 | "branch-alias": { 1209 | "dev-master": "5.x-dev" 1210 | } 1211 | }, 1212 | "autoload": { 1213 | "psr-4": { 1214 | "phpDocumentor\\Reflection\\": "src" 1215 | } 1216 | }, 1217 | "notification-url": "https://packagist.org/downloads/", 1218 | "license": [ 1219 | "MIT" 1220 | ], 1221 | "authors": [ 1222 | { 1223 | "name": "Mike van Riel", 1224 | "email": "me@mikevanriel.com" 1225 | }, 1226 | { 1227 | "name": "Jaap van Otterdijk", 1228 | "email": "opensource@ijaap.nl" 1229 | } 1230 | ], 1231 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 1232 | "support": { 1233 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 1234 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" 1235 | }, 1236 | "time": "2024-05-21T05:55:05+00:00" 1237 | }, 1238 | { 1239 | "name": "phpdocumentor/type-resolver", 1240 | "version": "1.8.2", 1241 | "source": { 1242 | "type": "git", 1243 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 1244 | "reference": "153ae662783729388a584b4361f2545e4d841e3c" 1245 | }, 1246 | "dist": { 1247 | "type": "zip", 1248 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", 1249 | "reference": "153ae662783729388a584b4361f2545e4d841e3c", 1250 | "shasum": "" 1251 | }, 1252 | "require": { 1253 | "doctrine/deprecations": "^1.0", 1254 | "php": "^7.3 || ^8.0", 1255 | "phpdocumentor/reflection-common": "^2.0", 1256 | "phpstan/phpdoc-parser": "^1.13" 1257 | }, 1258 | "require-dev": { 1259 | "ext-tokenizer": "*", 1260 | "phpbench/phpbench": "^1.2", 1261 | "phpstan/extension-installer": "^1.1", 1262 | "phpstan/phpstan": "^1.8", 1263 | "phpstan/phpstan-phpunit": "^1.1", 1264 | "phpunit/phpunit": "^9.5", 1265 | "rector/rector": "^0.13.9", 1266 | "vimeo/psalm": "^4.25" 1267 | }, 1268 | "type": "library", 1269 | "extra": { 1270 | "branch-alias": { 1271 | "dev-1.x": "1.x-dev" 1272 | } 1273 | }, 1274 | "autoload": { 1275 | "psr-4": { 1276 | "phpDocumentor\\Reflection\\": "src" 1277 | } 1278 | }, 1279 | "notification-url": "https://packagist.org/downloads/", 1280 | "license": [ 1281 | "MIT" 1282 | ], 1283 | "authors": [ 1284 | { 1285 | "name": "Mike van Riel", 1286 | "email": "me@mikevanriel.com" 1287 | } 1288 | ], 1289 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 1290 | "support": { 1291 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 1292 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" 1293 | }, 1294 | "time": "2024-02-23T11:10:43+00:00" 1295 | }, 1296 | { 1297 | "name": "phpstan/phpdoc-parser", 1298 | "version": "1.29.1", 1299 | "source": { 1300 | "type": "git", 1301 | "url": "https://github.com/phpstan/phpdoc-parser.git", 1302 | "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" 1303 | }, 1304 | "dist": { 1305 | "type": "zip", 1306 | "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", 1307 | "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", 1308 | "shasum": "" 1309 | }, 1310 | "require": { 1311 | "php": "^7.2 || ^8.0" 1312 | }, 1313 | "require-dev": { 1314 | "doctrine/annotations": "^2.0", 1315 | "nikic/php-parser": "^4.15", 1316 | "php-parallel-lint/php-parallel-lint": "^1.2", 1317 | "phpstan/extension-installer": "^1.0", 1318 | "phpstan/phpstan": "^1.5", 1319 | "phpstan/phpstan-phpunit": "^1.1", 1320 | "phpstan/phpstan-strict-rules": "^1.0", 1321 | "phpunit/phpunit": "^9.5", 1322 | "symfony/process": "^5.2" 1323 | }, 1324 | "type": "library", 1325 | "autoload": { 1326 | "psr-4": { 1327 | "PHPStan\\PhpDocParser\\": [ 1328 | "src/" 1329 | ] 1330 | } 1331 | }, 1332 | "notification-url": "https://packagist.org/downloads/", 1333 | "license": [ 1334 | "MIT" 1335 | ], 1336 | "description": "PHPDoc parser with support for nullable, intersection and generic types", 1337 | "support": { 1338 | "issues": "https://github.com/phpstan/phpdoc-parser/issues", 1339 | "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" 1340 | }, 1341 | "time": "2024-05-31T08:52:43+00:00" 1342 | }, 1343 | { 1344 | "name": "psr/container", 1345 | "version": "2.0.2", 1346 | "source": { 1347 | "type": "git", 1348 | "url": "https://github.com/php-fig/container.git", 1349 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" 1350 | }, 1351 | "dist": { 1352 | "type": "zip", 1353 | "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", 1354 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", 1355 | "shasum": "" 1356 | }, 1357 | "require": { 1358 | "php": ">=7.4.0" 1359 | }, 1360 | "type": "library", 1361 | "extra": { 1362 | "branch-alias": { 1363 | "dev-master": "2.0.x-dev" 1364 | } 1365 | }, 1366 | "autoload": { 1367 | "psr-4": { 1368 | "Psr\\Container\\": "src/" 1369 | } 1370 | }, 1371 | "notification-url": "https://packagist.org/downloads/", 1372 | "license": [ 1373 | "MIT" 1374 | ], 1375 | "authors": [ 1376 | { 1377 | "name": "PHP-FIG", 1378 | "homepage": "https://www.php-fig.org/" 1379 | } 1380 | ], 1381 | "description": "Common Container Interface (PHP FIG PSR-11)", 1382 | "homepage": "https://github.com/php-fig/container", 1383 | "keywords": [ 1384 | "PSR-11", 1385 | "container", 1386 | "container-interface", 1387 | "container-interop", 1388 | "psr" 1389 | ], 1390 | "support": { 1391 | "issues": "https://github.com/php-fig/container/issues", 1392 | "source": "https://github.com/php-fig/container/tree/2.0.2" 1393 | }, 1394 | "time": "2021-11-05T16:47:00+00:00" 1395 | }, 1396 | { 1397 | "name": "psr/log", 1398 | "version": "2.0.0", 1399 | "source": { 1400 | "type": "git", 1401 | "url": "https://github.com/php-fig/log.git", 1402 | "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" 1403 | }, 1404 | "dist": { 1405 | "type": "zip", 1406 | "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", 1407 | "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", 1408 | "shasum": "" 1409 | }, 1410 | "require": { 1411 | "php": ">=8.0.0" 1412 | }, 1413 | "type": "library", 1414 | "extra": { 1415 | "branch-alias": { 1416 | "dev-master": "2.0.x-dev" 1417 | } 1418 | }, 1419 | "autoload": { 1420 | "psr-4": { 1421 | "Psr\\Log\\": "src" 1422 | } 1423 | }, 1424 | "notification-url": "https://packagist.org/downloads/", 1425 | "license": [ 1426 | "MIT" 1427 | ], 1428 | "authors": [ 1429 | { 1430 | "name": "PHP-FIG", 1431 | "homepage": "https://www.php-fig.org/" 1432 | } 1433 | ], 1434 | "description": "Common interface for logging libraries", 1435 | "homepage": "https://github.com/php-fig/log", 1436 | "keywords": [ 1437 | "log", 1438 | "psr", 1439 | "psr-3" 1440 | ], 1441 | "support": { 1442 | "source": "https://github.com/php-fig/log/tree/2.0.0" 1443 | }, 1444 | "time": "2021-07-14T16:41:46+00:00" 1445 | }, 1446 | { 1447 | "name": "sebastian/diff", 1448 | "version": "4.0.6", 1449 | "source": { 1450 | "type": "git", 1451 | "url": "https://github.com/sebastianbergmann/diff.git", 1452 | "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" 1453 | }, 1454 | "dist": { 1455 | "type": "zip", 1456 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", 1457 | "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", 1458 | "shasum": "" 1459 | }, 1460 | "require": { 1461 | "php": ">=7.3" 1462 | }, 1463 | "require-dev": { 1464 | "phpunit/phpunit": "^9.3", 1465 | "symfony/process": "^4.2 || ^5" 1466 | }, 1467 | "type": "library", 1468 | "extra": { 1469 | "branch-alias": { 1470 | "dev-master": "4.0-dev" 1471 | } 1472 | }, 1473 | "autoload": { 1474 | "classmap": [ 1475 | "src/" 1476 | ] 1477 | }, 1478 | "notification-url": "https://packagist.org/downloads/", 1479 | "license": [ 1480 | "BSD-3-Clause" 1481 | ], 1482 | "authors": [ 1483 | { 1484 | "name": "Sebastian Bergmann", 1485 | "email": "sebastian@phpunit.de" 1486 | }, 1487 | { 1488 | "name": "Kore Nordmann", 1489 | "email": "mail@kore-nordmann.de" 1490 | } 1491 | ], 1492 | "description": "Diff implementation", 1493 | "homepage": "https://github.com/sebastianbergmann/diff", 1494 | "keywords": [ 1495 | "diff", 1496 | "udiff", 1497 | "unidiff", 1498 | "unified diff" 1499 | ], 1500 | "support": { 1501 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1502 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" 1503 | }, 1504 | "funding": [ 1505 | { 1506 | "url": "https://github.com/sebastianbergmann", 1507 | "type": "github" 1508 | } 1509 | ], 1510 | "time": "2024-03-02T06:30:58+00:00" 1511 | }, 1512 | { 1513 | "name": "squizlabs/php_codesniffer", 1514 | "version": "3.10.2", 1515 | "source": { 1516 | "type": "git", 1517 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 1518 | "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" 1519 | }, 1520 | "dist": { 1521 | "type": "zip", 1522 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", 1523 | "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", 1524 | "shasum": "" 1525 | }, 1526 | "require": { 1527 | "ext-simplexml": "*", 1528 | "ext-tokenizer": "*", 1529 | "ext-xmlwriter": "*", 1530 | "php": ">=5.4.0" 1531 | }, 1532 | "require-dev": { 1533 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 1534 | }, 1535 | "bin": [ 1536 | "bin/phpcbf", 1537 | "bin/phpcs" 1538 | ], 1539 | "type": "library", 1540 | "extra": { 1541 | "branch-alias": { 1542 | "dev-master": "3.x-dev" 1543 | } 1544 | }, 1545 | "notification-url": "https://packagist.org/downloads/", 1546 | "license": [ 1547 | "BSD-3-Clause" 1548 | ], 1549 | "authors": [ 1550 | { 1551 | "name": "Greg Sherwood", 1552 | "role": "Former lead" 1553 | }, 1554 | { 1555 | "name": "Juliette Reinders Folmer", 1556 | "role": "Current lead" 1557 | }, 1558 | { 1559 | "name": "Contributors", 1560 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 1561 | } 1562 | ], 1563 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 1564 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 1565 | "keywords": [ 1566 | "phpcs", 1567 | "standards", 1568 | "static analysis" 1569 | ], 1570 | "support": { 1571 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 1572 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 1573 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 1574 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 1575 | }, 1576 | "funding": [ 1577 | { 1578 | "url": "https://github.com/PHPCSStandards", 1579 | "type": "github" 1580 | }, 1581 | { 1582 | "url": "https://github.com/jrfnl", 1583 | "type": "github" 1584 | }, 1585 | { 1586 | "url": "https://opencollective.com/php_codesniffer", 1587 | "type": "open_collective" 1588 | } 1589 | ], 1590 | "time": "2024-07-21T23:26:44+00:00" 1591 | }, 1592 | { 1593 | "name": "symfony/console", 1594 | "version": "v5.4.42", 1595 | "source": { 1596 | "type": "git", 1597 | "url": "https://github.com/symfony/console.git", 1598 | "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f" 1599 | }, 1600 | "dist": { 1601 | "type": "zip", 1602 | "url": "https://api.github.com/repos/symfony/console/zipball/cef62396a0477e94fc52e87a17c6e5c32e226b7f", 1603 | "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f", 1604 | "shasum": "" 1605 | }, 1606 | "require": { 1607 | "php": ">=7.2.5", 1608 | "symfony/deprecation-contracts": "^2.1|^3", 1609 | "symfony/polyfill-mbstring": "~1.0", 1610 | "symfony/polyfill-php73": "^1.9", 1611 | "symfony/polyfill-php80": "^1.16", 1612 | "symfony/service-contracts": "^1.1|^2|^3", 1613 | "symfony/string": "^5.1|^6.0" 1614 | }, 1615 | "conflict": { 1616 | "psr/log": ">=3", 1617 | "symfony/dependency-injection": "<4.4", 1618 | "symfony/dotenv": "<5.1", 1619 | "symfony/event-dispatcher": "<4.4", 1620 | "symfony/lock": "<4.4", 1621 | "symfony/process": "<4.4" 1622 | }, 1623 | "provide": { 1624 | "psr/log-implementation": "1.0|2.0" 1625 | }, 1626 | "require-dev": { 1627 | "psr/log": "^1|^2", 1628 | "symfony/config": "^4.4|^5.0|^6.0", 1629 | "symfony/dependency-injection": "^4.4|^5.0|^6.0", 1630 | "symfony/event-dispatcher": "^4.4|^5.0|^6.0", 1631 | "symfony/lock": "^4.4|^5.0|^6.0", 1632 | "symfony/process": "^4.4|^5.0|^6.0", 1633 | "symfony/var-dumper": "^4.4|^5.0|^6.0" 1634 | }, 1635 | "suggest": { 1636 | "psr/log": "For using the console logger", 1637 | "symfony/event-dispatcher": "", 1638 | "symfony/lock": "", 1639 | "symfony/process": "" 1640 | }, 1641 | "type": "library", 1642 | "autoload": { 1643 | "psr-4": { 1644 | "Symfony\\Component\\Console\\": "" 1645 | }, 1646 | "exclude-from-classmap": [ 1647 | "/Tests/" 1648 | ] 1649 | }, 1650 | "notification-url": "https://packagist.org/downloads/", 1651 | "license": [ 1652 | "MIT" 1653 | ], 1654 | "authors": [ 1655 | { 1656 | "name": "Fabien Potencier", 1657 | "email": "fabien@symfony.com" 1658 | }, 1659 | { 1660 | "name": "Symfony Community", 1661 | "homepage": "https://symfony.com/contributors" 1662 | } 1663 | ], 1664 | "description": "Eases the creation of beautiful and testable command line interfaces", 1665 | "homepage": "https://symfony.com", 1666 | "keywords": [ 1667 | "cli", 1668 | "command-line", 1669 | "console", 1670 | "terminal" 1671 | ], 1672 | "support": { 1673 | "source": "https://github.com/symfony/console/tree/v5.4.42" 1674 | }, 1675 | "funding": [ 1676 | { 1677 | "url": "https://symfony.com/sponsor", 1678 | "type": "custom" 1679 | }, 1680 | { 1681 | "url": "https://github.com/fabpot", 1682 | "type": "github" 1683 | }, 1684 | { 1685 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1686 | "type": "tidelift" 1687 | } 1688 | ], 1689 | "time": "2024-07-26T12:21:55+00:00" 1690 | }, 1691 | { 1692 | "name": "symfony/deprecation-contracts", 1693 | "version": "v3.5.0", 1694 | "source": { 1695 | "type": "git", 1696 | "url": "https://github.com/symfony/deprecation-contracts.git", 1697 | "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" 1698 | }, 1699 | "dist": { 1700 | "type": "zip", 1701 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", 1702 | "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", 1703 | "shasum": "" 1704 | }, 1705 | "require": { 1706 | "php": ">=8.1" 1707 | }, 1708 | "type": "library", 1709 | "extra": { 1710 | "branch-alias": { 1711 | "dev-main": "3.5-dev" 1712 | }, 1713 | "thanks": { 1714 | "name": "symfony/contracts", 1715 | "url": "https://github.com/symfony/contracts" 1716 | } 1717 | }, 1718 | "autoload": { 1719 | "files": [ 1720 | "function.php" 1721 | ] 1722 | }, 1723 | "notification-url": "https://packagist.org/downloads/", 1724 | "license": [ 1725 | "MIT" 1726 | ], 1727 | "authors": [ 1728 | { 1729 | "name": "Nicolas Grekas", 1730 | "email": "p@tchwork.com" 1731 | }, 1732 | { 1733 | "name": "Symfony Community", 1734 | "homepage": "https://symfony.com/contributors" 1735 | } 1736 | ], 1737 | "description": "A generic function and convention to trigger deprecation notices", 1738 | "homepage": "https://symfony.com", 1739 | "support": { 1740 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" 1741 | }, 1742 | "funding": [ 1743 | { 1744 | "url": "https://symfony.com/sponsor", 1745 | "type": "custom" 1746 | }, 1747 | { 1748 | "url": "https://github.com/fabpot", 1749 | "type": "github" 1750 | }, 1751 | { 1752 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1753 | "type": "tidelift" 1754 | } 1755 | ], 1756 | "time": "2024-04-18T09:32:20+00:00" 1757 | }, 1758 | { 1759 | "name": "symfony/polyfill-ctype", 1760 | "version": "v1.30.0", 1761 | "source": { 1762 | "type": "git", 1763 | "url": "https://github.com/symfony/polyfill-ctype.git", 1764 | "reference": "0424dff1c58f028c451efff2045f5d92410bd540" 1765 | }, 1766 | "dist": { 1767 | "type": "zip", 1768 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", 1769 | "reference": "0424dff1c58f028c451efff2045f5d92410bd540", 1770 | "shasum": "" 1771 | }, 1772 | "require": { 1773 | "php": ">=7.1" 1774 | }, 1775 | "provide": { 1776 | "ext-ctype": "*" 1777 | }, 1778 | "suggest": { 1779 | "ext-ctype": "For best performance" 1780 | }, 1781 | "type": "library", 1782 | "extra": { 1783 | "thanks": { 1784 | "name": "symfony/polyfill", 1785 | "url": "https://github.com/symfony/polyfill" 1786 | } 1787 | }, 1788 | "autoload": { 1789 | "files": [ 1790 | "bootstrap.php" 1791 | ], 1792 | "psr-4": { 1793 | "Symfony\\Polyfill\\Ctype\\": "" 1794 | } 1795 | }, 1796 | "notification-url": "https://packagist.org/downloads/", 1797 | "license": [ 1798 | "MIT" 1799 | ], 1800 | "authors": [ 1801 | { 1802 | "name": "Gert de Pagter", 1803 | "email": "BackEndTea@gmail.com" 1804 | }, 1805 | { 1806 | "name": "Symfony Community", 1807 | "homepage": "https://symfony.com/contributors" 1808 | } 1809 | ], 1810 | "description": "Symfony polyfill for ctype functions", 1811 | "homepage": "https://symfony.com", 1812 | "keywords": [ 1813 | "compatibility", 1814 | "ctype", 1815 | "polyfill", 1816 | "portable" 1817 | ], 1818 | "support": { 1819 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" 1820 | }, 1821 | "funding": [ 1822 | { 1823 | "url": "https://symfony.com/sponsor", 1824 | "type": "custom" 1825 | }, 1826 | { 1827 | "url": "https://github.com/fabpot", 1828 | "type": "github" 1829 | }, 1830 | { 1831 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1832 | "type": "tidelift" 1833 | } 1834 | ], 1835 | "time": "2024-05-31T15:07:36+00:00" 1836 | }, 1837 | { 1838 | "name": "symfony/polyfill-intl-grapheme", 1839 | "version": "v1.30.0", 1840 | "source": { 1841 | "type": "git", 1842 | "url": "https://github.com/symfony/polyfill-intl-grapheme.git", 1843 | "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" 1844 | }, 1845 | "dist": { 1846 | "type": "zip", 1847 | "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", 1848 | "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", 1849 | "shasum": "" 1850 | }, 1851 | "require": { 1852 | "php": ">=7.1" 1853 | }, 1854 | "suggest": { 1855 | "ext-intl": "For best performance" 1856 | }, 1857 | "type": "library", 1858 | "extra": { 1859 | "thanks": { 1860 | "name": "symfony/polyfill", 1861 | "url": "https://github.com/symfony/polyfill" 1862 | } 1863 | }, 1864 | "autoload": { 1865 | "files": [ 1866 | "bootstrap.php" 1867 | ], 1868 | "psr-4": { 1869 | "Symfony\\Polyfill\\Intl\\Grapheme\\": "" 1870 | } 1871 | }, 1872 | "notification-url": "https://packagist.org/downloads/", 1873 | "license": [ 1874 | "MIT" 1875 | ], 1876 | "authors": [ 1877 | { 1878 | "name": "Nicolas Grekas", 1879 | "email": "p@tchwork.com" 1880 | }, 1881 | { 1882 | "name": "Symfony Community", 1883 | "homepage": "https://symfony.com/contributors" 1884 | } 1885 | ], 1886 | "description": "Symfony polyfill for intl's grapheme_* functions", 1887 | "homepage": "https://symfony.com", 1888 | "keywords": [ 1889 | "compatibility", 1890 | "grapheme", 1891 | "intl", 1892 | "polyfill", 1893 | "portable", 1894 | "shim" 1895 | ], 1896 | "support": { 1897 | "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" 1898 | }, 1899 | "funding": [ 1900 | { 1901 | "url": "https://symfony.com/sponsor", 1902 | "type": "custom" 1903 | }, 1904 | { 1905 | "url": "https://github.com/fabpot", 1906 | "type": "github" 1907 | }, 1908 | { 1909 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1910 | "type": "tidelift" 1911 | } 1912 | ], 1913 | "time": "2024-05-31T15:07:36+00:00" 1914 | }, 1915 | { 1916 | "name": "symfony/polyfill-intl-normalizer", 1917 | "version": "v1.30.0", 1918 | "source": { 1919 | "type": "git", 1920 | "url": "https://github.com/symfony/polyfill-intl-normalizer.git", 1921 | "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" 1922 | }, 1923 | "dist": { 1924 | "type": "zip", 1925 | "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", 1926 | "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", 1927 | "shasum": "" 1928 | }, 1929 | "require": { 1930 | "php": ">=7.1" 1931 | }, 1932 | "suggest": { 1933 | "ext-intl": "For best performance" 1934 | }, 1935 | "type": "library", 1936 | "extra": { 1937 | "thanks": { 1938 | "name": "symfony/polyfill", 1939 | "url": "https://github.com/symfony/polyfill" 1940 | } 1941 | }, 1942 | "autoload": { 1943 | "files": [ 1944 | "bootstrap.php" 1945 | ], 1946 | "psr-4": { 1947 | "Symfony\\Polyfill\\Intl\\Normalizer\\": "" 1948 | }, 1949 | "classmap": [ 1950 | "Resources/stubs" 1951 | ] 1952 | }, 1953 | "notification-url": "https://packagist.org/downloads/", 1954 | "license": [ 1955 | "MIT" 1956 | ], 1957 | "authors": [ 1958 | { 1959 | "name": "Nicolas Grekas", 1960 | "email": "p@tchwork.com" 1961 | }, 1962 | { 1963 | "name": "Symfony Community", 1964 | "homepage": "https://symfony.com/contributors" 1965 | } 1966 | ], 1967 | "description": "Symfony polyfill for intl's Normalizer class and related functions", 1968 | "homepage": "https://symfony.com", 1969 | "keywords": [ 1970 | "compatibility", 1971 | "intl", 1972 | "normalizer", 1973 | "polyfill", 1974 | "portable", 1975 | "shim" 1976 | ], 1977 | "support": { 1978 | "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" 1979 | }, 1980 | "funding": [ 1981 | { 1982 | "url": "https://symfony.com/sponsor", 1983 | "type": "custom" 1984 | }, 1985 | { 1986 | "url": "https://github.com/fabpot", 1987 | "type": "github" 1988 | }, 1989 | { 1990 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1991 | "type": "tidelift" 1992 | } 1993 | ], 1994 | "time": "2024-05-31T15:07:36+00:00" 1995 | }, 1996 | { 1997 | "name": "symfony/polyfill-mbstring", 1998 | "version": "v1.30.0", 1999 | "source": { 2000 | "type": "git", 2001 | "url": "https://github.com/symfony/polyfill-mbstring.git", 2002 | "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" 2003 | }, 2004 | "dist": { 2005 | "type": "zip", 2006 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", 2007 | "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", 2008 | "shasum": "" 2009 | }, 2010 | "require": { 2011 | "php": ">=7.1" 2012 | }, 2013 | "provide": { 2014 | "ext-mbstring": "*" 2015 | }, 2016 | "suggest": { 2017 | "ext-mbstring": "For best performance" 2018 | }, 2019 | "type": "library", 2020 | "extra": { 2021 | "thanks": { 2022 | "name": "symfony/polyfill", 2023 | "url": "https://github.com/symfony/polyfill" 2024 | } 2025 | }, 2026 | "autoload": { 2027 | "files": [ 2028 | "bootstrap.php" 2029 | ], 2030 | "psr-4": { 2031 | "Symfony\\Polyfill\\Mbstring\\": "" 2032 | } 2033 | }, 2034 | "notification-url": "https://packagist.org/downloads/", 2035 | "license": [ 2036 | "MIT" 2037 | ], 2038 | "authors": [ 2039 | { 2040 | "name": "Nicolas Grekas", 2041 | "email": "p@tchwork.com" 2042 | }, 2043 | { 2044 | "name": "Symfony Community", 2045 | "homepage": "https://symfony.com/contributors" 2046 | } 2047 | ], 2048 | "description": "Symfony polyfill for the Mbstring extension", 2049 | "homepage": "https://symfony.com", 2050 | "keywords": [ 2051 | "compatibility", 2052 | "mbstring", 2053 | "polyfill", 2054 | "portable", 2055 | "shim" 2056 | ], 2057 | "support": { 2058 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" 2059 | }, 2060 | "funding": [ 2061 | { 2062 | "url": "https://symfony.com/sponsor", 2063 | "type": "custom" 2064 | }, 2065 | { 2066 | "url": "https://github.com/fabpot", 2067 | "type": "github" 2068 | }, 2069 | { 2070 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2071 | "type": "tidelift" 2072 | } 2073 | ], 2074 | "time": "2024-06-19T12:30:46+00:00" 2075 | }, 2076 | { 2077 | "name": "symfony/polyfill-php73", 2078 | "version": "v1.30.0", 2079 | "source": { 2080 | "type": "git", 2081 | "url": "https://github.com/symfony/polyfill-php73.git", 2082 | "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" 2083 | }, 2084 | "dist": { 2085 | "type": "zip", 2086 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", 2087 | "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", 2088 | "shasum": "" 2089 | }, 2090 | "require": { 2091 | "php": ">=7.1" 2092 | }, 2093 | "type": "library", 2094 | "extra": { 2095 | "thanks": { 2096 | "name": "symfony/polyfill", 2097 | "url": "https://github.com/symfony/polyfill" 2098 | } 2099 | }, 2100 | "autoload": { 2101 | "files": [ 2102 | "bootstrap.php" 2103 | ], 2104 | "psr-4": { 2105 | "Symfony\\Polyfill\\Php73\\": "" 2106 | }, 2107 | "classmap": [ 2108 | "Resources/stubs" 2109 | ] 2110 | }, 2111 | "notification-url": "https://packagist.org/downloads/", 2112 | "license": [ 2113 | "MIT" 2114 | ], 2115 | "authors": [ 2116 | { 2117 | "name": "Nicolas Grekas", 2118 | "email": "p@tchwork.com" 2119 | }, 2120 | { 2121 | "name": "Symfony Community", 2122 | "homepage": "https://symfony.com/contributors" 2123 | } 2124 | ], 2125 | "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", 2126 | "homepage": "https://symfony.com", 2127 | "keywords": [ 2128 | "compatibility", 2129 | "polyfill", 2130 | "portable", 2131 | "shim" 2132 | ], 2133 | "support": { 2134 | "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" 2135 | }, 2136 | "funding": [ 2137 | { 2138 | "url": "https://symfony.com/sponsor", 2139 | "type": "custom" 2140 | }, 2141 | { 2142 | "url": "https://github.com/fabpot", 2143 | "type": "github" 2144 | }, 2145 | { 2146 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2147 | "type": "tidelift" 2148 | } 2149 | ], 2150 | "time": "2024-05-31T15:07:36+00:00" 2151 | }, 2152 | { 2153 | "name": "symfony/polyfill-php80", 2154 | "version": "v1.30.0", 2155 | "source": { 2156 | "type": "git", 2157 | "url": "https://github.com/symfony/polyfill-php80.git", 2158 | "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" 2159 | }, 2160 | "dist": { 2161 | "type": "zip", 2162 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", 2163 | "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", 2164 | "shasum": "" 2165 | }, 2166 | "require": { 2167 | "php": ">=7.1" 2168 | }, 2169 | "type": "library", 2170 | "extra": { 2171 | "thanks": { 2172 | "name": "symfony/polyfill", 2173 | "url": "https://github.com/symfony/polyfill" 2174 | } 2175 | }, 2176 | "autoload": { 2177 | "files": [ 2178 | "bootstrap.php" 2179 | ], 2180 | "psr-4": { 2181 | "Symfony\\Polyfill\\Php80\\": "" 2182 | }, 2183 | "classmap": [ 2184 | "Resources/stubs" 2185 | ] 2186 | }, 2187 | "notification-url": "https://packagist.org/downloads/", 2188 | "license": [ 2189 | "MIT" 2190 | ], 2191 | "authors": [ 2192 | { 2193 | "name": "Ion Bazan", 2194 | "email": "ion.bazan@gmail.com" 2195 | }, 2196 | { 2197 | "name": "Nicolas Grekas", 2198 | "email": "p@tchwork.com" 2199 | }, 2200 | { 2201 | "name": "Symfony Community", 2202 | "homepage": "https://symfony.com/contributors" 2203 | } 2204 | ], 2205 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 2206 | "homepage": "https://symfony.com", 2207 | "keywords": [ 2208 | "compatibility", 2209 | "polyfill", 2210 | "portable", 2211 | "shim" 2212 | ], 2213 | "support": { 2214 | "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" 2215 | }, 2216 | "funding": [ 2217 | { 2218 | "url": "https://symfony.com/sponsor", 2219 | "type": "custom" 2220 | }, 2221 | { 2222 | "url": "https://github.com/fabpot", 2223 | "type": "github" 2224 | }, 2225 | { 2226 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2227 | "type": "tidelift" 2228 | } 2229 | ], 2230 | "time": "2024-05-31T15:07:36+00:00" 2231 | }, 2232 | { 2233 | "name": "symfony/service-contracts", 2234 | "version": "v3.5.0", 2235 | "source": { 2236 | "type": "git", 2237 | "url": "https://github.com/symfony/service-contracts.git", 2238 | "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" 2239 | }, 2240 | "dist": { 2241 | "type": "zip", 2242 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", 2243 | "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", 2244 | "shasum": "" 2245 | }, 2246 | "require": { 2247 | "php": ">=8.1", 2248 | "psr/container": "^1.1|^2.0", 2249 | "symfony/deprecation-contracts": "^2.5|^3" 2250 | }, 2251 | "conflict": { 2252 | "ext-psr": "<1.1|>=2" 2253 | }, 2254 | "type": "library", 2255 | "extra": { 2256 | "branch-alias": { 2257 | "dev-main": "3.5-dev" 2258 | }, 2259 | "thanks": { 2260 | "name": "symfony/contracts", 2261 | "url": "https://github.com/symfony/contracts" 2262 | } 2263 | }, 2264 | "autoload": { 2265 | "psr-4": { 2266 | "Symfony\\Contracts\\Service\\": "" 2267 | }, 2268 | "exclude-from-classmap": [ 2269 | "/Test/" 2270 | ] 2271 | }, 2272 | "notification-url": "https://packagist.org/downloads/", 2273 | "license": [ 2274 | "MIT" 2275 | ], 2276 | "authors": [ 2277 | { 2278 | "name": "Nicolas Grekas", 2279 | "email": "p@tchwork.com" 2280 | }, 2281 | { 2282 | "name": "Symfony Community", 2283 | "homepage": "https://symfony.com/contributors" 2284 | } 2285 | ], 2286 | "description": "Generic abstractions related to writing services", 2287 | "homepage": "https://symfony.com", 2288 | "keywords": [ 2289 | "abstractions", 2290 | "contracts", 2291 | "decoupling", 2292 | "interfaces", 2293 | "interoperability", 2294 | "standards" 2295 | ], 2296 | "support": { 2297 | "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" 2298 | }, 2299 | "funding": [ 2300 | { 2301 | "url": "https://symfony.com/sponsor", 2302 | "type": "custom" 2303 | }, 2304 | { 2305 | "url": "https://github.com/fabpot", 2306 | "type": "github" 2307 | }, 2308 | { 2309 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2310 | "type": "tidelift" 2311 | } 2312 | ], 2313 | "time": "2024-04-18T09:32:20+00:00" 2314 | }, 2315 | { 2316 | "name": "symfony/string", 2317 | "version": "v6.4.10", 2318 | "source": { 2319 | "type": "git", 2320 | "url": "https://github.com/symfony/string.git", 2321 | "reference": "ccf9b30251719567bfd46494138327522b9a9446" 2322 | }, 2323 | "dist": { 2324 | "type": "zip", 2325 | "url": "https://api.github.com/repos/symfony/string/zipball/ccf9b30251719567bfd46494138327522b9a9446", 2326 | "reference": "ccf9b30251719567bfd46494138327522b9a9446", 2327 | "shasum": "" 2328 | }, 2329 | "require": { 2330 | "php": ">=8.1", 2331 | "symfony/polyfill-ctype": "~1.8", 2332 | "symfony/polyfill-intl-grapheme": "~1.0", 2333 | "symfony/polyfill-intl-normalizer": "~1.0", 2334 | "symfony/polyfill-mbstring": "~1.0" 2335 | }, 2336 | "conflict": { 2337 | "symfony/translation-contracts": "<2.5" 2338 | }, 2339 | "require-dev": { 2340 | "symfony/error-handler": "^5.4|^6.0|^7.0", 2341 | "symfony/http-client": "^5.4|^6.0|^7.0", 2342 | "symfony/intl": "^6.2|^7.0", 2343 | "symfony/translation-contracts": "^2.5|^3.0", 2344 | "symfony/var-exporter": "^5.4|^6.0|^7.0" 2345 | }, 2346 | "type": "library", 2347 | "autoload": { 2348 | "files": [ 2349 | "Resources/functions.php" 2350 | ], 2351 | "psr-4": { 2352 | "Symfony\\Component\\String\\": "" 2353 | }, 2354 | "exclude-from-classmap": [ 2355 | "/Tests/" 2356 | ] 2357 | }, 2358 | "notification-url": "https://packagist.org/downloads/", 2359 | "license": [ 2360 | "MIT" 2361 | ], 2362 | "authors": [ 2363 | { 2364 | "name": "Nicolas Grekas", 2365 | "email": "p@tchwork.com" 2366 | }, 2367 | { 2368 | "name": "Symfony Community", 2369 | "homepage": "https://symfony.com/contributors" 2370 | } 2371 | ], 2372 | "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", 2373 | "homepage": "https://symfony.com", 2374 | "keywords": [ 2375 | "grapheme", 2376 | "i18n", 2377 | "string", 2378 | "unicode", 2379 | "utf-8", 2380 | "utf8" 2381 | ], 2382 | "support": { 2383 | "source": "https://github.com/symfony/string/tree/v6.4.10" 2384 | }, 2385 | "funding": [ 2386 | { 2387 | "url": "https://symfony.com/sponsor", 2388 | "type": "custom" 2389 | }, 2390 | { 2391 | "url": "https://github.com/fabpot", 2392 | "type": "github" 2393 | }, 2394 | { 2395 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2396 | "type": "tidelift" 2397 | } 2398 | ], 2399 | "time": "2024-07-22T10:21:14+00:00" 2400 | }, 2401 | { 2402 | "name": "vimeo/psalm", 2403 | "version": "4.30.0", 2404 | "source": { 2405 | "type": "git", 2406 | "url": "https://github.com/vimeo/psalm.git", 2407 | "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69" 2408 | }, 2409 | "dist": { 2410 | "type": "zip", 2411 | "url": "https://api.github.com/repos/vimeo/psalm/zipball/d0bc6e25d89f649e4f36a534f330f8bb4643dd69", 2412 | "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69", 2413 | "shasum": "" 2414 | }, 2415 | "require": { 2416 | "amphp/amp": "^2.4.2", 2417 | "amphp/byte-stream": "^1.5", 2418 | "composer/package-versions-deprecated": "^1.8.0", 2419 | "composer/semver": "^1.4 || ^2.0 || ^3.0", 2420 | "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", 2421 | "dnoegel/php-xdg-base-dir": "^0.1.1", 2422 | "ext-ctype": "*", 2423 | "ext-dom": "*", 2424 | "ext-json": "*", 2425 | "ext-libxml": "*", 2426 | "ext-mbstring": "*", 2427 | "ext-simplexml": "*", 2428 | "ext-tokenizer": "*", 2429 | "felixfbecker/advanced-json-rpc": "^3.0.3", 2430 | "felixfbecker/language-server-protocol": "^1.5", 2431 | "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", 2432 | "nikic/php-parser": "^4.13", 2433 | "openlss/lib-array2xml": "^1.0", 2434 | "php": "^7.1|^8", 2435 | "sebastian/diff": "^3.0 || ^4.0", 2436 | "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", 2437 | "symfony/polyfill-php80": "^1.25", 2438 | "webmozart/path-util": "^2.3" 2439 | }, 2440 | "provide": { 2441 | "psalm/psalm": "self.version" 2442 | }, 2443 | "require-dev": { 2444 | "bamarni/composer-bin-plugin": "^1.2", 2445 | "brianium/paratest": "^4.0||^6.0", 2446 | "ext-curl": "*", 2447 | "php-parallel-lint/php-parallel-lint": "^1.2", 2448 | "phpdocumentor/reflection-docblock": "^5", 2449 | "phpmyadmin/sql-parser": "5.1.0||dev-master", 2450 | "phpspec/prophecy": ">=1.9.0", 2451 | "phpstan/phpdoc-parser": "1.2.* || 1.6.4", 2452 | "phpunit/phpunit": "^9.0", 2453 | "psalm/plugin-phpunit": "^0.16", 2454 | "slevomat/coding-standard": "^7.0", 2455 | "squizlabs/php_codesniffer": "^3.5", 2456 | "symfony/process": "^4.3 || ^5.0 || ^6.0", 2457 | "weirdan/prophecy-shim": "^1.0 || ^2.0" 2458 | }, 2459 | "suggest": { 2460 | "ext-curl": "In order to send data to shepherd", 2461 | "ext-igbinary": "^2.0.5 is required, used to serialize caching data" 2462 | }, 2463 | "bin": [ 2464 | "psalm", 2465 | "psalm-language-server", 2466 | "psalm-plugin", 2467 | "psalm-refactor", 2468 | "psalter" 2469 | ], 2470 | "type": "library", 2471 | "extra": { 2472 | "branch-alias": { 2473 | "dev-master": "4.x-dev", 2474 | "dev-3.x": "3.x-dev", 2475 | "dev-2.x": "2.x-dev", 2476 | "dev-1.x": "1.x-dev" 2477 | } 2478 | }, 2479 | "autoload": { 2480 | "files": [ 2481 | "src/functions.php", 2482 | "src/spl_object_id.php" 2483 | ], 2484 | "psr-4": { 2485 | "Psalm\\": "src/Psalm/" 2486 | } 2487 | }, 2488 | "notification-url": "https://packagist.org/downloads/", 2489 | "license": [ 2490 | "MIT" 2491 | ], 2492 | "authors": [ 2493 | { 2494 | "name": "Matthew Brown" 2495 | } 2496 | ], 2497 | "description": "A static analysis tool for finding errors in PHP applications", 2498 | "keywords": [ 2499 | "code", 2500 | "inspection", 2501 | "php" 2502 | ], 2503 | "support": { 2504 | "issues": "https://github.com/vimeo/psalm/issues", 2505 | "source": "https://github.com/vimeo/psalm/tree/4.30.0" 2506 | }, 2507 | "time": "2022-11-06T20:37:08+00:00" 2508 | }, 2509 | { 2510 | "name": "webmozart/assert", 2511 | "version": "1.11.0", 2512 | "source": { 2513 | "type": "git", 2514 | "url": "https://github.com/webmozarts/assert.git", 2515 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" 2516 | }, 2517 | "dist": { 2518 | "type": "zip", 2519 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", 2520 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", 2521 | "shasum": "" 2522 | }, 2523 | "require": { 2524 | "ext-ctype": "*", 2525 | "php": "^7.2 || ^8.0" 2526 | }, 2527 | "conflict": { 2528 | "phpstan/phpstan": "<0.12.20", 2529 | "vimeo/psalm": "<4.6.1 || 4.6.2" 2530 | }, 2531 | "require-dev": { 2532 | "phpunit/phpunit": "^8.5.13" 2533 | }, 2534 | "type": "library", 2535 | "extra": { 2536 | "branch-alias": { 2537 | "dev-master": "1.10-dev" 2538 | } 2539 | }, 2540 | "autoload": { 2541 | "psr-4": { 2542 | "Webmozart\\Assert\\": "src/" 2543 | } 2544 | }, 2545 | "notification-url": "https://packagist.org/downloads/", 2546 | "license": [ 2547 | "MIT" 2548 | ], 2549 | "authors": [ 2550 | { 2551 | "name": "Bernhard Schussek", 2552 | "email": "bschussek@gmail.com" 2553 | } 2554 | ], 2555 | "description": "Assertions to validate method input/output with nice error messages.", 2556 | "keywords": [ 2557 | "assert", 2558 | "check", 2559 | "validate" 2560 | ], 2561 | "support": { 2562 | "issues": "https://github.com/webmozarts/assert/issues", 2563 | "source": "https://github.com/webmozarts/assert/tree/1.11.0" 2564 | }, 2565 | "time": "2022-06-03T18:03:27+00:00" 2566 | }, 2567 | { 2568 | "name": "webmozart/glob", 2569 | "version": "4.7.0", 2570 | "source": { 2571 | "type": "git", 2572 | "url": "https://github.com/webmozarts/glob.git", 2573 | "reference": "8a2842112d6916e61e0e15e316465b611f3abc17" 2574 | }, 2575 | "dist": { 2576 | "type": "zip", 2577 | "url": "https://api.github.com/repos/webmozarts/glob/zipball/8a2842112d6916e61e0e15e316465b611f3abc17", 2578 | "reference": "8a2842112d6916e61e0e15e316465b611f3abc17", 2579 | "shasum": "" 2580 | }, 2581 | "require": { 2582 | "php": "^7.3 || ^8.0.0" 2583 | }, 2584 | "require-dev": { 2585 | "phpunit/phpunit": "^9.5", 2586 | "symfony/filesystem": "^5.3" 2587 | }, 2588 | "type": "library", 2589 | "extra": { 2590 | "branch-alias": { 2591 | "dev-master": "4.1-dev" 2592 | } 2593 | }, 2594 | "autoload": { 2595 | "psr-4": { 2596 | "Webmozart\\Glob\\": "src/" 2597 | } 2598 | }, 2599 | "notification-url": "https://packagist.org/downloads/", 2600 | "license": [ 2601 | "MIT" 2602 | ], 2603 | "authors": [ 2604 | { 2605 | "name": "Bernhard Schussek", 2606 | "email": "bschussek@gmail.com" 2607 | } 2608 | ], 2609 | "description": "A PHP implementation of Ant's glob.", 2610 | "support": { 2611 | "issues": "https://github.com/webmozarts/glob/issues", 2612 | "source": "https://github.com/webmozarts/glob/tree/4.7.0" 2613 | }, 2614 | "time": "2024-03-07T20:33:40+00:00" 2615 | }, 2616 | { 2617 | "name": "webmozart/path-util", 2618 | "version": "2.3.0", 2619 | "source": { 2620 | "type": "git", 2621 | "url": "https://github.com/webmozart/path-util.git", 2622 | "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" 2623 | }, 2624 | "dist": { 2625 | "type": "zip", 2626 | "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", 2627 | "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", 2628 | "shasum": "" 2629 | }, 2630 | "require": { 2631 | "php": ">=5.3.3", 2632 | "webmozart/assert": "~1.0" 2633 | }, 2634 | "require-dev": { 2635 | "phpunit/phpunit": "^4.6", 2636 | "sebastian/version": "^1.0.1" 2637 | }, 2638 | "type": "library", 2639 | "extra": { 2640 | "branch-alias": { 2641 | "dev-master": "2.3-dev" 2642 | } 2643 | }, 2644 | "autoload": { 2645 | "psr-4": { 2646 | "Webmozart\\PathUtil\\": "src/" 2647 | } 2648 | }, 2649 | "notification-url": "https://packagist.org/downloads/", 2650 | "license": [ 2651 | "MIT" 2652 | ], 2653 | "authors": [ 2654 | { 2655 | "name": "Bernhard Schussek", 2656 | "email": "bschussek@gmail.com" 2657 | } 2658 | ], 2659 | "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", 2660 | "support": { 2661 | "issues": "https://github.com/webmozart/path-util/issues", 2662 | "source": "https://github.com/webmozart/path-util/tree/2.3.0" 2663 | }, 2664 | "abandoned": "symfony/filesystem", 2665 | "time": "2015-12-17T08:42:14+00:00" 2666 | } 2667 | ], 2668 | "aliases": [], 2669 | "minimum-stability": "stable", 2670 | "stability-flags": { 2671 | "giorgiosironi/eris": 5 2672 | }, 2673 | "prefer-stable": false, 2674 | "prefer-lowest": false, 2675 | "platform": { 2676 | "php": "^8.1" 2677 | }, 2678 | "platform-dev": [], 2679 | "plugin-api-version": "2.1.0" 2680 | } 2681 | -------------------------------------------------------------------------------- /crc-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbol-whitelist": [ 3 | "null", "bool", "self", "true", "false", "callable", "array" 4 | ], 5 | "php-core-extensions" : [ 6 | "Core", 7 | "date", 8 | "pcre", 9 | "Phar", 10 | "Reflection", 11 | "SPL", 12 | "standard" 13 | ], 14 | "scan-files" : [] 15 | } 16 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | src 15 | spec 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /spec/ToBeEither.php: -------------------------------------------------------------------------------- 1 | eval( 15 | fn($actualLeft) => $expected->eval( 16 | fn($expectedLeft) => $actualLeft === $expectedLeft, 17 | fn($_) => false 18 | ), 19 | fn($actualRight) => $expected->eval( 20 | fn($_) => false, 21 | fn($expectedRight) => $actualRight === $expectedRight 22 | ) 23 | ); 24 | } 25 | 26 | public static function description() 27 | { 28 | return "strict check on the content of Either"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spec/UseCaseValidationSpec.php: -------------------------------------------------------------------------------- 1 | */ 17 | private static array $dateType = []; 18 | 19 | public static function isFollowBelgiumRule1(string $tin): bool 20 | { 21 | $divisionRemainderBy97 = (int) (substr($tin, 0, 9)) % 97; 22 | 23 | return 97 - $divisionRemainderBy97 === (int) (substr($tin, 9, 3)); 24 | } 25 | 26 | public static function isFollowBelgiumRule2(string $tin): bool 27 | { 28 | $divisionRemainderBy97 = (int) ('2' . substr($tin, 0, 9)) % 97; 29 | 30 | return 97 - $divisionRemainderBy97 === (int) (substr($tin, 9, 3)); 31 | } 32 | 33 | public static function getDateType(string $tin): int 34 | { 35 | if (array_key_exists($tin, self::$dateType)) { 36 | return self::$dateType[$tin]; 37 | } 38 | 39 | $year = (int) (substr($tin, 0, 2)); 40 | $month = (int) (substr($tin, 2, 2)); 41 | $day = (int) (substr($tin, 4, 2)); 42 | 43 | $y1 = checkdate($month, $day, 1900 + $year); 44 | $y2 = checkdate($month, $day, 2000 + $year); 45 | 46 | if (0 === $day || 0 === $month || ($y1 && $y2)) { 47 | $dateType = 3; 48 | } elseif ($y1) { 49 | $dateType = 1; 50 | } elseif ($y2) { 51 | $dateType = 2; 52 | } else { 53 | $dateType = 0; 54 | } 55 | 56 | self::$dateType[$tin] = $dateType; 57 | 58 | return $dateType; 59 | } 60 | 61 | public static function hasValidDateType(string $tin): bool 62 | { 63 | return 0 !== self::getDateType($tin); 64 | } 65 | 66 | public static function followRule1(string $tin): bool 67 | { 68 | $dateType = self::getDateType($tin); 69 | 70 | return self::isFollowBelgiumRule1($tin) && (1 === $dateType || 3 === $dateType); 71 | } 72 | 73 | public static function followRule2(string $tin): bool 74 | { 75 | $dateType = self::getDateType($tin); 76 | 77 | return self::isFollowBelgiumRule2($tin) && 2 <= $dateType; 78 | } 79 | 80 | public static function hasValidLength(string $tin): bool 81 | { 82 | return 11 === strlen($tin); 83 | } 84 | 85 | public static function hasValidPattern(string $tin): bool 86 | { 87 | $pattern = '\\d{2}[0-1]\\d[0-3]\\d{6}'; 88 | 89 | return 1 === preg_match(sprintf('/%s/', $pattern), $tin); 90 | } 91 | 92 | public static function belgian(): Validation 93 | { 94 | $r = V::isString(['TIN type must be a string.']) 95 | ->then(V::satisfies([TinValidator::class, 'hasValidLength'], ['TIN length is invalid.'])) 96 | ->then(V::satisfies([TinValidator::class, 'hasValidPattern'], ['TIN pattern is invalid.'])) 97 | ->then(V::satisfies([TinValidator::class, 'hasValidDateType'], ['TIN date is invalid.'])); 98 | 99 | // Branch 1 100 | $br1 = V::satisfies([TinValidator::class, 'followRule1'], ['TIN validation of rule 1 failed.']); 101 | // Branch 2 102 | $br2 = V::satisfies([TinValidator::class, 'followRule2'], ['TIN validation of rule 2 failed.']); 103 | 104 | return $r->then($br1->or(new ConcatenationMonoid(), $br2)); 105 | } 106 | } 107 | 108 | describe('Use Case Validation Spec', function () { 109 | describe('Validate Belgian TIN numbers', function () { 110 | $testCases = function (): Generator { 111 | yield ['Invalidate TIN number having wrong type', 123456, Either::left(['TIN type must be a string.'])]; 112 | yield ['Invalidate TIN number having invalid length', '0123456789', Either::left(['TIN length is invalid.'])]; 113 | yield ['Invalidate TIN number having invalid pattern', 'wwwwwwwwwww', Either::left(['TIN pattern is invalid.'])]; 114 | yield ['Invalidate TIN number having invalid date', '81023011101', Either::left(['TIN date is invalid.'])]; 115 | yield ['Validate TIN number', '01062624339', Either::right('01062624339')]; 116 | yield ['Invalidate TIN number', '81092499999', Either::left(['TIN validation of rule 1 failed.', 'TIN validation of rule 2 failed.'])]; 117 | }; 118 | 119 | foreach ($testCases() as list($testCase, $tinNumber, $expected)) { 120 | it($testCase, fn() => expect(TinValidator::Belgian()->validate($tinNumber))->toEqual($expected)); 121 | } 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /spec/UserValidatorSpec.php: -------------------------------------------------------------------------------- 1 | name = $name; 39 | $this->age = $age; 40 | } 41 | 42 | /** 43 | * @param array{name: non-empty-string, age: positive-int} $rawData 44 | */ 45 | public static function fromRawData(array $rawData): self 46 | { 47 | return new self($rawData['name'], $rawData['age']); 48 | } 49 | } 50 | 51 | /* 52 | * and we receive from somewhere some raw data like 53 | * 54 | * [ 55 | * 'name' => $name, 56 | * 'age' => $age 57 | * ] 58 | * 59 | * that we need to validate and parse into our User object. 60 | * 61 | * We basically need to write a validator of type Validator, where string[] represents our 62 | * failure type. 63 | * 64 | * To do this, we need lenses to focus an array of type array{name: A, age: B} to its fields name of type A and age of 65 | * type B 66 | */ 67 | 68 | $nameLens = Lens::lens( 69 | fn(array $rawUser) => $rawUser['name'], 70 | function (array $rawUser, $newName) { 71 | $rawUser['name'] = $newName; 72 | 73 | return $rawUser; 74 | } 75 | ); 76 | 77 | $ageLens = Lens::lens( 78 | fn(array $rawUser) => $rawUser['age'], 79 | function (array $rawUser, $newAge) { 80 | $rawUser['age'] = $newAge; 81 | 82 | return $rawUser; 83 | } 84 | ); 85 | 86 | /* 87 | * Now we can write our validator. Let's build it up step by step. 88 | * 89 | * First we want validators for non-empty strings and for positive integers. 90 | * 91 | * They both check whether the input is of the correct type and then perform the further check. 92 | */ 93 | 94 | /** @var V $nonEmptyString */ 95 | $nonEmptyString = V::isString(['name is not a string'])->then(V::nonEmptyString(['name is empty'])); 96 | 97 | /** @var V $positiveInteger */ 98 | $positiveInteger = V::isInteger(['age is not an integer']) 99 | ->then(V::satisfies(fn(int $i) => $i > 0, ['age is not a positive integer'])); 100 | 101 | /* 102 | * Next we want to use these validators to build new validators which check whether an array has a "name" key containing 103 | * a non-empty string and has an "age" key containing a positive integer 104 | * 105 | * First we need to check whether the array contains the key. Once we know that, we can focus on the value contained at 106 | * that key and use the validators defined above. 107 | */ 108 | 109 | /** @var V $validation */ 110 | $hasNameKeyContainingNonEmptyString = V::hasKey('name', ['missing "name" key'])->then( 111 | V::focus( 112 | $nameLens, 113 | $nonEmptyString 114 | ) 115 | ); 116 | 117 | /** @var V $hasAgeKeyContainingPositiveInteger */ 118 | $hasAgeKeyContainingPositiveInteger = V::hasKey('age', ['missing "age" key'])->then( 119 | V::focus( 120 | $ageLens, 121 | $positiveInteger 122 | ) 123 | ); 124 | 125 | /* 126 | * Now we have all the ingredients to write the complete validator. 127 | * 128 | * First we check that the input is actually an array. 129 | * Then we check that both fields are present and contain correct data using the validators defined above; if both 130 | * validation fail we keep track of both error messages using the ConcatenationMonoid with just merges the lists of 131 | * errors. 132 | * Last we map the result into the `User` object. 133 | */ 134 | 135 | /** @var V $validation */ 136 | $validation = 137 | V::isArray(['not an array'])->then( 138 | V::all( 139 | new ConcatenationMonoid(), 140 | new FirstSemigroup(), 141 | [ 142 | $hasNameKeyContainingNonEmptyString, 143 | $hasAgeKeyContainingPositiveInteger 144 | ] 145 | ) 146 | )->rmap([User::class, 'fromRawData']); 147 | 148 | /* 149 | * Now we can check that the validator actually behaves how we expect 150 | */ 151 | 152 | describe('UserValidation', function () use ($test, $validation) { 153 | it('succeeds if it receives valid input', function () use ($test, $validation) { 154 | $test->forAll( 155 | new AssociativeArrayGenerator([ 156 | 'name' => new SuchThatGenerator( 157 | fn(string $s) => $s !== '', 158 | new StringGenerator() 159 | ), 160 | 'age' => new SuchThatGenerator( 161 | fn(int $i) => $i > 0, 162 | new IntegerGenerator() 163 | ) 164 | ]) 165 | )->then(function (array $rawData) use ($validation) { 166 | expect($validation->validate($rawData))->toEqual(Either::right(new User($rawData['name'], $rawData['age']))); 167 | }); 168 | }); 169 | 170 | it('fails if the input is not an array', function () use ($test, $validation) { 171 | $test->forAll( 172 | new IntegerGenerator() 173 | )->then(function (int $i) use ($validation) { 174 | expect($validation->validate($i))->toEqual(Either::left(['not an array'])); 175 | }); 176 | }); 177 | 178 | it('fails if it is an array but does not contain any of the required keys', function () use ($test, $validation) { 179 | $test->forAll( 180 | new SequenceGenerator(new IntegerGenerator()) 181 | )->then(function (array $a) use ($validation) { 182 | expect($validation->validate($a))->toEqual(Either::left([ 183 | 'missing "name" key', 184 | 'missing "age" key' 185 | ])); 186 | }); 187 | }); 188 | 189 | it('fails if it is an array but does not contain the "name" key', function () use ($test, $validation) { 190 | $test->forAll( 191 | new AssociativeArrayGenerator([ 192 | 'age' => new SuchThatGenerator( 193 | fn(int $i) => $i > 0, 194 | new IntegerGenerator() 195 | ) 196 | ]) 197 | )->then(function (array $a) use ($validation) { 198 | expect($validation->validate($a))->toEqual(Either::left([ 199 | 'missing "name" key' 200 | ])); 201 | }); 202 | }); 203 | 204 | it('fails if it is an array but does not contain the "age" key', function () use ($test, $validation) { 205 | $test->forAll( 206 | new AssociativeArrayGenerator([ 207 | 'name' => new SuchThatGenerator( 208 | fn(string $s) => $s !== '', 209 | new StringGenerator() 210 | ) 211 | ]) 212 | )->then(function (array $a) use ($validation) { 213 | expect($validation->validate($a))->toEqual(Either::left([ 214 | 'missing "age" key' 215 | ])); 216 | }); 217 | }); 218 | 219 | it('fails if name is not a string', function () use ($test, $validation) { 220 | $test->forAll( 221 | new AssociativeArrayGenerator([ 222 | 'name' => new IntegerGenerator(), 223 | 'age' => new SuchThatGenerator( 224 | fn(int $i) => $i > 0, 225 | new IntegerGenerator() 226 | ) 227 | ]) 228 | )->then(function (array $rawData) use ($validation) { 229 | expect($validation->validate($rawData))->toEqual(Either::left(['name is not a string'])); 230 | }); 231 | }); 232 | 233 | it('fails if name is not a non-empty string', function () use ($test, $validation) { 234 | $test->forAll( 235 | new AssociativeArrayGenerator([ 236 | 'name' => '', 237 | 'age' => new SuchThatGenerator( 238 | fn(int $i) => $i > 0, 239 | new IntegerGenerator() 240 | ) 241 | ]) 242 | )->then(function (array $rawData) use ($validation) { 243 | expect($validation->validate($rawData))->toEqual(Either::left(['name is empty'])); 244 | }); 245 | }); 246 | 247 | it('fails if age is not an integer', function () use ($test, $validation) { 248 | $test->forAll( 249 | new AssociativeArrayGenerator([ 250 | 'name' => new SuchThatGenerator( 251 | fn(string $s) => $s !== '', 252 | new StringGenerator() 253 | ), 254 | 'age' => new StringGenerator() 255 | ]) 256 | )->then(function (array $rawData) use ($validation) { 257 | expect($validation->validate($rawData))->toEqual(Either::left(['age is not an integer'])); 258 | }); 259 | }); 260 | 261 | it('fails if name is not a non-empty string', function () use ($test, $validation) { 262 | $test->forAll( 263 | new AssociativeArrayGenerator([ 264 | 'name' => new SuchThatGenerator( 265 | fn(string $s) => $s !== '', 266 | new StringGenerator() 267 | ), 268 | 'age' => new SuchThatGenerator( 269 | fn(int $i) => $i <= 0, 270 | new IntegerGenerator() 271 | ) 272 | ]) 273 | )->then(function (array $rawData) use ($validation) { 274 | expect($validation->validate($rawData))->toEqual(Either::left(['age is not a positive integer'])); 275 | }); 276 | }); 277 | }); 278 | -------------------------------------------------------------------------------- /spec/ValidationSpec.php: -------------------------------------------------------------------------------- 1 | forAll( 40 | new IntegerGenerator() 41 | )->then( 42 | function (int $i) { 43 | expect(V::valid()->validate($i))->toBeEither(Either::right($i)); 44 | } 45 | ); 46 | }); 47 | }); 48 | 49 | describe('invalid', function () use ($test) { 50 | it('always fails', function () use ($test) { 51 | $test->forAll( 52 | new IntegerGenerator() 53 | )->then( 54 | function (int $i) { 55 | expect(V::invalid('nope')->validate($i))->toBeEither(Either::left('nope')); 56 | } 57 | ); 58 | }); 59 | }); 60 | }); 61 | 62 | describe('Then', function () { 63 | it('fails if the first validation fails', function () { 64 | $validation1 = V::invalid('nope'); 65 | $validation2 = V::valid(); 66 | 67 | expect($validation1->then($validation2)->validate(42))->toBeEither(Either::left('nope')); 68 | }); 69 | 70 | it('fails if the second validation fails', function () { 71 | $validation1 = V::valid(); 72 | $validation2 = V::invalid('nope'); 73 | 74 | expect($validation1->then($validation2)->validate(42))->toBeEither(Either::left('nope')); 75 | }); 76 | 77 | it('succeeds if both validation succeed', function () { 78 | $validation1 = V::valid(); 79 | $validation2 = V::valid(); 80 | 81 | expect($validation1->then($validation2)->validate(42))->toBeEither(Either::right(42)); 82 | }); 83 | }); 84 | 85 | describe('Or', function () { 86 | it('fails if both validations fail combining the errors', function () { 87 | $validation1 = V::invalid('nope'); 88 | $validation2 = V::invalid('epon'); 89 | 90 | expect($validation1->or(new StringConcatenationMonoid(), $validation2)->validate(42)) 91 | ->toBeEither(Either::left('nopeepon')); 92 | }); 93 | 94 | it('succeeds if the first validation succeeds', function () { 95 | $validation1 = V::valid(); 96 | $validation2 = V::invalid('nope'); 97 | 98 | expect($validation1->or(new StringConcatenationMonoid(), $validation2)->validate(42)) 99 | ->toBeEither(Either::right(42)); 100 | }); 101 | 102 | it('succeeds if the second validation succeeds', function () { 103 | $validation1 = V::invalid('nope'); 104 | $validation2 = V::valid(); 105 | 106 | expect($validation1->or(new StringConcatenationMonoid(), $validation2)->validate(42)) 107 | ->toBeEither(Either::right(42)); 108 | }); 109 | 110 | it('succeeds if both validations succeed', function () { 111 | $validation1 = V::valid(); 112 | $validation2 = V::valid(); 113 | 114 | expect($validation1->or(new StringConcatenationMonoid(), $validation2)->validate(42)) 115 | ->toBeEither(Either::right(42)); 116 | }); 117 | }); 118 | 119 | describe('And', function () { 120 | it('fails if both validations fail combining the errors', function () { 121 | $validation1 = V::invalid('nope'); 122 | $validation2 = V::invalid('epon'); 123 | 124 | expect($validation1->and(new StringConcatenationMonoid(), $validation2)->validate(42)) 125 | ->toBeEither(Either::left('nopeepon')); 126 | }); 127 | 128 | it('fails if the second validation fails', function () { 129 | $validation1 = V::valid(); 130 | $validation2 = V::invalid('nope'); 131 | 132 | expect($validation1->and(new StringConcatenationMonoid(), $validation2)->validate(42)) 133 | ->toBeEither(Either::left('nope')); 134 | }); 135 | 136 | it('fails if the first validation fails', function () { 137 | $validation1 = V::invalid('nope'); 138 | $validation2 = V::valid(); 139 | 140 | expect($validation1->and(new StringConcatenationMonoid(), $validation2)->validate(42)) 141 | ->toBeEither(Either::left('nope')); 142 | }); 143 | 144 | it('succeeds if both validations succeed', function () { 145 | $validation1 = V::valid(); 146 | $validation2 = V::valid(); 147 | 148 | expect($validation1->and(new StringConcatenationMonoid(), $validation2)->validate(42)) 149 | ->toBeEither(Either::right(42)); 150 | }); 151 | }); 152 | 153 | describe('Basic validators', function () use ($test) { 154 | 155 | describe('is', function () use ($test) { 156 | it('suceeds if the input corresponds to the value', function () use ($test) { 157 | $test->forAll( 158 | new IntegerGenerator() 159 | )->then( 160 | function (int $i) { 161 | expect(V::is($i, 'nope')->validate($i))->toBeEither(Either::right($i)); 162 | } 163 | ); 164 | }); 165 | 166 | it('fails comparing a string and an integer', function () use ($test) { 167 | $test->forAll( 168 | new IntegerGenerator(), 169 | new StringGenerator() 170 | )->then( 171 | function (int $i, string $s) { 172 | expect(V::is($i, 'nope')->validate($s))->toBeEither(Either::left('nope')); 173 | } 174 | ); 175 | }); 176 | 177 | it('fails comparing two different integers', function () use ($test) { 178 | $test->forAll( 179 | new BindGenerator( 180 | new IntegerGenerator(), 181 | function (int $i) { 182 | return new TupleGenerator([ 183 | new ConstantGenerator($i), 184 | new SuchThatGenerator( 185 | fn (int $j) => $j != $i, 186 | new IntegerGenerator() 187 | ) 188 | ]); 189 | } 190 | ) 191 | )->then(function ($tuple) { 192 | expect(V::is($tuple[0], 'nope')->validate($tuple[1]))->toBeEither(Either::left('nope')); 193 | }); 194 | }); 195 | }); 196 | 197 | describe('hasKey', function () use ($test) { 198 | it('succeeds if the array has the key', function () use ($test) { 199 | $test->forAll( 200 | new SequenceGenerator(new IntegerGenerator()) 201 | )->then( 202 | function (array $a) { 203 | $a[42] = 42; 204 | 205 | expect(V::hasKey(42, 'nope')->validate($a))->toBeEither(Either::right($a)); 206 | } 207 | ); 208 | }); 209 | 210 | it('fails if the array does not have the key', function () use ($test) { 211 | $test->forAll( 212 | new SequenceGenerator(new IntegerGenerator()) 213 | )->then( 214 | function (array $a) { 215 | unset($a[3]); 216 | 217 | expect(V::hasKey(3, 'nope')->validate($a))->toBeEither(Either::left('nope')); 218 | } 219 | ); 220 | }); 221 | }); 222 | 223 | describe('isArray', function () use ($test) { 224 | it('always succeeds for arrays', function () use ($test) { 225 | $test->forAll( 226 | new SequenceGenerator(new IntegerGenerator()) 227 | )->then( 228 | function (array $a) { 229 | expect(V::isArray('nope')->validate($a))->toBeEither(Either::right($a)); 230 | } 231 | ); 232 | }); 233 | 234 | it('always fails for integers', function () use ($test) { 235 | $test->forAll( 236 | new IntegerGenerator() 237 | )->then( 238 | function (int $i) { 239 | expect(V::isArray('nope')->validate($i))->toBeEither(Either::left('nope')); 240 | } 241 | ); 242 | }); 243 | }); 244 | 245 | describe('isBool', function () use ($test) { 246 | it('always succeeds for booleans', function () use ($test) { 247 | $test->forAll( 248 | new BooleanGenerator() 249 | )->then( 250 | function (bool $b) { 251 | expect(V::isBool('nope')->validate($b))->toBeEither(Either::right($b)); 252 | } 253 | ); 254 | }); 255 | 256 | it('always fails for integers', function () use ($test) { 257 | $test->forAll( 258 | new IntegerGenerator() 259 | )->then( 260 | function (int $i) { 261 | expect(V::isBool('nope')->validate($i))->toBeEither(Either::left('nope')); 262 | } 263 | ); 264 | }); 265 | }); 266 | 267 | describe('isDate', function () use ($test) { 268 | it('always succeeds for dates', function () use ($test) { 269 | $test->forAll( 270 | new DateGenerator( 271 | new DateTime("@0"), 272 | new DateTime("@" . ((2 ** 31) - 1)) 273 | ) 274 | )->then( 275 | function (DateTime $date) { 276 | expect(V::isDate(fn ($e) => $e)->validate($date->format('Y:m:d H:i:s'))) 277 | ->toEqual(Either::right(DateTimeImmutable::createFromMutable($date))); 278 | } 279 | ); 280 | }); 281 | 282 | it('always fails for invalid dates', function () use ($test) { 283 | $test->forAll( 284 | new SuchThatGenerator( 285 | function (string $s) { 286 | try { 287 | new DateTimeImmutable($s); 288 | 289 | return false; 290 | } catch (Exception $e) { 291 | return true; 292 | } 293 | }, 294 | new StringGenerator() 295 | ) 296 | )->then( 297 | function (string $i) { 298 | expect(V::isDate(fn ($_) => 'nope')->validate($i))->toBeEither(Either::left('nope')); 299 | } 300 | ); 301 | }); 302 | }); 303 | 304 | describe('isFloat', function () use ($test) { 305 | it('always succeeds for floats', function () use ($test) { 306 | $test->forAll( 307 | new FloatGenerator() 308 | )->then( 309 | function (float $i) { 310 | expect(V::isFloat('nope')->validate($i))->toBeEither(Either::right($i)); 311 | } 312 | ); 313 | }); 314 | 315 | it('always fails for strings', function () use ($test) { 316 | $test->forAll( 317 | new StringGenerator() 318 | )->then( 319 | function (string $s) { 320 | expect(V::isFloat('nope')->validate($s))->toBeEither(Either::left('nope')); 321 | } 322 | ); 323 | }); 324 | }); 325 | 326 | describe('isInteger', function () use ($test) { 327 | it('always succeeds for integers', function () use ($test) { 328 | $test->forAll( 329 | new IntegerGenerator() 330 | )->then( 331 | function (int $i) { 332 | expect(V::isInteger('nope')->validate($i))->toBeEither(Either::right($i)); 333 | } 334 | ); 335 | }); 336 | 337 | it('always fails for strings', function () use ($test) { 338 | $test->forAll( 339 | new StringGenerator() 340 | )->then( 341 | function (string $s) { 342 | expect(V::isInteger('nope')->validate($s))->toBeEither(Either::left('nope')); 343 | } 344 | ); 345 | }); 346 | }); 347 | 348 | describe('isList', function () use ($test) { 349 | it('always succeeds for lists', function () use ($test) { 350 | $test->forAll( 351 | new SequenceGenerator(new IntegerGenerator()) 352 | )->then( 353 | function (array $a) { 354 | expect(V::isList('nope')->validate($a))->toBeEither(Either::right($a)); 355 | } 356 | ); 357 | }); 358 | 359 | it('always fails for arrays with non integer keys', function () use ($test) { 360 | $test->forAll( 361 | new SuchThatGenerator( 362 | fn(string $s) => !is_numeric($s), 363 | new StringGenerator() 364 | ), 365 | new SequenceGenerator(new IntegerGenerator()) 366 | )->then( 367 | function (string $s, array $a) { 368 | $a[$s] = 0; 369 | 370 | expect(V::isList('nope')->validate($a))->toBeEither(Either::left('nope')); 371 | } 372 | ); 373 | }); 374 | 375 | it('always fails for arrays with non consecutive keys', function () use ($test) { 376 | $test->forAll( 377 | new SuchThatGenerator( 378 | fn (array $a) => !empty($a), 379 | new SequenceGenerator(new IntegerGenerator()) 380 | ) 381 | )->then( 382 | function (array $a) { 383 | $maxKey = max(array_keys($a)); 384 | $a[$maxKey + 2] = 0; 385 | 386 | expect(V::isList('nope')->validate($a))->toBeEither(Either::left('nope')); 387 | } 388 | ); 389 | }); 390 | }); 391 | 392 | describe('isNull', function () use ($test) { 393 | it('always succeeds for null', function () { 394 | expect(V::isNull('nope')->validate(null))->toBeEither(Either::right(null)); 395 | }); 396 | 397 | it('always fails for integers', function () use ($test) { 398 | $test->forAll( 399 | new IntegerGenerator() 400 | )->then( 401 | function (int $i) { 402 | expect(V::isNull('nope')->validate($i))->toBeEither(Either::left('nope')); 403 | } 404 | ); 405 | }); 406 | }); 407 | 408 | describe('isString', function () use ($test) { 409 | it('always succeeds for strings', function () use ($test) { 410 | $test->forAll( 411 | new StringGenerator() 412 | )->then( 413 | function (string $a) { 414 | expect(V::isString('nope')->validate($a))->toBeEither(Either::right($a)); 415 | } 416 | ); 417 | }); 418 | 419 | it('always fails for integers', function () use ($test) { 420 | $test->forAll( 421 | new IntegerGenerator() 422 | )->then( 423 | function (int $i) { 424 | expect(V::isString('nope')->validate($i))->toBeEither(Either::left('nope')); 425 | } 426 | ); 427 | }); 428 | }); 429 | 430 | describe('notEmptyArray', function () use ($test) { 431 | it('always succeeds for non-empty arrays', function () use ($test) { 432 | $test->forAll( 433 | new SuchThatGenerator( 434 | fn(array $s) => !empty($s), 435 | new SequenceGenerator(new IntegerGenerator()) 436 | ) 437 | )->then( 438 | function (array $s) { 439 | expect(V::nonEmptyArray('nope')->validate(($s)))->toBeEither(Either::right($s)); 440 | } 441 | ); 442 | }); 443 | 444 | it('fails for the empty array', function () { 445 | expect(V::nonEmptyArray('nope')->validate(([])))->toBeEither(Either::left('nope')); 446 | }); 447 | }); 448 | 449 | describe('notEmptyString', function () use ($test) { 450 | it('always succeeds for non-empty strings', function () use ($test) { 451 | $test->forAll( 452 | new SuchThatGenerator( 453 | fn(string $s) => $s !== '', 454 | new StringGenerator() 455 | ) 456 | )->then( 457 | function (string $s) { 458 | expect(V::nonEmptyString('nope')->validate(($s)))->toBeEither(Either::right($s)); 459 | } 460 | ); 461 | }); 462 | 463 | it('fails for the empty string', function () { 464 | expect(V::nonEmptyString('nope')->validate(('')))->toBeEither(Either::left('nope')); 465 | }); 466 | }); 467 | }); 468 | 469 | describe('Combinators', function () use ($test) { 470 | 471 | describe('all', function () use ($test) { 472 | $all = V::all( 473 | new ConcatenationMonoid(), 474 | new FirstSemigroup(), 475 | [ 476 | V::satisfies(fn(int $i) => $i % 2 === 0, ['not multiple of 2']), 477 | V::satisfies(fn(int $i) => $i % 3 === 0, ['not multiple of 3']) 478 | ] 479 | ); 480 | 481 | it('succeeds if every validator succeeds', function () use ($test, $all) { 482 | $test->forAll( 483 | new IntegerGenerator() 484 | )->then( 485 | function (int $i) use ($all) { 486 | expect($all->validate($i * 6))->toBeEither(Either::right($i * 6)); 487 | } 488 | ); 489 | }); 490 | 491 | it('fails if one of the validators fails', function () use ($test, $all) { 492 | $test->forAll( 493 | new SuchThatGenerator( 494 | function (int $i) { 495 | return $i % 3 !== 0; 496 | }, 497 | new IntegerGenerator() 498 | ) 499 | )->then( 500 | function (int $i) use ($all) { 501 | expect($all->validate($i * 2))->toBeEither(Either::left(['not multiple of 3'])); 502 | } 503 | ); 504 | }); 505 | 506 | it('fails if all validator fails combining the errors', function () use ($test, $all) { 507 | $test->forAll( 508 | new SuchThatGenerator( 509 | function (int $i) { 510 | return $i % 3 !== 0 && $i % 2 !== 0; 511 | }, 512 | new IntegerGenerator() 513 | ) 514 | )->then( 515 | function (int $i) use ($all) { 516 | expect($all->validate($i))->toBeEither(Either::left(['not multiple of 2', 'not multiple of 3'])); 517 | } 518 | ); 519 | }); 520 | }); 521 | 522 | describe('any', function () use ($test) { 523 | $any = V::any( 524 | new ConcatenationMonoid(), 525 | new FirstSemigroup(), 526 | [ 527 | V::satisfies(fn(int $i) => $i % 2 === 0, ['not multiple of 2']), 528 | V::satisfies(fn(int $i) => $i % 3 === 0, ['not multiple of 3']) 529 | ] 530 | ); 531 | 532 | it('succeeds if every validator succeeds', function () use ($test, $any) { 533 | $test->forAll( 534 | new IntegerGenerator() 535 | )->then( 536 | function (int $i) use ($any) { 537 | expect($any->validate($i * 6))->toBeEither(Either::right($i * 6)); 538 | } 539 | ); 540 | }); 541 | 542 | it('succeeds if one of the validators succeeds', function () use ($test, $any) { 543 | $test->forAll( 544 | new SuchThatGenerator( 545 | function (int $i) { 546 | return $i % 3 !== 0; 547 | }, 548 | new IntegerGenerator() 549 | ) 550 | )->then( 551 | function (int $i) use ($any) { 552 | expect($any->validate($i * 2))->toBeEither(Either::right($i * 2)); 553 | } 554 | ); 555 | }); 556 | 557 | it('fails if any validator fails combining the errors', function () use ($test, $any) { 558 | $test->forAll( 559 | new SuchThatGenerator( 560 | function (int $i) { 561 | return $i % 3 !== 0 && $i % 2 !== 0; 562 | }, 563 | new IntegerGenerator() 564 | ) 565 | )->then( 566 | function (int $i) use ($any) { 567 | expect($any->validate($i))->toBeEither(Either::left(['not multiple of 2', 'not multiple of 3'])); 568 | } 569 | ); 570 | }); 571 | }); 572 | 573 | describe('anyElement', function () use ($test) { 574 | it('fails it the validation fails for every element', function () use ($test) { 575 | $test->forAll( 576 | new SequenceGenerator(new IntegerGenerator()) 577 | )->then( 578 | function (array $a) { 579 | $anyElement = V::anyElement(V::invalid('nope'), 'nope nope'); 580 | 581 | expect($anyElement->validate($a))->toBeEither(Either::left('nope nope')); 582 | } 583 | ); 584 | }); 585 | 586 | it('succeeds it the validation succeeds for at least one element', function () use ($test) { 587 | $test->forAll( 588 | new SequenceGenerator(new IntegerGenerator()) 589 | )->then( 590 | function (array $a) { 591 | $anyElement = V::anyElement(V::satisfies(fn($x) => $x === 42, 'nope'), 'nope nope'); 592 | 593 | $a[] = 42; 594 | 595 | expect($anyElement->validate($a))->toBeEither(Either::right($a)); 596 | } 597 | ); 598 | }); 599 | }); 600 | 601 | describe('associativeArray', function () use ($test) { 602 | $validation = V::associativeArray( 603 | [ 604 | 'int' => V::isInteger(['int is not an integer']), 605 | 'string' => V::isString(['string is not a string']) 606 | ], 607 | ['not an array'], 608 | new ConcatenationMonoid(), 609 | fn($key) => [sprintf('key %s is missing', $key)], 610 | fn($key, $error) => [$key => $error] 611 | ); 612 | 613 | it('succeeds when the validation of every field succeeds', function () use ($test, $validation) { 614 | $test->forAll( 615 | new AssociativeArrayGenerator([ 616 | 'int' => new IntegerGenerator(), 617 | 'string' => new StringGenerator() 618 | ]) 619 | )->then(function (array $a) use ($validation) { 620 | expect($validation->validate($a))->toBeEither(Either::right($a)); 621 | }); 622 | }); 623 | 624 | it('fails when the input is not an array', function () use ($test, $validation) { 625 | $test->forAll( 626 | new IntegerGenerator() 627 | )->then(function (int $i) use ($validation) { 628 | expect($validation->validate($i))->toBeEither(Either::left(['not an array'])); 629 | }); 630 | }); 631 | 632 | it('fails when a key is missing', function () use ($test, $validation) { 633 | $test->forAll( 634 | new AssociativeArrayGenerator([ 635 | 'int' => new IntegerGenerator() 636 | ]) 637 | )->then(function (array $a) use ($validation) { 638 | expect($validation->validate($a))->toBeEither(Either::left(['key string is missing'])); 639 | }); 640 | }); 641 | 642 | it('fails when a key contains a value which does not pass validation', function () use ($test, $validation) { 643 | $test->forAll( 644 | new AssociativeArrayGenerator([ 645 | 'int' => new IntegerGenerator(), 646 | 'string' => new IntegerGenerator() 647 | ]) 648 | )->then(function (array $a) use ($validation) { 649 | expect($validation->validate($a))->toBeEither(Either::left(['string' => ['string is not a string']])); 650 | }); 651 | }); 652 | 653 | it('preserves the type changing validations at the field level', function () use ($test) { 654 | $validation = V::associativeArray( 655 | [ 656 | 'aaa' => V::valid(), 657 | 'foo' => new V(fn(int $i) => Either::right((string)$i)), 658 | 'bar' => new V(fn(int $i) => Either::right((float)$i)), 659 | 'baz' => V::isInteger('not an integer'), 660 | 'gaf' => V::isString('not a string') 661 | ], 662 | ['not an array'], 663 | new ConcatenationMonoid(), 664 | fn($key) => [sprintf('key %s is missing', $key)], 665 | fn($key, $error) => [$key => $error] 666 | ); 667 | 668 | $data = [ 669 | 'aaa' => null, 670 | 'foo' => 42, 671 | 'bar' => 37, 672 | 'baz' => 29, 673 | 'gaf' => 'a string' 674 | ]; 675 | 676 | expect($validation->validate($data)) 677 | ->toBeEither(Either::right([ 678 | 'aaa' => null, 679 | 'foo' => '42', 680 | 'bar' => (float)37, 681 | 'baz' => 29, 682 | 'gaf' => 'a string' 683 | ])); 684 | }); 685 | }); 686 | 687 | describe('everyElement', function () use ($test) { 688 | it('fails if the validation fails for at least one element', function () use ($test) { 689 | $test->forAll( 690 | new SequenceGenerator(new IntegerGenerator()) 691 | )->then( 692 | function (array $a) { 693 | $shouldNotContain42 = V::everyElement( 694 | new FirstSemigroup(), 695 | V::satisfies(fn(int $x) => $x !== 42, ['not 42']) 696 | ); 697 | 698 | $a[] = 42; 699 | 700 | expect($shouldNotContain42->validate($a))->toBeEither(Either::left(['not 42'])); 701 | } 702 | ); 703 | }); 704 | 705 | it('succeeds if the validation succeeds for every element', function () use ($test) { 706 | $test->forAll( 707 | new SequenceGenerator(new IntegerGenerator()) 708 | )->then(function (array $a) { 709 | $everyElementIsFine = V::everyElement( 710 | new FirstSemigroup(), 711 | V::valid() 712 | ); 713 | 714 | expect($everyElementIsFine->validate($a))->toBeEither(Either::right($a)); 715 | }); 716 | }); 717 | }); 718 | 719 | describe('focus', function () use ($test) { 720 | /** @var Lens $lens */ 721 | $lens = Lens::lens( 722 | /** 723 | * @param array{foo: string} $a 724 | * @return string 725 | */ 726 | fn (array $a) => $a['foo'], 727 | /** 728 | * @param array{foo: string} $a 729 | * @return array{foo: int} 730 | */ 731 | function (array $a, int $b): array { 732 | $a['foo'] = $b; 733 | 734 | return $a; 735 | } 736 | ); 737 | 738 | it('fails if the validation on the focus fails', function () use ($lens) { 739 | $validation = V::invalid('nope'); 740 | 741 | expect(V::focus($lens, $validation)->validate(['foo' => 'a string'])) 742 | ->toBeEither(Either::left('nope')); 743 | }); 744 | 745 | it('succeeds if the validation on the focus succeeds', function () use ($lens) { 746 | $validation = new V(fn(string $s) => Either::right(strlen($s))); 747 | 748 | expect(V::focus($lens, $validation)->validate(['foo' => 'a string'])) 749 | ->toBeEither(Either::right(['foo' => 8])); 750 | }); 751 | }); 752 | 753 | describe('nullable', function () use ($test) { 754 | $validation = V::nullable(new ConcatenationMonoid(), ['not null'], V::isInteger(['not an integer'])); 755 | 756 | it('fails if the value does not satisfy the validation', function () use ($test, $validation) { 757 | $test->forAll( 758 | new StringGenerator() 759 | )->then(function (string $s) use ($validation) { 760 | expect($validation->validate($s))->toBeEither(Either::left(['not an integer', 'not null'])); 761 | }); 762 | }); 763 | 764 | it('succeeds if the value satisfies the validation', function () use ($test, $validation) { 765 | $test->forAll( 766 | new IntegerGenerator() 767 | )->then(function (int $i) use ($validation) { 768 | expect($validation->validate($i))->toBeEither(Either::right($i)); 769 | }); 770 | }); 771 | 772 | it('succeeds if the value is null', function () use ($validation) { 773 | expect($validation->validate(null))->toBeEither(Either::right(null)); 774 | }); 775 | }); 776 | }); 777 | }); 778 | -------------------------------------------------------------------------------- /spec/ValidationTest.php: -------------------------------------------------------------------------------- 1 | seedingRandomNumberGeneration(); 17 | $this->listeners = array_filter( 18 | $this->listeners, 19 | function ($listener) { 20 | return !($listener instanceof MinimumEvaluations); 21 | } 22 | ); 23 | $this->withRand('rand'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Brand/ValidationBrand.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class ValidationBrand implements Brand 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Brand/ValidationBrand2.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class ValidationBrand2 implements Brand 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Instances/Validation/AllMonoid.php: -------------------------------------------------------------------------------- 1 | > 18 | * 19 | * @psalm-immutable 20 | */ 21 | final class AllMonoid implements Monoid 22 | { 23 | /** @var Semigroup */ 24 | private $eSemigroup; 25 | 26 | /** @var Semigroup */ 27 | private $bSemigroup; 28 | 29 | /** 30 | * @param Semigroup $eSemigroup 31 | * @param Semigroup $bSemigroup 32 | */ 33 | public function __construct(Semigroup $eSemigroup, Semigroup $bSemigroup) 34 | { 35 | $this->eSemigroup = $eSemigroup; 36 | $this->bSemigroup = $bSemigroup; 37 | } 38 | 39 | /** 40 | * @return Validation 41 | */ 42 | public function mempty(): Validation 43 | { 44 | return Validation::valid(); 45 | } 46 | 47 | /** 48 | * @param Validation $a 49 | * @param Validation $b 50 | * @return Validation 51 | */ 52 | public function append($a, $b): Validation 53 | { 54 | return (new ValidationSemigroup(new JoinEitherSemigroup($this->eSemigroup, $this->bSemigroup)))->append($a, $b); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Instances/Validation/AnyMonoid.php: -------------------------------------------------------------------------------- 1 | > 18 | * 19 | * @psalm-immutable 20 | */ 21 | final class AnyMonoid implements Monoid 22 | { 23 | /** @var Monoid */ 24 | private Monoid $eMonoid; 25 | 26 | /** @var Semigroup */ 27 | private Semigroup $bSemigroup; 28 | 29 | /** 30 | * @param Monoid $eMonoid 31 | */ 32 | public function __construct(Monoid $eMonoid, Semigroup $bSemigroup) 33 | { 34 | $this->eMonoid = $eMonoid; 35 | $this->bSemigroup = $bSemigroup; 36 | } 37 | 38 | /** 39 | * @return Validation 40 | */ 41 | public function mempty(): Validation 42 | { 43 | return Validation::invalid($this->eMonoid->mempty()); 44 | } 45 | 46 | /** 47 | * @param Validation $a 48 | * @param Validation $b 49 | * @return Validation 50 | */ 51 | public function append($a, $b): Validation 52 | { 53 | return (new ValidationSemigroup(new MeetEitherSemigroup($this->eMonoid, $this->bSemigroup)))->append($a, $b); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationAlt.php: -------------------------------------------------------------------------------- 1 | > 19 | * 20 | * @psalm-immutable 21 | */ 22 | final class ValidationAlt implements Alt 23 | { 24 | /** @var Semigroup */ 25 | private Semigroup $eSemigroup; 26 | 27 | /** 28 | * @param Semigroup $eSemigroup 29 | */ 30 | public function __construct(Semigroup $eSemigroup) 31 | { 32 | $this->eSemigroup = $eSemigroup; 33 | } 34 | 35 | /** 36 | * @template A 37 | * @template B 38 | * @param callable(A): B $f 39 | * @param HK1, A> $a 40 | * @return Validation 41 | * 42 | * @psalm-pure 43 | * 44 | * @psalm-suppress ImplementedReturnTypeMismatch 45 | */ 46 | public function map(callable $f, HK1 $a): Validation 47 | { 48 | return (new ValidationFunctor())->map($f, $a); 49 | } 50 | 51 | /** 52 | * @template A 53 | * @param HK1, A> $a 54 | * @param HK1, A> $b 55 | * @return Validation 56 | * 57 | * @psalm-suppress ImplementedReturnTypeMismatch 58 | */ 59 | public function alt($a, $b): Validation 60 | { 61 | $aValidation = Validation::fromBrand($a); 62 | $bValidation = Validation::fromBrand($b); 63 | 64 | return new Validation( 65 | /** 66 | * @param C $c 67 | * @return Either 68 | * 69 | * @psalm-suppress ArgumentTypeCoercion 70 | */ 71 | fn($c) => (new EitherAlt($this->eSemigroup))->alt( 72 | $aValidation->validate($c), 73 | $bValidation->validate($c) 74 | ) 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationAlternative.php: -------------------------------------------------------------------------------- 1 | > 20 | * 21 | * @psalm-immutable 22 | */ 23 | final class ValidationAlternative implements Alternative 24 | { 25 | /** @var Monoid */ 26 | private Monoid $eMonoid; 27 | 28 | /** 29 | * @param Monoid $eMonoid 30 | */ 31 | public function __construct(Monoid $eMonoid) 32 | { 33 | $this->eMonoid = $eMonoid; 34 | } 35 | 36 | /** 37 | * @template A 38 | * @template B 39 | * @param callable(A): B $f 40 | * @param HK1, A> $a 41 | * @return Validation 42 | * 43 | * @psalm-pure 44 | * 45 | * @psalm-suppress ImplementedReturnTypeMismatch 46 | */ 47 | public function map(callable $f, HK1 $a): Validation 48 | { 49 | return (new ValidationFunctor())->map($f, $a); 50 | } 51 | 52 | /** 53 | * @template A 54 | * @template B 55 | * @param HK1, callable(A): B> $f 56 | * @param HK1, A> $a 57 | * @return Validation 58 | * 59 | * @psalm-suppress ImplementedReturnTypeMismatch 60 | */ 61 | public function apply(HK1 $f, HK1 $a): Validation 62 | { 63 | return (new ValidationApply($this->eMonoid))->apply($f, $a); 64 | } 65 | 66 | /** 67 | * @template A 68 | * @param A $a 69 | * @return Validation 70 | * 71 | * @psalm-pure 72 | * 73 | * @psalm-suppress ImplementedReturnTypeMismatch 74 | */ 75 | public function pure($a): Validation 76 | { 77 | return new Validation( 78 | /** 79 | * @param C $_ 80 | * @return Either 81 | */ 82 | fn($_) => (new EitherApplicative())->pure($a) 83 | ); 84 | } 85 | 86 | /** 87 | * @template A 88 | * @return Validation 89 | * 90 | * @psalm-suppress ImplementedReturnTypeMismatch 91 | */ 92 | public function empty(): Validation 93 | { 94 | return (new ValidationPlus($this->eMonoid))->empty(); 95 | } 96 | 97 | /** 98 | * @template A 99 | * @param HK1, A> $a 100 | * @param HK1, A> $b 101 | * @return Validation 102 | * 103 | * @psalm-suppress ImplementedReturnTypeMismatch 104 | */ 105 | public function alt($a, $b): Validation 106 | { 107 | return (new ValidationAlt($this->eMonoid))->alt($a, $b); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationApplicative.php: -------------------------------------------------------------------------------- 1 | > 19 | * 20 | * @psalm-immutable 21 | */ 22 | final class ValidationApplicative implements Applicative 23 | { 24 | /** @var Semigroup */ 25 | private Semigroup $eSemigroup; 26 | 27 | /** 28 | * @param Semigroup $eSemigroup 29 | */ 30 | public function __construct(Semigroup $eSemigroup) 31 | { 32 | $this->eSemigroup = $eSemigroup; 33 | } 34 | 35 | /** 36 | * @template A 37 | * @template B 38 | * @param callable(A): B $f 39 | * @param HK1, A> $a 40 | * @return Validation 41 | * 42 | * @psalm-pure 43 | * 44 | * @psalm-suppress ImplementedReturnTypeMismatch 45 | */ 46 | public function map(callable $f, HK1 $a): Validation 47 | { 48 | return (new ValidationFunctor())->map($f, $a); 49 | } 50 | 51 | /** 52 | * @template A 53 | * @template B 54 | * @param HK1, callable(A): B> $f 55 | * @param HK1, A> $a 56 | * @return Validation 57 | * 58 | * @psalm-suppress ImplementedReturnTypeMismatch 59 | */ 60 | public function apply(HK1 $f, HK1 $a): Validation 61 | { 62 | return (new ValidationApply($this->eSemigroup))->apply($f, $a); 63 | } 64 | 65 | /** 66 | * @template A 67 | * @param A $a 68 | * @return Validation 69 | * 70 | * @psalm-pure 71 | * 72 | * @psalm-suppress ImplementedReturnTypeMismatch 73 | */ 74 | public function pure($a): Validation 75 | { 76 | return new Validation( 77 | /** 78 | * @param C $_ 79 | * @return Either 80 | */ 81 | fn($_) => (new EitherApplicative())->pure($a) 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationApply.php: -------------------------------------------------------------------------------- 1 | > 19 | * 20 | * @psalm-immutable 21 | */ 22 | final class ValidationApply implements Apply 23 | { 24 | /** @var Semigroup */ 25 | private Semigroup $eSemigroup; 26 | 27 | /** 28 | * @param Semigroup $eSemigroup 29 | */ 30 | public function __construct(Semigroup $eSemigroup) 31 | { 32 | $this->eSemigroup = $eSemigroup; 33 | } 34 | 35 | /** 36 | * @template A 37 | * @template B 38 | * @param callable(A): B $f 39 | * @param HK1, A> $a 40 | * @return Validation 41 | * 42 | * @psalm-pure 43 | * 44 | * @psalm-suppress ImplementedReturnTypeMismatch 45 | */ 46 | public function map(callable $f, HK1 $a): Validation 47 | { 48 | return (new ValidationFunctor())->map($f, $a); 49 | } 50 | 51 | /** 52 | * @template A 53 | * @template B 54 | * @param HK1, callable(A): B> $f 55 | * @param HK1, A> $a 56 | * @return Validation 57 | * 58 | * @psalm-pure 59 | * 60 | * @psalm-suppress ImplementedReturnTypeMismatch 61 | */ 62 | public function apply(HK1 $f, HK1 $a): Validation 63 | { 64 | $fValidation = Validation::fromBrand($f); 65 | $aValidation = Validation::fromBrand($a); 66 | 67 | return new Validation( 68 | /** 69 | * @param C $c 70 | * @return Either 71 | * 72 | * @psalm-suppress ArgumentTypeCoercion 73 | */ 74 | fn($c) => (new EitherValidationApplicative($this->eSemigroup))->apply( 75 | $fValidation->validate($c), 76 | $aValidation->validate($c) 77 | ) 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationFunctor.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * @psalm-immutable 17 | */ 18 | final class ValidationFunctor implements Functor 19 | { 20 | /** 21 | * @template A 22 | * @template B 23 | * @template C 24 | * @template E 25 | * @param callable(A): B $f 26 | * @param HK1, A> $a 27 | * @return Validation 28 | * 29 | * @psalm-pure 30 | */ 31 | public function map(callable $f, HK1 $a): Validation 32 | { 33 | $aValidation = Validation::fromBrand($a); 34 | 35 | return new Validation( 36 | /** 37 | * @param C $c 38 | * @return Either 39 | */ 40 | fn($c) => $aValidation->validate($c)->map($f) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationPlus.php: -------------------------------------------------------------------------------- 1 | > 17 | * 18 | * @psalm-immutable 19 | */ 20 | class ValidationPlus implements Plus 21 | { 22 | /** @var Monoid */ 23 | private Monoid $eMonoid; 24 | 25 | /** 26 | * @param Monoid $eMonoid 27 | */ 28 | public function __construct(Monoid $eMonoid) 29 | { 30 | $this->eMonoid = $eMonoid; 31 | } 32 | 33 | /** 34 | * @template A 35 | * @template B 36 | * @param callable(A): B $f 37 | * @param HK1, A> $a 38 | * @return Validation 39 | * 40 | * @psalm-pure 41 | * 42 | * @psalm-suppress ImplementedReturnTypeMismatch 43 | */ 44 | public function map(callable $f, HK1 $a): Validation 45 | { 46 | return (new ValidationFunctor())->map($f, $a); 47 | } 48 | 49 | /** 50 | * @template A 51 | * @return Validation 52 | * 53 | * @psalm-suppress ImplementedReturnTypeMismatch 54 | */ 55 | public function empty(): Validation 56 | { 57 | return Validation::invalid($this->eMonoid->mempty()); 58 | } 59 | 60 | /** 61 | * @template A 62 | * @param HK1, A> $a 63 | * @param HK1, A> $b 64 | * @return Validation 65 | * 66 | * @psalm-suppress ImplementedReturnTypeMismatch 67 | */ 68 | public function alt($a, $b): Validation 69 | { 70 | return (new ValidationAlt($this->eMonoid))->alt($a, $b); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationProfunctor.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * @psalm-immutable 17 | */ 18 | final class ValidationProfunctor implements Profunctor 19 | { 20 | /** 21 | * @template A 22 | * @template B 23 | * @template C 24 | * @template D 25 | * @template E 26 | * @param callable(A): B $f 27 | * @param callable(C): D $g 28 | * @param HK2, B, C> $a 29 | * @return Validation 30 | */ 31 | public function diMap(callable $f, callable $g, HK2 $a): Validation 32 | { 33 | $validation = Validation::fromBrand2($a); 34 | 35 | return new Validation( 36 | /** 37 | * @param A $a 38 | * @return Either 39 | */ 40 | fn($a) => $validation->validate($f($a))->map($g) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Instances/Validation/ValidationSemigroup.php: -------------------------------------------------------------------------------- 1 | > 16 | * 17 | * @psalm-immutable 18 | */ 19 | final class ValidationSemigroup implements Semigroup 20 | { 21 | /** @var Semigroup> */ 22 | private $eitherSemigroup; 23 | 24 | /** 25 | * @param Semigroup> $eitherSemigroup 26 | */ 27 | public function __construct(Semigroup $eitherSemigroup) 28 | { 29 | $this->eitherSemigroup = $eitherSemigroup; 30 | } 31 | 32 | /** 33 | * @param Validation $a 34 | * @param Validation $b 35 | * @return Validation 36 | * 37 | * @psalm-pure 38 | */ 39 | public function append($a, $b): Validation 40 | { 41 | return new Validation( 42 | /** 43 | * @param A $c 44 | * @return Either 45 | */ 46 | function ($c) use ($a, $b) { 47 | return $this->eitherSemigroup->append( 48 | $a->validate($c), 49 | $b->validate($c) 50 | ); 51 | } 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Validation.php: -------------------------------------------------------------------------------- 1 | 37 | * 38 | * @template A raw input to the validation 39 | * @template-covariant E potential validation error 40 | * @template-covariant B parsed validation result 41 | * 42 | * @implements DefaultProfunctor, A, B> 43 | * @implements HK1, B> 44 | * 45 | * @psalm-immutable 46 | */ 47 | final class Validation implements DefaultProfunctor, HK1 48 | { 49 | /** @var callable(A): Either */ 50 | private $validation; 51 | 52 | /** 53 | * @param callable(A): Either $validation 54 | */ 55 | public function __construct(callable $validation) 56 | { 57 | $this->validation = $validation; 58 | } 59 | 60 | /** 61 | * @template C 62 | * @template D 63 | * @template F 64 | * @param HK1, D> $hk 65 | * @return Validation 66 | * 67 | * @psalm-pure 68 | */ 69 | public static function fromBrand(HK1 $hk): self 70 | { 71 | /** @var Validation */ 72 | return $hk; 73 | } 74 | 75 | /** 76 | * @template C 77 | * @template D 78 | * @template F 79 | * @param HK2, C, D> $hk 80 | * @return Validation 81 | * 82 | * @psalm-pure 83 | */ 84 | public static function fromBrand2(HK2 $hk): self 85 | { 86 | /** @var Validation */ 87 | return $hk; 88 | } 89 | 90 | // PROFUNCTOR INSTANCE 91 | 92 | /** 93 | * @template C 94 | * @template D 95 | * @param callable(C): A $f 96 | * @param callable(B): D $g 97 | * @return Validation 98 | */ 99 | public function diMap(callable $f, callable $g): self 100 | { 101 | return (new ValidationProfunctor())->diMap($f, $g, $this); 102 | } 103 | 104 | /** 105 | * @template C 106 | * @param callable(C): A $f 107 | * @return Validation 108 | */ 109 | public function lmap(callable $f): self 110 | { 111 | return self::fromBrand2((new ExtraProfunctor(new ValidationProfunctor()))->lmap($f, $this)); 112 | } 113 | 114 | /** 115 | * @template D 116 | * @param callable(B): D $g 117 | * @return Validation 118 | */ 119 | public function rmap(callable $g): self 120 | { 121 | return self::fromBrand2((new ExtraProfunctor(new ValidationProfunctor()))->rmap($g, $this)); 122 | } 123 | 124 | /** 125 | * this is the only thing you can do with a validation: pass to it some data and get back either an error or some 126 | * valid data 127 | * 128 | * @param A $a 129 | * @return Either 130 | * 131 | * @psalm-mutation-free 132 | */ 133 | public function validate($a): Either 134 | { 135 | /** @psalm-suppress ImpureFunctionCall */ 136 | return ($this->validation)($a); 137 | } 138 | 139 | /** 140 | * Kleisli composition of validations 141 | * 142 | * @template C 143 | * @param Validation $that 144 | * @return Validation 145 | */ 146 | public function then(self $that): self 147 | { 148 | return new self( 149 | /** 150 | * @param A $a 151 | * @return Either 152 | * 153 | * @psalm-suppress ArgumentTypeCoercion 154 | */ 155 | fn($a) => $this->validate($a)->bind($that->validation) 156 | ); 157 | } 158 | 159 | /** 160 | * @template F 161 | * @param callable(E): F $f 162 | * @return self Validation 163 | */ 164 | public function mapFailure(callable $f): self 165 | { 166 | return new self( 167 | /** 168 | * @param A $a 169 | * @return Either 170 | */ 171 | fn($a) => $this->validate($a)->mapLeft($f) 172 | ); 173 | } 174 | 175 | /** 176 | * @param Validation $that 177 | * @return Validation 178 | */ 179 | public function or(Semigroup $eSemigroup, Validation $that): self 180 | { 181 | return (new ValidationAlt($eSemigroup))->alt($this, $that); 182 | } 183 | 184 | /** 185 | * @param Validation $that 186 | * @return Validation 187 | */ 188 | public function and(Semigroup $eSemigroup, Validation $that): self 189 | { 190 | /** @psalm-suppress ImpureMethodCall */ 191 | return self::all( 192 | $eSemigroup, 193 | new FirstSemigroup(), 194 | [$this, $that] 195 | ); 196 | } 197 | 198 | // TRIVIAL COMBINATORS 199 | 200 | /** 201 | * @template C 202 | * @template F 203 | * @return Validation 204 | * 205 | * @psalm-pure 206 | */ 207 | public static function valid(): self 208 | { 209 | return new self( 210 | /** 211 | * @param C $a 212 | */ 213 | fn($a) => Either::right($a) 214 | ); 215 | } 216 | 217 | /** 218 | * @template C 219 | * @template F 220 | * @template D 221 | * @param F $e 222 | * @return Validation 223 | * 224 | * @psalm-pure 225 | */ 226 | public static function invalid($e): self 227 | { 228 | return new self( 229 | /** 230 | * @param C $_ 231 | */ 232 | fn($_) => Either::left($e) 233 | ); 234 | } 235 | 236 | // BASIC VALIDATORS 237 | 238 | /** 239 | * @template C 240 | * @template F 241 | * @param C $a 242 | * @param F $e 243 | * @return Validation 244 | */ 245 | public static function is($a, $e): self 246 | { 247 | return self::satisfies( 248 | /** 249 | * @param mixed $b 250 | */ 251 | fn ($b) => $b === $a, 252 | $e 253 | ); 254 | } 255 | 256 | /** 257 | * @template C of array 258 | * @template F 259 | * @param array-key $key 260 | * @param F $e 261 | * @return Validation 262 | */ 263 | public static function hasKey($key, $e): self 264 | { 265 | return self::satisfies( 266 | /** 267 | * @param C $a 268 | */ 269 | fn($a) => array_key_exists($key, $a), 270 | $e 271 | ); 272 | } 273 | 274 | /** 275 | * @template C 276 | * @template F 277 | * @param F $e 278 | * @return (C is array ? Validation : Validation) 279 | */ 280 | public static function isArray($e): self 281 | { 282 | /** @var (C is array ? Validation : Validation) */ 283 | return self::satisfies('is_array', $e); 284 | } 285 | 286 | /** 287 | * @template C 288 | * @template F 289 | * @param F $e 290 | * @return Validation 291 | */ 292 | public static function isBool($e): self 293 | { 294 | /** @var Validation */ 295 | return self::satisfies('is_bool', $e); 296 | } 297 | 298 | /** 299 | * @template C 300 | * @template F 301 | * @param callable(string): F $e 302 | * @return Validation 303 | */ 304 | public static function isDate(callable $e): self 305 | { 306 | /** @var Validation */ 307 | return new self( 308 | /** 309 | * @return Either 310 | */ 311 | static function (string $date) use ($e) { 312 | try { 313 | return Either::right(new DateTimeImmutable($date)); 314 | } catch (Exception $exception) { 315 | return Either::left($e($exception->getMessage())); 316 | } 317 | } 318 | ); 319 | } 320 | 321 | /** 322 | * @template C 323 | * @template F 324 | * @param F $e 325 | * @return (C is float ? Validation : Validation) 326 | */ 327 | public static function isFloat($e): self 328 | { 329 | /** @var (C is float ? Validation : Validation) */ 330 | return self::satisfies('is_float', $e); 331 | } 332 | 333 | /** 334 | * @template C 335 | * @template F 336 | * @param F $e 337 | * @return (C is int ? Validation : Validation) 338 | */ 339 | public static function isInteger($e): self 340 | { 341 | /** @var (C is int ? Validation : Validation) */ 342 | return self::satisfies('is_int', $e); 343 | } 344 | 345 | /** 346 | * @template C 347 | * @template F 348 | * @param F $e 349 | * @return Validation 350 | */ 351 | public static function isList($e): self 352 | { 353 | /** @var Validation */ 354 | return self::satisfies( 355 | fn(array $a) => $a === array_values($a), 356 | $e 357 | ); 358 | } 359 | 360 | /** 361 | * @template C 362 | * @template F 363 | * @param F $e 364 | * @return Validation 365 | */ 366 | public static function isNull($e): self 367 | { 368 | /** @var Validation */ 369 | return self::satisfies( 370 | /** 371 | * @param mixed $a 372 | */ 373 | fn($a) => null === $a, 374 | $e 375 | ); 376 | } 377 | 378 | /** 379 | * @template C 380 | * @template F 381 | * @param F $e 382 | * @return (C is string ? Validation : Validation) 383 | */ 384 | public static function isString($e): self 385 | { 386 | /** @var (C is string ? Validation : Validation) */ 387 | return self::satisfies('is_string', $e); 388 | } 389 | 390 | /** 391 | * @template F 392 | * @param F $e 393 | * @return Validation 394 | */ 395 | public static function nonEmptyArray($e): self 396 | { 397 | /** @var Validation */ 398 | return self::satisfies( 399 | fn (array $a) => $a !== [], 400 | $e 401 | ); 402 | } 403 | 404 | /** 405 | * @template F 406 | * @param F $e 407 | * @return Validation 408 | */ 409 | public static function nonEmptyString($e): self 410 | { 411 | /** @var Validation */ 412 | return self::satisfies( 413 | fn (string $a) => $a !== '', 414 | $e 415 | ); 416 | } 417 | 418 | /** 419 | * @template C 420 | * @template F 421 | * @param callable(C): bool $f 422 | * @param F $e 423 | * @return Validation 424 | */ 425 | public static function satisfies(callable $f, $e): self 426 | { 427 | return new self( 428 | /** 429 | * @param C $a 430 | */ 431 | static function ($a) use ($e, $f) { 432 | if (!$f($a)) { 433 | /** @var Either */ 434 | return Either::left($e); 435 | } 436 | 437 | /** @var Either */ 438 | return Either::right($a); 439 | } 440 | ); 441 | } 442 | 443 | // COMBINATORS 444 | 445 | /** 446 | * @template C 447 | * @template F 448 | * @template D 449 | * @param Semigroup $eSemigroup 450 | * @param Semigroup $bSemigroup 451 | * @param list> $validations 452 | * @return Validation 453 | */ 454 | public static function all(Semigroup $eSemigroup, Semigroup $bSemigroup, array $validations): self 455 | { 456 | /** @var AllMonoid $allMonoid */ 457 | $allMonoid = new AllMonoid($eSemigroup, $bSemigroup); 458 | 459 | return self::fold($allMonoid, $validations); 460 | } 461 | 462 | /** 463 | * @template C 464 | * @template F 465 | * @template D 466 | * @param Monoid $eMonoid 467 | * @param Semigroup $bSemigroup 468 | * @param list> $validations 469 | * @return Validation 470 | */ 471 | public static function any(Monoid $eMonoid, Semigroup $bSemigroup, array $validations): self 472 | { 473 | /** @var AnyMonoid $anyMonoid */ 474 | $anyMonoid = new AnyMonoid($eMonoid, $bSemigroup); 475 | 476 | return self::fold($anyMonoid, $validations); 477 | } 478 | 479 | /** 480 | * @template C 481 | * @template F 482 | * @param Validation $elementValidation 483 | * @param F $e 484 | * @return Validation, F, list> 485 | */ 486 | public static function anyElement(Validation $elementValidation, $e): self 487 | { 488 | /** @var Validation, F, list> */ 489 | return new self( 490 | /** 491 | * @param list $cs 492 | * @return Either> 493 | */ 494 | function (array $cs) use ($elementValidation, $e) { 495 | foreach ($cs as $c) { 496 | /** @psalm-suppress InvalidArgument */ 497 | $succeeds = $elementValidation->validate($c)->eval( 498 | /** 499 | * @param F $_ 500 | * @return bool 501 | */ 502 | fn($_) => false, 503 | /** 504 | * @param C $_ 505 | * @return bool 506 | */ 507 | fn($_) => true 508 | ); 509 | 510 | if ($succeeds) { 511 | /** @var Either> */ 512 | return Either::right($cs); 513 | } 514 | } 515 | 516 | /** @var Either> */ 517 | return Either::left($e); 518 | } 519 | ); 520 | } 521 | 522 | /** 523 | * @template K of array-key 524 | * @template F 525 | * @param array $validators 526 | * @param F $notArrayError 527 | * @param Semigroup $eSemigroup 528 | * @param callable(K): F $missingKey 529 | * @param callable(K, F): F $focusFailure 530 | * @return Validation> 531 | */ 532 | public static function associativeArray( 533 | array $validators, 534 | $notArrayError, 535 | Semigroup $eSemigroup, 536 | callable $missingKey, 537 | callable $focusFailure 538 | ): self { 539 | /** @var Validation $isArray */ 540 | $isArray = self::isArray($notArrayError); 541 | 542 | /** @var Validation> $keysValidator */ 543 | $keysValidator = self::all( 544 | $eSemigroup, 545 | new OppositeMonoid(new ConcatenationMonoid()), 546 | array_map( 547 | /** 548 | * @param K $key 549 | * @param Validation $validator 550 | * @return Validation 551 | */ 552 | function ($key, self $validator) use ($missingKey, $focusFailure) { 553 | /** 554 | * @psalm-suppress InvalidArgument 555 | * @psalm-suppress MixedArgumentTypeCoercion 556 | */ 557 | return self::hasKey($key, $missingKey($key))->then(self::focus( 558 | Lens::arrayKey($key), 559 | $validator->mapFailure( 560 | /** 561 | * @param F $error 562 | * @return F 563 | */ 564 | fn($error) => $focusFailure($key, $error) 565 | ) 566 | ))->rmap(fn(array $a) => [$key => $a[$key]]); 567 | }, 568 | array_keys($validators), 569 | $validators 570 | ) 571 | ); 572 | 573 | return $isArray->then($keysValidator); 574 | } 575 | 576 | /** 577 | * @template C 578 | * @template D 579 | * @template F 580 | * @param Semigroup $eSemigroup 581 | * @param Validation $elementValidation 582 | * @return Validation, F, list> 583 | */ 584 | public static function everyElement(Semigroup $eSemigroup, Validation $elementValidation): self 585 | { 586 | /** @var Validation, F, list> */ 587 | return new self( 588 | /** 589 | * @param list $cs 590 | * @return Either> 591 | */ 592 | function (array $cs) use ($eSemigroup, $elementValidation) { 593 | /** @var Applicative > $applicative */ 594 | $applicative = new ValidationApplicative($eSemigroup); 595 | 596 | /** 597 | * @psalm-suppress ArgumentTypeCoercion 598 | * @psalm-suppress InvalidArgument 599 | */ 600 | return (new EitherFunctor())->map( 601 | /** 602 | * @param HK1 $hk 603 | * @return list 604 | */ 605 | fn (HK1 $hk) => ListL::fromBrand($hk)->asNativeList(), 606 | (new ListTraversable())->traverse( 607 | $applicative, 608 | $elementValidation->validation, 609 | new ListL($cs) 610 | ) 611 | ); 612 | } 613 | ); 614 | } 615 | 616 | /** 617 | * @template C 618 | * @template F 619 | * @template D 620 | * @template L 621 | * @template M 622 | * @param Lens $lens 623 | * @param Validation $validation 624 | * @return Validation 625 | */ 626 | public static function focus(Lens $lens, self $validation): self 627 | { 628 | return new Validation( 629 | /** 630 | * @param C $c 631 | * @return Either 632 | */ 633 | function ($c) use ($lens, $validation) { 634 | $l = $lens->get($c); 635 | 636 | /** @psalm-suppress InvalidArgument */ 637 | return $validation->validate($l)->map( 638 | /** 639 | * @param M $m 640 | * @return D 641 | */ 642 | fn ($m) => $lens->set($c, $m) 643 | ); 644 | } 645 | ); 646 | } 647 | 648 | /** 649 | * @template C 650 | * @template F 651 | * @template D 652 | * @param Monoid> $validationMonoid 653 | * @param list> $validations 654 | * @return Validation 655 | */ 656 | public static function fold(Monoid $validationMonoid, array $validations): self 657 | { 658 | /** @var Validation */ 659 | return Traversable::fromArray($validations)->foldr( 660 | [$validationMonoid, 'append'], 661 | $validationMonoid->mempty() 662 | ); 663 | } 664 | 665 | /** 666 | * @template C 667 | * @template F 668 | * @template D 669 | * @param Semigroup $eSemigroup 670 | * @param F $e 671 | * @param Validation $validation 672 | * @return Validation 673 | */ 674 | public static function nullable(Semigroup $eSemigroup, $e, Validation $validation): self 675 | { 676 | $f = 677 | /** 678 | * @param D $d 679 | * @return D|null 680 | */ 681 | fn($d) => $d; 682 | 683 | $g = 684 | /** 685 | * @param null $n 686 | * @return D|null 687 | */ 688 | fn($n) => $n; 689 | 690 | /** @var Validation $nullValidation */ 691 | $nullValidation = self::isNull($e)->rmap($g); 692 | 693 | return $validation->rmap($f)->or($eSemigroup, $nullValidation); 694 | } 695 | } 696 | --------------------------------------------------------------------------------