├── .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 |
--------------------------------------------------------------------------------