├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ └── merge-dependabot-upgrades.yml
├── .gitignore
├── .laminas-ci.json
├── .php-cs-fixer.dist.php
├── CNAME
├── CONTRIBUTE.md
├── LICENSE.md
├── README.md
├── composer.json
├── composer.lock
├── phpunit.xml
├── psalm.xml
├── src
├── TypedInput.php
└── Value.php
└── tests
├── TypedInputTest.php
└── ValueTest.php
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "04:00"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 |
2 | name: "Continuous Integration"
3 |
4 | on:
5 | pull_request:
6 | push:
7 |
8 | jobs:
9 | matrix:
10 | name: Generate job matrix
11 | runs-on: ubuntu-latest
12 | outputs:
13 | matrix: ${{ steps.matrix.outputs.matrix }}
14 | steps:
15 | - name: Gather CI configuration
16 | id: matrix
17 | uses: laminas/laminas-ci-matrix-action@v1
18 |
19 | qa:
20 | name: QA Checks
21 | needs: [matrix]
22 | runs-on: ${{ matrix.operatingSystem }}
23 | strategy:
24 | fail-fast: false
25 | matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }}
26 | steps:
27 | - name: ${{ matrix.name }}
28 | uses: laminas/laminas-continuous-integration-action@v1
29 | with:
30 | job: ${{ matrix.job }}
31 |
--------------------------------------------------------------------------------
/.github/workflows/merge-dependabot-upgrades.yml:
--------------------------------------------------------------------------------
1 | # See https://github.com/ridedott/merge-me-action/
2 | # This workflow automates merges from patches sent by Dependabot, and
3 | # only by dependabot, once the other CI workflows pass
4 | name: Auto-merge Dependabot PRs
5 |
6 | on:
7 | workflow_run:
8 | types:
9 | - completed
10 | workflows:
11 | - "Continuous Integration"
12 |
13 | jobs:
14 | merge-me:
15 | name: Auto-merge Dependabot PRs
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Auto-Merge
19 | if: ${{ github.event.workflow_run.conclusion == 'success' }}
20 | uses: ridedott/merge-me-action@v2.9.54
21 | with:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | MERGE_METHOD: MERGE
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | .phpunit.result.cache
3 | .php-cs-fixer.cache
4 | .idea
--------------------------------------------------------------------------------
/.laminas-ci.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_php_platform_requirements": {
3 | "8.4": true
4 | },
5 | "backwardCompatibilityCheck": true
6 | }
7 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | in([__DIR__ . '/src', __DIR__ . '/tests']);
5 |
6 | return (new PhpCsFixer\Config())
7 | ->setRules([
8 | 'declare_strict_types' => true,
9 | 'strict_param' => true,
10 | 'array_syntax' => ['syntax' => 'short'],
11 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
12 | 'phpdoc_to_comment' => false,
13 | ])
14 | ->setFinder($finder)
15 | ->setRiskyAllowed(true);
16 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | typed-input.michael-petri.com
--------------------------------------------------------------------------------
/CONTRIBUTE.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome. I accept merge requests on [GitHub][].
4 | This project follows semantic versioning and the [semantic branching model][].
5 |
6 | ## Communication Channels
7 |
8 | You can find help and discussion in the following places:
9 |
10 | - [GitHub Issues][issues]
11 |
12 | ## Reporting Bugs
13 |
14 | Bugs get tracked in the project's [issue tracker][issues].
15 |
16 | When submitting a bug report, please include enough information to reproduce the
17 | bug. A good bug report includes the following sections:
18 |
19 | - Given input
20 | - Expected output
21 | - Actual output
22 | - Steps to reproduce, including sample code
23 | - Any other information that will help debug and reproduce the issue, including
24 | stack traces, system/environment information, screenshots or at best a
25 | merge request with a test scenario which proofs the error.
26 |
27 | ## Fixing Bugs
28 |
29 | I welcome merge requests to fix bugs!
30 |
31 | If you see a bug report that you'd like to fix, please feel free to do so.
32 | See the [bug fixes][] section of the [semantic branching model][] documentation.
33 |
34 | ## Adding New Features
35 |
36 | If you have an idea for a new feature, it's a good idea to check out the
37 | [issues][] or active [merge requests][] first to see if the feature has already
38 | requested and being worked on. If not, feel free to submit an issue first, asking
39 | whether the feature is beneficial to the project. This will save you from doing a
40 | lot of development work only to have your feature rejected. I don't enjoy rejecting
41 | your hard work, but some features just don't fit with the goals of the project.
42 |
43 | When you do begin working on your feature, here are some guidelines to consider:
44 |
45 | - Check the [branch semantics][] section of the [semantic branching model][] documentation.
46 | - Your merge request description should clearly detail the changes you have made.
47 | I will use this description to update the CHANGELOG. If there is no
48 | description, or it does not adequately describe your feature, I will ask you
49 | to update the description.
50 | - This package follows the **[PSR-2 coding standard][psr-2]**. Please
51 | ensure your code does, too.
52 | - Please **write tests** for any new features you add.
53 | - Please **ensure that tests pass** before submitting your merge request.
54 | This package has automatically running tests for merge requests.
55 | However, running the tests locally will help save time.
56 | - Use **feature/{issue-id}.** branches. Please do not ask to merge from your master
57 | branch.
58 | - **Submit one feature per merge request.** If you have multiple features you
59 | wish to submit, please break them up into separate merge requests.
60 | - **Write good commit messages.** Make sure each individual commit in your merge
61 | request is meaningful. If you had to make multiple intermediate commits while
62 | developing, please squash them before submitting.
63 |
64 |
65 | ## Running Tests and Linters
66 |
67 | The following must pass before I will accept a merge request. If this does not
68 | pass, it will result in a complete build failure. Before you can run this, be
69 | sure to `composer install`.
70 |
71 | To run all the tests and coding standards checks, execute the following from the
72 | command line, while in the project root directory:
73 |
74 | ```
75 | php vendor/bin/php-cs-fixer fix src --dry-run
76 | php vendor/bin/psalm
77 | php vendor/bin/phpunit
78 | ```
79 |
80 | [GitHub]: https://github.com/michaelpetri/typed-input
81 | [issues]: https://github.com/michaelpetri/typed-input/issues
82 | [bug fixes]: https://dev-cafe.github.io/branching-model#bugfixes
83 | [branch semantics]: https://dev-cafe.github.io/branching-model/#branch-semantics
84 | [merge reqeusts]: https://github.com/michaelpetri/typed-input/compare
85 | [semantic branching model]: https://dev-cafe.github.io/branching-model
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Michael Petri
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Typed Input
2 |
3 | [](https://shepherd.dev/github/michaelpetri/typed-input)
4 | [](//packagist.org/packages/michaelpetri/typed-input)
5 | [](//packagist.org/packages/michaelpetri/typed-input)
6 |
7 | ## Installation
8 |
9 | ```shell
10 | composer require michaelpetri/typed-input
11 | ```
12 |
13 | ## Usage
14 |
15 | ```php
16 | $typedInput = TypedInput::fromInput($input);
17 |
18 | echo $typedInput->getOption('my-option')->asNonEmptyString();
19 | echo $typedInput->getArgument('my-argument')->asInteger();
20 | ```
21 |
22 | ## Available methods
23 |
24 | * `asBoolean`
25 | * `asBooleanOrNull`
26 | * `asInteger`
27 | * `asIntegerOrNull`
28 | * `asPositiveInteger`
29 | * `asPositiveIntegerOrNull`
30 | * `asNaturalInteger`
31 | * `asNaturalIntegerOrNull`
32 | * `asString`
33 | * `asStringOrNull`
34 | * `asNonEmptyString`
35 | * `asNonEmptyStringOrNull`
36 | * `asNonEmptyStrings`
37 | * `asDateTimeImmutable`
38 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "michaelpetri/typed-input",
3 | "description": "Type safe input wrapper for symfony input",
4 | "authors": [
5 | {
6 | "name": "Michael Petri",
7 | "email": "mpetri@lyska.io"
8 | }
9 | ],
10 | "license": "MIT",
11 | "require": {
12 | "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
13 | "symfony/console": "^5.2 || ^6.0 || ^7.0",
14 | "webmozart/assert": "^1.11"
15 | },
16 | "require-dev": {
17 | "friendsofphp/php-cs-fixer": "^3.0",
18 | "phpunit/phpunit": "^10.1.1",
19 | "roave/security-advisories": "dev-latest",
20 | "vimeo/psalm": "^5.1"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "MichaelPetri\\TypedInput\\": "src/"
25 | }
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "MichaelPetri\\TypedInput\\Tests\\": "tests/"
30 | }
31 | },
32 | "conflict": {
33 | "stevebauman/unfinalize": "*"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | src
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | tests/
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/TypedInput.php:
--------------------------------------------------------------------------------
1 | input = $input;
16 | }
17 |
18 | public static function fromInput(InputInterface $input): self
19 | {
20 | return new self($input);
21 | }
22 |
23 | public function getOption(string $name): Value
24 | {
25 | return new Value($this->input->getOption($name));
26 | }
27 |
28 | public function getArgument(string $name): Value
29 | {
30 | return new Value($this->input->getArgument($name));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Value.php:
--------------------------------------------------------------------------------
1 | value = $value;
22 | }
23 |
24 | public function asBoolean(): bool
25 | {
26 | $value = $this->asBooleanOrNull();
27 | Assert::notNull($value);
28 |
29 | return $value;
30 | }
31 |
32 | public function asBooleanOrNull(): ?bool
33 | {
34 | Assert::nullOrBoolean($this->value);
35 |
36 | return $this->value;
37 | }
38 |
39 | public function asInteger(): int
40 | {
41 | $value = $this->asIntegerOrNull();
42 | Assert::notNull($value);
43 |
44 | return $value;
45 | }
46 |
47 | public function asIntegerOrNull(): ?int
48 | {
49 | Assert::nullOrIntegerish($this->value);
50 |
51 | return null !== $this->value
52 | ? (int) $this->value
53 | : null;
54 | }
55 |
56 | /** @psalm-return positive-int */
57 | public function asPositiveInteger(): int
58 | {
59 | $value = $this->asPositiveIntegerOrNull();
60 | Assert::notNull($value);
61 |
62 | return $value;
63 | }
64 |
65 | /**
66 | * @psalm-return positive-int|null
67 | * @psalm-suppress MoreSpecificReturnType
68 | */
69 | public function asPositiveIntegerOrNull(): ?int
70 | {
71 | $value = $this->asIntegerOrNull();
72 | Assert::nullOrGreaterThan($value, 0);
73 |
74 | /** @psalm-suppress LessSpecificReturnStatement */
75 | return $value;
76 | }
77 |
78 | /**
79 | * @psalm-return positive-int|0
80 | * @psalm-suppress MoreSpecificReturnType
81 | */
82 | public function asNaturalInteger(): int {
83 | $value = $this->asNaturalIntegerOrNull();
84 | Assert::notNull($value);
85 |
86 | return $value;
87 | }
88 |
89 | /**
90 | * @psalm-return positive-int|0|null
91 | * @psalm-suppress LessSpecificReturnStatement
92 | * @psalm-suppress MoreSpecificReturnType
93 | */
94 | public function asNaturalIntegerOrNull(): ?int {
95 | $value = $this->asIntegerOrNull();
96 | Assert::nullOrGreaterThanEq($value, 0);
97 |
98 | return $value;
99 | }
100 |
101 | public function asString(): string
102 | {
103 | $value = $this->asStringOrNull();
104 | Assert::string($value);
105 |
106 | return $value;
107 | }
108 |
109 | public function asStringOrNull(): ?string
110 | {
111 | Assert::nullOrString($this->value);
112 |
113 | return $this->value;
114 | }
115 |
116 | /** @psalm-return non-empty-string */
117 | public function asNonEmptyString(): string
118 | {
119 | $value = $this->asNonEmptyStringOrNull();
120 | Assert::notNull($value);
121 |
122 | return $value;
123 | }
124 |
125 | /** @psalm-return non-empty-string|null */
126 | public function asNonEmptyStringOrNull(): ?string
127 | {
128 | Assert::nullOrStringNotEmpty($this->value);
129 |
130 | return $this->value;
131 | }
132 |
133 | /**
134 | * @psalm-return list
135 | * @return string[]
136 | */
137 | public function asNonEmptyStrings(): array
138 | {
139 | Assert::isArray($this->value);
140 | Assert::allStringNotEmpty($this->value);
141 |
142 | return array_values($this->value);
143 | }
144 |
145 | public function asDateTimeImmutable(?string $format = null): DateTimeImmutable
146 | {
147 | $value = $this->asNonEmptyString();
148 |
149 | try {
150 | $date = $format !== null
151 | ? DateTimeImmutable::createFromFormat($format, $value)
152 | : new DateTimeImmutable($value);
153 | } catch (Exception $e) {
154 | throw new TypeError(sprintf('"%s" is not a valid date string.', $value), 412, $e);
155 | }
156 |
157 | if(!$date instanceof DateTimeImmutable) {
158 | if($format !== null) {
159 | throw new TypeError(sprintf('Invalid date "%s" with format "%s"', $value, $format), 412);
160 | }
161 |
162 | throw new TypeError(sprintf('"%s" is not a valid date string.', $value), 412);
163 | }
164 |
165 | return $date;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/tests/TypedInputTest.php:
--------------------------------------------------------------------------------
1 | addOption(new InputOption(
20 | 'my-option',
21 | null,
22 | InputOption::VALUE_REQUIRED,
23 | 'My option which is an non-empty-string',
24 | 'my-default-value'
25 | ));
26 |
27 | $input = new ArrayInput(['--my-option' => 'my-value'], $definition);
28 |
29 | $typedInput = TypedInput::fromInput($input);
30 | self::assertSame('my-value', $typedInput->getOption('my-option')->asNonEmptyString());
31 | }
32 |
33 | public function testGetArgument(): void
34 | {
35 | $definition = new InputDefinition();
36 | $definition->addArgument(new InputArgument(
37 | 'my-argument',
38 | InputArgument::OPTIONAL,
39 | 'My option which is an non-empty-string',
40 | 'my-default-value'
41 | ));
42 |
43 | $input = new ArrayInput(['my-argument' => 'my-value'], $definition);
44 |
45 | $typedInput = TypedInput::fromInput($input);
46 | self::assertSame('my-value', $typedInput->getArgument('my-argument')->asNonEmptyString());
47 | }
48 | }
--------------------------------------------------------------------------------
/tests/ValueTest.php:
--------------------------------------------------------------------------------
1 | asBoolean());
23 | }
24 |
25 | /**
26 | * @psalm-param string|string[]|bool|null $raw
27 | * @dataProvider booleanOrNullProvider
28 | */
29 | public function testAsBooleanOrNull($raw, ?bool $expected): void
30 | {
31 | $value = new Value($raw);
32 | self::assertSame($expected, $value->asBooleanOrNull());
33 | }
34 |
35 | /**
36 | * @psalm-param string|string[]|bool|null $raw
37 | * @dataProvider integerProvider
38 | */
39 | public function testAsInteger($raw, int $expected): void
40 | {
41 | $value = new Value($raw);
42 | self::assertSame($expected, $value->asInteger());
43 | }
44 |
45 | /**
46 | * @psalm-param string|string[]|bool|null $raw
47 | * @dataProvider integerProvider
48 | */
49 | public function testAsIntegerOrNull($raw, ?int $expected): void
50 | {
51 | $value = new Value($raw);
52 | self::assertSame($expected, $value->asIntegerOrNull());
53 | }
54 |
55 | /**
56 | * @psalm-param string|string[]|bool|null $raw
57 | * @psalm-param positive-int $expected
58 | * @dataProvider positiveIntegerProvider
59 | */
60 | public function testAsPositiveInteger($raw, int $expected): void
61 | {
62 | $value = new Value($raw);
63 | self::assertSame($expected, $value->asPositiveInteger());
64 | }
65 |
66 | /**
67 | * @psalm-param string|string[]|bool|null $raw
68 | * @dataProvider nonPositiveIntegerOrNullProvider
69 | */
70 | public function testFailAsPositiveInteger($raw): void
71 | {
72 | $value = new Value($raw);
73 | $this->expectException(InvalidArgumentException::class);
74 | $value->asPositiveInteger();
75 | }
76 |
77 | /**
78 | * @psalm-param string|string[]|bool|null $raw
79 | * @psalm-param positive-int|null $expected
80 | * @dataProvider positiveIntegerOrNullProvider
81 | */
82 | public function testAsPositiveIntegerOrNull($raw, ?int $expected): void
83 | {
84 | $value = new Value($raw);
85 | self::assertSame($expected, $value->asPositiveIntegerOrNull());
86 | }
87 |
88 | /**
89 | * @psalm-param string|string[]|bool|null $raw
90 | * @dataProvider nonPositiveIntegerProvider
91 | */
92 | public function testFailAsPositiveIntegerOrNull($raw): void
93 | {
94 | $value = new Value($raw);
95 | $this->expectException(InvalidArgumentException::class);
96 | $value->asPositiveIntegerOrNull();
97 | }
98 |
99 | /**
100 | * @psalm-param string|string[]|bool|null $raw
101 | * @psalm-param positive-int|0 $expected
102 | * @dataProvider naturalIntegerProvider
103 | */
104 | public function testAsNaturalInteger($raw, int $expected): void
105 | {
106 | $value = new Value($raw);
107 | self::assertSame($expected, $value->asNaturalInteger());
108 | }
109 |
110 | /**
111 | * @psalm-param string|string[]|bool|null $raw
112 | * @dataProvider negativeIntegerOrNullProvider
113 | */
114 | public function testFailAsNaturalInteger($raw): void
115 | {
116 | $value = new Value($raw);
117 | $this->expectException(InvalidArgumentException::class);
118 | $value->asNaturalInteger();
119 | }
120 |
121 | /**
122 | * @psalm-param string|string[]|bool|null $raw
123 | * @psalm-param positive-int|0|null $expected
124 | * @dataProvider naturalIntegerOrNullProvider
125 | */
126 | public function testAsNaturalIntegerOrNull($raw, ?int $expected): void
127 | {
128 | $value = new Value($raw);
129 | self::assertSame($expected, $value->asNaturalIntegerOrNull());
130 | }
131 |
132 | /**
133 | * @psalm-param string|string[]|bool|null $raw
134 | * @dataProvider negativeIntegerProvider
135 | */
136 | public function testFailAsNaturalIntegerOrNull($raw): void
137 | {
138 | $value = new Value($raw);
139 | $this->expectException(InvalidArgumentException::class);
140 | $value->asNaturalIntegerOrNull();
141 | }
142 |
143 | /**
144 | * @psalm-param string|string[]|bool|null $raw
145 | * @dataProvider stringProvider
146 | */
147 | public function testAsString($raw, string $expected): void
148 | {
149 | $value = new Value($raw);
150 | self::assertSame($expected, $value->asString());
151 | }
152 |
153 | /**
154 | * @psalm-param string|string[]|bool|null $raw
155 | * @dataProvider stringOrNullProvider
156 | */
157 | public function testAsStringOrNull($raw, ?string $expected): void
158 | {
159 | $value = new Value($raw);
160 | self::assertSame($expected, $value->asStringOrNull());
161 | }
162 |
163 | /**
164 | * @psalm-param string|string[]|bool|null $raw
165 | * @psalm-param non-empty-string $expected
166 | * @dataProvider nonEmptyStringProvider
167 | */
168 | public function testAsNonEmptyString($raw, string $expected): void
169 | {
170 | $value = new Value($raw);
171 | self::assertSame($expected, $value->asStringOrNull());
172 | }
173 |
174 | /**
175 | * @psalm-param string|string[]|bool|null $raw
176 | * @dataProvider emptyStringOrNullProvider
177 | */
178 | public function testFailAsNonEmptyString($raw): void
179 | {
180 | $value = new Value($raw);
181 | $this->expectException(InvalidArgumentException::class);
182 | $value->asNonEmptyString();
183 | }
184 |
185 | /**
186 | * @psalm-param string|string[]|bool|null $raw
187 | * @psalm-param non-empty-string|null $expected
188 | * @dataProvider nonEmptyStringOrNullProvider
189 | */
190 | public function testAsNonEmptyStringOrNull($raw, ?string $expected): void
191 | {
192 | $value = new Value($raw);
193 | self::assertSame($expected, $value->asNonEmptyStringOrNull());
194 | }
195 |
196 | /**
197 | * @psalm-param string|string[]|bool|null $raw
198 | * @dataProvider emptyStringProvider
199 | */
200 | public function testFailAsNonEmptyStringOrNull($raw): void
201 | {
202 | $value = new Value($raw);
203 | $this->expectException(InvalidArgumentException::class);
204 | $value->asNonEmptyStringOrNull();
205 | }
206 |
207 | /**
208 | * @psalm-param string|string[]|bool|null $raw
209 | * @psalm-param non-empty-string $expected
210 | * @dataProvider nonEmptyStringProvider
211 | */
212 | public function testAsNonEmptyStrings($raw, string $expected): void
213 | {
214 | $value = new Value([$raw, $raw]);
215 | self::assertSame([$expected, $expected], $value->asNonEmptyStrings());
216 | }
217 |
218 | /**
219 | * @psalm-param string|string[]|bool|null $raw
220 | * @dataProvider emptyStringOrNullProvider
221 | */
222 | public function testFailAsNonEmptyStrings($raw): void
223 | {
224 | $value = new Value([$raw, $raw]);
225 | $this->expectException(InvalidArgumentException::class);
226 | $value->asNonEmptyStrings();
227 | }
228 |
229 | /**
230 | * @dataProvider dateTimeProvider
231 | *
232 | * @psalm-param mixed $raw
233 | * @psalm-param class-string|null $expectedException
234 | */
235 | public function testAsDateTimeImmutable($raw, ?string $expectedException, $expected, ?string $format): void
236 | {
237 | $value = new Value($raw);
238 |
239 | if ($expectedException !== null) {
240 | $this->expectException($expectedException);
241 | }
242 |
243 | $date = $value->asDateTimeImmutable($format);
244 |
245 | self::assertSame($expected, $date->format(DateTimeInterface::RSS));
246 | }
247 |
248 | public static function dateTimeProvider(): iterable
249 | {
250 | yield [null, InvalidArgumentException::class, null, null];
251 | yield [1234, InvalidArgumentException::class, null, null];
252 | yield ['blub', TypeError::class, null, null];
253 | yield ['20221111', null, 'Fri, 11 Nov 2022 00:00:00 +0000', null];
254 | yield ['2022-11-11', null, 'Fri, 11 Nov 2022 00:00:00 +0000', null];
255 | yield ['2022-11-11T05:06:07+01:00', null, 'Fri, 11 Nov 2022 05:06:07 +0100', null];
256 | yield ['2022-11-11', TypeError::class, null, DateTimeInterface::ATOM];
257 | yield ['2022-11-11T03:04:02+00:00', null, 'Fri, 11 Nov 2022 03:04:02 +0000', DateTimeInterface::ATOM];
258 | }
259 |
260 | /**
261 | * @psalm-return iterable
265 | */
266 | public static function booleanProvider(): iterable
267 | {
268 | yield [true, true];
269 | yield [false, false];
270 | }
271 |
272 | /**
273 | * @psalm-return iterable
277 | */
278 | public static function booleanOrNullProvider(): iterable
279 | {
280 | yield from self::booleanProvider();
281 | yield from self::nullProvider();
282 | }
283 |
284 | /**
285 | * @psalm-return iterable
289 | */
290 | public static function integerProvider(): iterable
291 | {
292 | yield from self::naturalIntegerProvider();
293 | yield from self::negativeIntegerProvider();
294 | }
295 |
296 | /**
297 | * @psalm-return iterable
301 | */
302 | public static function integerOrNullProvider(): iterable
303 | {
304 | yield from self::integerProvider();
305 | yield from self::nullProvider();
306 | }
307 |
308 | /**
309 | * @psalm-return iterable
313 | */
314 | private static function zeroIntegerProvider(): iterable
315 | {
316 | yield ['0', 0];
317 | }
318 |
319 | /**
320 | * @psalm-return iterable
324 | */
325 | public static function positiveIntegerProvider(): iterable
326 | {
327 | yield ['1', 1];
328 | }
329 |
330 | /**
331 | * @psalm-return iterable
335 | */
336 | public static function positiveIntegerOrNullProvider(): iterable
337 | {
338 | yield from self::positiveIntegerProvider();
339 | yield from self::nullProvider();
340 | }
341 |
342 | /**
343 | * @psalm-return iterable
347 | */
348 | public static function naturalIntegerProvider(): iterable
349 | {
350 | yield from self::zeroIntegerProvider();
351 | yield from self::positiveIntegerProvider();
352 | }
353 |
354 | public static function naturalIntegerOrNullProvider(): iterable
355 | {
356 | yield from self::naturalIntegerProvider();
357 | yield from self::nullProvider();
358 | }
359 |
360 | /**
361 | * @psalm-return iterable
365 | */
366 | public static function negativeIntegerProvider(): iterable
367 | {
368 | yield ['-1', -1];
369 | }
370 |
371 | /**
372 | * @psalm-return iterable
376 | */
377 | public static function negativeIntegerOrNullProvider(): iterable
378 | {
379 | yield from self::negativeIntegerProvider();
380 | yield [null, null];
381 | }
382 |
383 | /**
384 | * @psalm-return iterable
388 | */
389 | public static function nonPositiveIntegerProvider(): iterable
390 | {
391 | yield from self::negativeIntegerProvider();
392 | yield from self::zeroIntegerProvider();
393 | }
394 |
395 | /**
396 | * @psalm-return iterable
400 | */
401 | public static function nonPositiveIntegerOrNullProvider(): iterable
402 | {
403 | yield from self::negativeIntegerOrNullProvider();
404 | yield from self::zeroIntegerProvider();
405 | }
406 |
407 | /**
408 | * @psalm-return iterable
412 | */
413 | public static function stringProvider(): iterable
414 | {
415 | yield from self::nonEmptyStringProvider();
416 | yield from self::emptyStringProvider();
417 | }
418 |
419 | /**
420 | * @psalm-return iterable
424 | */
425 | public static function stringOrNullProvider(): iterable
426 | {
427 | yield from self::stringProvider();
428 | yield from self::nullProvider();
429 | }
430 |
431 | /**
432 | * @psalm-return iterable
436 | */
437 | public static function emptyStringProvider(): iterable
438 | {
439 | yield ['', ''];
440 | }
441 |
442 | /**
443 | * @psalm-return iterable
447 | */
448 | public static function emptyStringOrNullProvider(): iterable
449 | {
450 | yield from self::emptyStringProvider();
451 | yield from self::nullProvider();
452 | }
453 |
454 | /**
455 | * @psalm-return iterable
459 | */
460 | public static function nonEmptyStringProvider(): iterable
461 | {
462 | yield [' ', ' '];
463 | yield ['non-empty-string', 'non-empty-string'];
464 | yield ['true', 'true'];
465 | yield ['false', 'false'];
466 | yield ['1', '1'];
467 | yield ['0', '0'];
468 | yield ['-1', '-1'];
469 | }
470 |
471 | /**
472 | * @psalm-return iterable
476 | */
477 | public static function nonEmptyStringOrNullProvider(): iterable
478 | {
479 | yield from self::nonEmptyStringProvider();
480 | yield from self::nullProvider();
481 | }
482 |
483 | /**
484 | * @psalm-return iterable
488 | */
489 | private static function nullProvider(): iterable
490 | {
491 | yield [null, null];
492 | }
493 | }
494 |
--------------------------------------------------------------------------------