Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
29 / 29
96.97% covered (success)
96.97%
32 / 33
69.57% covered (warning)
69.57%
16 / 23
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Type
100.00% covered (success)
100.00%
29 / 29
96.97% covered (success)
96.97%
32 / 33
69.57% covered (warning)
69.57%
16 / 23
100.00% covered (success)
100.00%
6 / 6
31.28
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 of
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
9 / 9
50.00% covered (danger)
50.00%
4 / 8
100.00% covered (success)
100.00%
1 / 1
8.12
 convert
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
 expected
100.00% covered (success)
100.00%
5 / 5
87.50% covered (warning)
87.50%
7 / 8
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
4.25
 choices
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 case
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
6 / 6
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Console;
6
7use BackedEnum;
8use ReflectionNamedType;
9use ReflectionParameter;
10
11/**
12 * The declared type of an `#[Arg]` or `#[Opt]` parameter.
13 *
14 * @internal
15 */
16final 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}

Branches

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.

Type->__construct
19        public readonly string $name,
20    ) {}
Type->case
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    }
Type->choices
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    }
Type->convert
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    }
Type->expected
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    }
Type->of
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    }
{closure:/workspace/celema/console/src/Type.php:82-82}
82        return array_map(static fn(BackedEnum $case): string => (string) $case->value, $enum::cases());