• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PugKong / clibitica / 12449711568

22 Dec 2024 12:35AM UTC coverage: 98.911% (-0.1%) from 99.014%
12449711568

push

github

PugKong
Update dependencies

908 of 918 relevant lines covered (98.91%)

27.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.28
/src/Command/InputMapper/Mapper.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Command\InputMapper;
6

7
use BackedEnum;
8
use ReflectionClass;
9
use RuntimeException;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\TypeInfo\Type;
14
use Symfony\Component\TypeInfo\Type\BackedEnumType;
15
use Symfony\Component\TypeInfo\Type\BuiltinType;
16
use Symfony\Component\TypeInfo\Type\CollectionType;
17
use Symfony\Component\TypeInfo\Type\NullableType;
18
use Symfony\Component\TypeInfo\Type\ObjectType;
19
use Symfony\Component\TypeInfo\TypeIdentifier;
20
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
21

22
use function count;
23
use function is_array;
24
use function is_float;
25
use function is_int;
26
use function is_string;
27
use function sprintf;
28

29
final readonly class Mapper
30
{
31
    public function __construct(private TypeResolver $typeResolver, private ?Suggestions $suggestions)
60✔
32
    {
33
    }
60✔
34

35
    /**
36
     * @param class-string $class
37
     */
38
    public function configure(Command $command, string $class): void
60✔
39
    {
40
        $classReflection = new ReflectionClass($class);
60✔
41
        $constructor = $classReflection->getConstructor();
60✔
42
        if (null === $constructor) {
60✔
43
            throw new RuntimeException(sprintf('Class %s has no constructor', $class));
×
44
        }
45

46
        foreach ($constructor->getParameters() as $parameter) {
60✔
47
            $isOptional = $parameter->isDefaultValueAvailable();
60✔
48

49
            $default = null;
60✔
50
            if ($isOptional) {
60✔
51
                $default = $parameter->getDefaultValue();
30✔
52
                if ($default instanceof BackedEnum) {
30✔
53
                    $default = $default->value;
4✔
54
                }
55
                if (is_array($default)) {
30✔
56
                    foreach ($default as $i => $value) {
10✔
57
                        if ($value instanceof BackedEnum) {
10✔
58
                            $default[$i] = $value->value;
4✔
59
                        }
60
                    }
61
                }
62
            }
63

64
            $type = $this->typeResolver->resolve($parameter);
60✔
65
            $isArray = $type->isIdentifiedBy(TypeIdentifier::ARRAY);
60✔
66

67
            foreach ($parameter->getAttributes() as $attribute) {
60✔
68
                $attribute = $attribute->newInstance();
60✔
69

70
                $suggestions = [];
60✔
71
                if ($attribute instanceof Argument || $attribute instanceof Option) {
60✔
72
                    if (is_array($attribute->suggestions)) {
60✔
73
                        $suggestions = $attribute->suggestions;
55✔
74
                        if (0 === count($suggestions) && $type instanceof BackedEnumType) {
55✔
75
                            $suggestions = array_map(
8✔
76
                                fn (BackedEnum $enum) => $enum->value,
8✔
77
                                $type->getClassName()::cases(),
8✔
78
                            );
8✔
79
                        }
80
                        foreach ($suggestions as $i => $suggestion) {
55✔
81
                            if ($suggestion instanceof BackedEnum) {
13✔
82
                                $suggestions[$i] = $suggestion->value;
2✔
83
                            }
84
                        }
85
                        if (0 === count($suggestions) && $type instanceof NullableType) {
55✔
86
                            $wrappedType = $type->getWrappedType();
10✔
87
                            if ($wrappedType instanceof BackedEnumType) {
10✔
88
                                $suggestions = array_map(
4✔
89
                                    fn (BackedEnum $enum) => $enum->value,
4✔
90
                                    $wrappedType->getClassName()::cases(),
4✔
91
                                );
4✔
92
                            }
93
                        }
94
                        if (0 === count($suggestions) && $type instanceof CollectionType) {
55✔
95
                            $valueType = $type->getCollectionValueType();
20✔
96
                            if ($valueType instanceof ObjectType && is_subclass_of($valueType->getClassName(), BackedEnum::class)) {
20✔
97
                                $suggestions = array_map(
8✔
98
                                    fn (BackedEnum $enum) => $enum->value,
8✔
99
                                    $valueType->getClassName()::cases(),
8✔
100
                                );
8✔
101
                            }
102
                        }
103
                    } elseif (null !== $this->suggestions) {
5✔
104
                        $suggestions = $this->suggestions->suggester($attribute->suggestions);
5✔
105
                    } else {
106
                        throw new RuntimeException('Suggestions service was not set');
×
107
                    }
108
                }
109

110
                if ($attribute instanceof Argument) {
60✔
111
                    $mode = $isOptional ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
60✔
112
                    if ($isArray) {
60✔
113
                        $mode |= InputArgument::IS_ARRAY;
20✔
114
                    }
115

116
                    $command->addArgument(
60✔
117
                        name: $attribute->name,
60✔
118
                        mode: $mode,
60✔
119
                        description: $attribute->description,
60✔
120
                        default: $default,
60✔
121
                        suggestedValues: $suggestions,
60✔
122
                    );
60✔
123

124
                    break;
60✔
125
                }
126
            }
127
        }
128
    }
129

130
    /**
131
     * @template T of object
132
     *
133
     * @param class-string<T> $class
134
     *
135
     * @return T
136
     */
137
    public function map(InputInterface $input, string $class): mixed
60✔
138
    {
139
        $classReflection = new ReflectionClass($class);
60✔
140
        $constructor = $classReflection->getConstructor();
60✔
141
        if (null === $constructor) {
60✔
142
            throw new RuntimeException(sprintf('Class %s has no constructor', $class));
×
143
        }
144

145
        $args = [];
60✔
146
        foreach ($constructor->getParameters() as $parameter) {
60✔
147
            foreach ($parameter->getAttributes() as $attribute) {
60✔
148
                $attribute = $attribute->newInstance();
60✔
149
                if ($attribute instanceof Argument) {
60✔
150
                    $type = $this->typeResolver->resolve($parameter);
60✔
151
                    $arg = $input->getArgument($attribute->name);
60✔
152

153
                    $args[] = $this->cast($type, $arg);
60✔
154

155
                    break;
60✔
156
                }
157
            }
158
        }
159

160
        return new $class(...$args);
60✔
161
    }
162

163
    private function cast(Type $type, mixed $value): mixed
60✔
164
    {
165
        if ($type->isNullable() && null === $value) {
60✔
166
            return null;
5✔
167
        }
168

169
        if ($type instanceof CollectionType) {
55✔
170
            $keyType = $type->getCollectionKeyType();
20✔
171
            $valueType = $type->getCollectionValueType();
20✔
172

173
            if (!$keyType->isIdentifiedBy(TypeIdentifier::INT)) {
20✔
174
                throw new RuntimeException('Unsupported property collection type');
×
175
            }
176

177
            if (!is_array($value)) {
20✔
178
                throw new RuntimeException('One more exception');
×
179
            }
180

181
            return array_map(fn ($v) => $this->cast($valueType, $v), $value);
20✔
182
        }
183

184
        return match (true) {
185
            $type->isIdentifiedBy(TypeIdentifier::STRING) => $this->castToString($value),
55✔
186
            $type->isIdentifiedBy(TypeIdentifier::FLOAT) => $this->castToFloat($value),
44✔
187
            $type->isIdentifiedBy(TypeIdentifier::INT) => $this->castToInt($value),
33✔
188
            $type instanceof BackedEnumType => $this->castToBackedEnum($type, $value),
22✔
189
            $type instanceof NullableType && $type->getWrappedType() instanceof BackedEnumType => $this->castToBackedEnum($type->getWrappedType(), $value),
10✔
190
            $type instanceof ObjectType && is_subclass_of($type->getClassName(), BackedEnum::class) => $this->castToBackedEnum(
8✔
191
                Type::enum($type->getClassName()), // @phpstan-ignore argument.type
8✔
192
                $value,
8✔
193
            ),
8✔
194
            default => throw new RuntimeException('Unsupported property type: '.$type),
55✔
195
        };
196
    }
197

198
    private function castToString(mixed $value): string
22✔
199
    {
200
        if (is_string($value) || is_int($value) || is_float($value)) {
22✔
201
            return (string) $value;
22✔
202
        }
203

204
        throw new RuntimeException(sprintf('Unsupported cast from %s to string', get_debug_type($value)));
×
205
    }
206

207
    private function castToFloat(mixed $value): float
11✔
208
    {
209
        if (is_string($value) || is_float($value) || is_int($value)) {
11✔
210
            return (float) $value;
11✔
211
        }
212

213
        throw new RuntimeException(sprintf('Unsupported cast from %s to int', get_debug_type($value)));
×
214
    }
215

216
    private function castToInt(mixed $value): int
22✔
217
    {
218
        if (is_string($value) || is_int($value)) {
22✔
219
            return (int) $value;
22✔
220
        }
221

222
        throw new RuntimeException(sprintf('Unsupported cast from %s to int', get_debug_type($value)));
×
223
    }
224

225
    /**
226
     * @template T
227
     * @template U of BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>
228
     *
229
     * @param BackedEnumType<T, U> $type
230
     */
231
    private function castToBackedEnum(BackedEnumType $type, mixed $value): BackedEnum
22✔
232
    {
233
        $enum = $type->getClassName();
22✔
234
        $type = $type->getBackingType()->getTypeIdentifier();
22✔
235

236
        /** @noinspection PhpUncoveredEnumCasesInspection */
237
        $value = match ($type) {
22✔
238
            TypeIdentifier::STRING => $this->castToString($value),
22✔
239
            TypeIdentifier::INT => $this->castToInt($value),
11✔
240
        };
22✔
241

242
        return $enum::from($value);
22✔
243
    }
244
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc