Passed
Push — 2.x ( 31eec5...1a8680 )
by Aleksei
17:13
created

Typecast::cast()   B

Complexity

Conditions 10
Paths 13

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 11.897

Importance

Changes 0
Metric Value
cc 10
eloc 25
nc 13
nop 1
dl 0
loc 39
ccs 11
cts 15
cp 0.7332
crap 11.897
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Parser;
6
7
use BackedEnum;
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Cycle\ORM\Exception\TypecastException;
9
use DateTimeImmutable;
10
use Cycle\Database\DatabaseInterface;
11
use ReflectionEnum;
0 ignored issues
show
Bug introduced by
The type ReflectionEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use Throwable;
13
14
/**
15
 * @internal
16
 */
17
final class Typecast implements CastableInterface
18
{
19
    private const RULES = ['int', 'bool', 'float', 'datetime'];
20
21
    /** @var array<non-empty-string, bool> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, bool> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, bool>.
Loading history...
22
    private array $callableRules = [];
23
24
    /** @var array<string, class-string<BackedEnum>> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, class-string<BackedEnum>> at position 4 could not be parsed: Unknown type name 'class-string' at position 4 in array<string, class-string<BackedEnum>>.
Loading history...
25 3630
    private array $enumClasses = [];
26
27
    /** @var array<non-empty-string, callable|class-string<BackedEnum>|string> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, ...ing<BackedEnum>|string> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, callable|class-string<BackedEnum>|string>.
Loading history...
28
    private array $rules = [];
29
30 3630
    public function __construct(
31
        private DatabaseInterface $database
32 3630
    ) {
33 3630
    }
34 3536
35 3536
    public function setRules(array $rules): array
36 164
    {
37 162
        foreach ($rules as $key => $rule) {
38 162
            if (\in_array($rule, self::RULES, true)) {
39 162
                $this->rules[$key] = $rule;
40
                unset($rules[$key]);
41
            } elseif (\is_string($rule) && \is_subclass_of($rule, BackedEnum::class, true)) {
42
                $reflection = new ReflectionEnum($rule);
43 3630
                $this->enumClasses[$key] = (string)$reflection->getBackingType();
44
                $this->rules[$key] = $rule;
45
                unset($rules[$key]);
46 3476
            } elseif (\is_callable($rule)) {
47
                $this->callableRules[$key] = true;
48
                $this->rules[$key] = $rule;
49 3476
                unset($rules[$key]);
50 3474
            }
51 1198
        }
52
53
        return $rules;
54 3448
    }
55 138
56 138
    public function cast(array $data): array
57
    {
58
        try {
59 3378
            foreach ($this->rules as $key => $rule) {
60
                if (!isset($data[$key])) {
61
                    continue;
62
                }
63
64
                if (isset($this->callableRules[$key])) {
65
                    $data[$key] = $rule($data[$key], $this->database);
66
                    continue;
67
                }
68
69 3476
                if (isset($this->enumClasses[$key])) {
70
                    /** @var class-string<BackedEnum> $rule */
71
                    $type = $this->enumClasses[$key];
72
                    $value = $data[$key];
73
                    $data[$key] = match (true) {
74
                        !\is_scalar($value) => null,
75 3378
                        $type === 'string' && (\is_string($type) || \is_numeric($value))
76
                            => $rule::tryFrom((string)$value),
77 3378
                        $type === 'int' && (\is_int($value) || \preg_match('/^\\d++$/', $value) === 1)
78 3108
                            => $rule::tryFrom((int)$value),
79 45
                        default => null,
80 541
                    };
81 51
                    continue;
82
                }
83 51
84
                $data[$key] = $this->castPrimitive($rule, $data[$key]);
85 3378
            }
86
        } catch (Throwable $e) {
87
            throw new TypecastException(
88
                \sprintf('Unable to typecast the `%s` field. %s', $key, $e->getMessage()),
89
                $e->getCode(),
90
                $e
91
            );
92
        }
93
94
        return $data;
95
    }
96
97
    /**
98
     * @throws \Exception
99
     */
100
    private function castPrimitive(mixed $rule, mixed $value): mixed
101
    {
102
        return match ($rule) {
103
            'int' => (int)$value,
104
            'bool' => (bool)$value,
105
            'float' => (float)$value,
106
            'datetime' => new DateTimeImmutable(
107
                $value,
108
                $this->database->getDriver()->getTimezone()
109
            ),
110
            default => $value,
111
        };
112
    }
113
}
114