Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
29 / 29 |
|
96.97% |
32 / 33 |
|
69.57% |
16 / 23 |
|
83.33% |
5 / 6 |
CRAP | |
0.00% |
0 / 1 |
| Type | |
100.00% |
29 / 29 |
|
96.97% |
32 / 33 |
|
69.57% |
16 / 23 |
|
100.00% |
6 / 6 |
31.28 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| of | |
100.00% |
8 / 8 |
|
100.00% |
9 / 9 |
|
50.00% |
4 / 8 |
|
100.00% |
1 / 1 |
8.12 | |||
| convert | |
100.00% |
6 / 6 |
|
100.00% |
6 / 6 |
|
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
5 | |||
| expected | |
100.00% |
5 / 5 |
|
87.50% |
7 / 8 |
|
75.00% |
3 / 4 |
|
100.00% |
1 / 1 |
4.25 | |||
| choices | |
100.00% |
4 / 4 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| case | |
100.00% |
5 / 5 |
|
100.00% |
6 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Console; |
| 6 | |
| 7 | use BackedEnum; |
| 8 | use ReflectionNamedType; |
| 9 | use ReflectionParameter; |
| 10 | |
| 11 | /** |
| 12 | * The declared type of an `#[Arg]` or `#[Opt]` parameter. |
| 13 | * |
| 14 | * @internal |
| 15 | */ |
| 16 | final class Type |
| 17 | { |
| 18 | private function __construct( |
| 19 | public readonly string $name, |
| 20 | ) {} |
| 21 | |
| 22 | /** |
| 23 | * The parameter's type, or null if it is not a supported one. |
| 24 | */ |
| 25 | public static function of(ReflectionParameter $parameter): ?self |
| 26 | { |
| 27 | $type = $parameter->getType(); |
| 28 | |
| 29 | if (!$type instanceof ReflectionNamedType) { |
| 30 | return null; |
| 31 | } |
| 32 | |
| 33 | $name = $type->getName(); |
| 34 | |
| 35 | return match ($name) { |
| 36 | 'string', 'int', 'float', 'bool', 'array' => new self($name), |
| 37 | default => is_subclass_of($name, BackedEnum::class) ? new self($name) : null, |
| 38 | }; |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Converts a command-line value, or returns null if it does not fit. |
| 43 | * |
| 44 | * Not for `bool`, which takes no value, nor `array`, which collects |
| 45 | * the strings as they are. |
| 46 | */ |
| 47 | public function convert(string $value): string|int|float|BackedEnum|null |
| 48 | { |
| 49 | return match ($this->name) { |
| 50 | 'int' => filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE), |
| 51 | 'float' => filter_var($value, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE), |
| 52 | 'string' => $value, |
| 53 | default => $this->case($value), |
| 54 | }; |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Describes the values that convert, for error messages. |
| 59 | */ |
| 60 | public function expected(): string |
| 61 | { |
| 62 | return match ($this->name) { |
| 63 | 'int' => 'an integer', |
| 64 | 'float' => 'a number', |
| 65 | default => 'one of ' . implode(', ', $this->choices()), |
| 66 | }; |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * The backing values of an enum type, otherwise none. |
| 71 | * |
| 72 | * @return list<string> |
| 73 | */ |
| 74 | public function choices(): array |
| 75 | { |
| 76 | $enum = $this->name; |
| 77 | |
| 78 | if (!is_subclass_of($enum, BackedEnum::class)) { |
| 79 | return []; |
| 80 | } |
| 81 | |
| 82 | return array_map(static fn(BackedEnum $case): string => (string) $case->value, $enum::cases()); |
| 83 | } |
| 84 | |
| 85 | private function case(string $value): ?BackedEnum |
| 86 | { |
| 87 | /** @var class-string<BackedEnum> $enum */ |
| 88 | $enum = $this->name; |
| 89 | |
| 90 | // Compared as strings: under strict types, tryFrom() rejects the |
| 91 | // string input of an int-backed enum. |
| 92 | foreach ($enum::cases() as $case) { |
| 93 | if ((string) $case->value === $value) { |
| 94 | return $case; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | return null; |
| 99 | } |
| 100 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 19 | public readonly string $name, |
| 20 | ) {} |
| 85 | private function case(string $value): ?BackedEnum |
| 86 | { |
| 87 | /** @var class-string<BackedEnum> $enum */ |
| 88 | $enum = $this->name; |
| 89 | |
| 90 | // Compared as strings: under strict types, tryFrom() rejects the |
| 91 | // string input of an int-backed enum. |
| 92 | foreach ($enum::cases() as $case) { |
| 92 | foreach ($enum::cases() as $case) { |
| 93 | if ((string) $case->value === $value) { |
| 94 | return $case; |
| 92 | foreach ($enum::cases() as $case) { |
| 92 | foreach ($enum::cases() as $case) { |
| 93 | if ((string) $case->value === $value) { |
| 94 | return $case; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | return null; |
| 99 | } |
| 76 | $enum = $this->name; |
| 77 | |
| 78 | if (!is_subclass_of($enum, BackedEnum::class)) { |
| 79 | return []; |
| 82 | return array_map(static fn(BackedEnum $case): string => (string) $case->value, $enum::cases()); |
| 83 | } |
| 47 | public function convert(string $value): string|int|float|BackedEnum|null |
| 48 | { |
| 49 | return match ($this->name) { |
| 50 | 'int' => filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE), |
| 51 | 'float' => filter_var($value, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE), |
| 52 | 'string' => $value, |
| 53 | default => $this->case($value), |
| 53 | default => $this->case($value), |
| 54 | }; |
| 55 | } |
| 62 | return match ($this->name) { |
| 63 | 'int' => 'an integer', |
| 64 | 'float' => 'a number', |
| 65 | default => 'one of ' . implode(', ', $this->choices()), |
| 65 | default => 'one of ' . implode(', ', $this->choices()), |
| 65 | default => 'one of ' . implode(', ', $this->choices()), |
| 65 | default => 'one of ' . implode(', ', $this->choices()), |
| 65 | default => 'one of ' . implode(', ', $this->choices()), |
| 66 | }; |
| 67 | } |
| 25 | public static function of(ReflectionParameter $parameter): ?self |
| 26 | { |
| 27 | $type = $parameter->getType(); |
| 28 | |
| 29 | if (!$type instanceof ReflectionNamedType) { |
| 30 | return null; |
| 33 | $name = $type->getName(); |
| 34 | |
| 35 | return match ($name) { |
| 36 | 'string', 'int', 'float', 'bool', 'array' => new self($name), |
| 37 | default => is_subclass_of($name, BackedEnum::class) ? new self($name) : null, |
| 37 | default => is_subclass_of($name, BackedEnum::class) ? new self($name) : null, |
| 37 | default => is_subclass_of($name, BackedEnum::class) ? new self($name) : null, |
| 37 | default => is_subclass_of($name, BackedEnum::class) ? new self($name) : null, |
| 37 | default => is_subclass_of($name, BackedEnum::class) ? new self($name) : null, |
| 38 | }; |
| 39 | } |
| 82 | return array_map(static fn(BackedEnum $case): string => (string) $case->value, $enum::cases()); |