Passed
Push — master ( 2cda12...3e0ae5 )
by Melech
02:04 queued 37s
created

Castable::internalCheckAndCastPropertyValue()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 3
nop 2
dl 0
loc 18
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Type\Model\Trait;
15
16
use Closure;
17
use Valkyrja\Type\Contract\Type;
18
use Valkyrja\Type\Data\Cast;
19
20
use function is_array;
21
22
/**
23
 * Trait Castable.
24
 *
25
 * @author Melech Mizrachi
26
 */
27
trait Castable
28
{
29
    /**
30
     * Local cache for castings.
31
     *
32
     * <code>
33
     *      static::class => [
34
     *           // An property to be cast to a type
35
     *           'property_name' => new Cast(Type::class),
36
     *           // An property to be cast to an array of types
37
     *           'property_name' => new Cast(Type::class, isArray: true),
38
     *      ]
39
     * </code>
40
     *
41
     * @var array<string, array<string, Cast>>
42
     */
43
    private static array $castings = [];
44
45
    /**
46
     * @inheritDoc
47
     *
48
     * @return array<string, Cast>
49
     */
50
    public static function getCastings(): array
51
    {
52
        return [];
53
    }
54
55
    /**
56
     * @inheritDoc
57
     *
58
     * @param array<string, mixed>                          $properties  The properties to set
59
     * @param Closure(string, mixed, Cast|null): mixed|null $modifyValue [optional] The closure to modify the value before setting
60
     */
61
    protected function internalSetProperties(array $properties, Closure|null $modifyValue = null): void
62
    {
63
        $castings = $this->internalGetCastings();
64
65
        parent::internalSetProperties(
66
            $properties,
67
            fn (string $property, mixed $value): mixed => $modifyValue !== null
68
                ? $modifyValue($property, $value, $castings[$property] ?? null)
69
                : $this->internalCheckAndCastPropertyValue(
70
                    $castings[$property] ?? null,
71
                    $value
72
                )
73
        );
74
    }
75
76
    /**
77
     * Check and cast a property's value.
78
     *
79
     * @param Cast|null $cast  The cast
80
     * @param mixed     $value The property value
81
     *
82
     * @return mixed
83
     */
84
    protected function internalCheckAndCastPropertyValue(Cast|null $cast, mixed $value): mixed
85
    {
86
        // If there is no type specified or the value is null just return the value
87
        // cast assignment is set in the if specifically to avoid an assignment
88
        // if the value is null, which would be an unneeded assigned variable
89
        if ($value === null || $cast === null) {
90
            return $value;
91
        }
92
93
        // An array would indicate an array of types
94
        if ($cast->isArray && is_array($value)) {
95
            return array_map(
96
                fn (mixed $data) => $this->internalCastPropertyValue($cast, $data),
97
                $value
98
            );
99
        }
100
101
        return $this->internalCastPropertyValue($cast, $value);
102
    }
103
104
    /**
105
     * Cast a property's value.
106
     *
107
     * @param Cast  $cast  The cast for the property
108
     * @param mixed $value The value
109
     *
110
     * @return mixed|array|null
111
     */
112
    protected function internalCastPropertyValue(Cast $cast, mixed $value): mixed
113
    {
114
        if ($value === null) {
115
            return null;
116
        }
117
118
        /** @var class-string<Type<mixed>> $type */
119
        $type = $cast->type;
120
121
        $typeInstance = ($value instanceof $type)
122
            ? $value
123
            : $type::fromValue($value);
124
        $typeInstance = $this->internalModifyCastPropertyValue($typeInstance);
125
126
        // Convert specifically stated types
127
        if ($cast->convert) {
128
            return $typeInstance->asValue();
129
        }
130
131
        return $typeInstance;
132
    }
133
134
    /**
135
     * Modify the cast value before returning it.
136
     *
137
     * @param Type $type The type
138
     *
139
     * @return Type
140
     */
141
    protected function internalModifyCastPropertyValue(Type $type): Type
142
    {
143
        return $type;
144
    }
145
146
    /**
147
     * Get the castings for the Model.
148
     *
149
     * @return array<string, Cast>
150
     */
151
    protected function internalGetCastings(): array
152
    {
153
        return self::$castings[static::class]
154
            ??= static::getCastings();
155
    }
156
}
157