Lines
0.00%
0 / 53
Methods
0.00%
0 / 2
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| set | 0.00% | 0 / 13 | 0.00% | 0 / 1 | 56 | ||
| coerceValueToType | 0.00% | 0 / 40 | 0.00% | 0 / 1 | 812 | ||
| 14 | class TypedProperty | |
| 15 | { | |
| 16 | /** | |
| 17 | * Sets a ReflectionProperty's value with automatic casting / coercion | |
| 18 | * according to its declared type. | |
| 19 | */ | |
| 20 | public static function set(object $object, ReflectionProperty $prop, mixed $value): void | |
| 21 | { | |
| 22 | $type = $prop->getType(); | |
| 23 | ||
| 24 | // Untyped or mixed → assign directly | |
| 25 | if ($type === null || ($type instanceof ReflectionNamedType && $type->getName() === 'mixed')) { | |
| 26 | $prop->setValue($object, $value); | |
| 27 | return; | |
| 28 | } | |
| 29 | ||
| 30 | // Handle union types | |
| 31 | if ($type instanceof ReflectionUnionType) { | |
| 32 | foreach ($type->getTypes() as $innerType) { | |
| 33 | try { | |
| 34 | $coerced = self::coerceValueToType($innerType, $value); | |
| 35 | $prop->setValue($object, $coerced); | |
| 36 | return; | |
| 37 | } catch (TypeError) { | |
| 38 | // try next type | |
| 39 | } | |
| 40 | } | |
| 41 | throw new TypeError("Value does not match any allowed union type for \${$prop->getName()}"); | |
| 42 | } | |
| 43 | ||
| 44 | // Single type | |
| 45 | $coerced = self::coerceValueToType($type, $value); | |
| 46 | $prop->setValue($object, $coerced); | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * Coerces a value to the given ReflectionNamedType. | |
| 51 | * | |
| 52 | * @param ReflectionNamedType $type The target type | |
| 53 | * @param mixed $value The value to coerce | |
| 54 | * @return mixed The coerced value | |
| 55 | * @throws TypeError If the value cannot be coerced to the target type | |
| 56 | */ | |
| 57 | private static function coerceValueToType(ReflectionNamedType $type, mixed $value): mixed | |
| 58 | { | |
| 59 | $typeName = $type->getName(); | |
| 60 | $allowsNull = $type->allowsNull(); | |
| 61 | ||
| 62 | if ($value === null) { | |
| 63 | if ($allowsNull) { | |
| 64 | return null; | |
| 65 | } | |
| 66 | ||
| 67 | throw new TypeError("Cannot assign null to non-nullable type {$typeName}"); | |
| 68 | } | |
| 69 | ||
| 70 | // Built-in scalar types | |
| 71 | if ($type->isBuiltin()) { | |
| 72 | return match ($typeName) { | |
| 73 | 'int' => (int) $value, | |
| 74 | 'float' => (float) $value, | |
| 75 | 'string' => (string) $value, | |
| 76 | 'bool' => (bool) $value, | |
| 77 | 'array' => (array) $value, | |
| 78 | 'object' => (object) $value, | |
| 79 | 'mixed' => $value, | |
| 80 | 'callable' => is_callable($value) ? $value | |
| 81 | : throw new TypeError("Value is not callable"), | |
| 82 | default => throw new TypeError("Unsupported builtin type: {$typeName}"), | |
| 83 | }; | |
| 84 | } | |
| 85 | ||
| 86 | // Handle enums | |
| 87 | if (enum_exists($typeName)) { | |
| 88 | // Already the correct enum instance — return as-is | |
| 89 | if ($value instanceof $typeName) { | |
| 90 | return $value; | |
| 91 | } | |
| 92 | ||
| 93 | $refEnum = new ReflectionEnum($typeName); | |
| 94 | if ($refEnum->isBacked()) { | |
| 95 | // Only pass values tryFrom can accept for the backing type, | |
| 96 | // otherwise it throws a TypeError of its own. | |
| 97 | $backingType = $refEnum->getBackingType()->getName(); | |
| 98 | if ((is_string($value) && $backingType === 'string') | |
| 99 | || (is_int($value) && $backingType === 'int')) { | |
| 100 | $case = $typeName::tryFrom($value); | |
| 101 | if ($case !== null) { | |
| 102 | return $case; | |
| 103 | } | |
| 104 | } | |
| 105 | } else { | |
| 106 | foreach ($refEnum->getCases() as $case) { | |
| 107 | if ($case->getName() === $value) { | |
| 108 | // getValue() on a unit enum case returns the enum instance | |
| 109 | return $case->getValue(); | |
| 110 | } | |
| 111 | } | |
| 112 | } | |
| 113 | throw new TypeError("Invalid enum value for {$typeName}"); | |
| 114 | } | |
| 115 | ||
| 116 | // Handle class/interface types | |
| 117 | if (class_exists($typeName) || interface_exists($typeName)) { | |
| 118 | if ($value instanceof $typeName) { | |
| 119 | return $value; | |
| 120 | } | |
| 121 | // Optionally attempt to build object from array | |
| 122 | if (is_array($value)) { | |
| 123 | return new $typeName(...$value); | |
| 124 | } | |
| 125 | throw new TypeError("Value is not instance of {$typeName}"); | |
| 126 | } | |
| 127 | ||
| 128 | throw new TypeError("Unsupported type: {$typeName}"); | |
| 129 | } | |
| 130 | } |