|
4 | 4 |
|
5 | 5 | namespace ReallifeKip\ImmutableBase; |
6 | 6 |
|
| 7 | +use Closure; |
7 | 8 | use Exception; |
8 | 9 | use ReflectionClass; |
9 | 10 | use JsonSerializable; |
@@ -57,9 +58,11 @@ abstract class ImmutableBase implements JsonSerializable |
57 | 58 | { |
58 | 59 | /** @var ReflectionClass[] $reflectionsCache */ |
59 | 60 | private static array $reflectionsCache = []; |
| 61 | + private static array $classBoundSetter = []; |
60 | 62 | public function __construct(array $data = []) |
61 | 63 | { |
62 | | - $this->walkProperties(function (\ReflectionProperty $property) use ($data): void { |
| 64 | + $thisClass = (new ReflectionClass($this))->getName(); |
| 65 | + $this->walkProperties(function (\ReflectionProperty $property) use ($thisClass, $data) { |
63 | 66 | try { |
64 | 67 | $key = $property->getName(); |
65 | 68 | /** @var \ReflectionNamedType|\ReflectionUnionType $type */ |
@@ -94,7 +97,23 @@ public function __construct(array $data = []) |
94 | 97 | $exists => $this->valueDecide($type, $data[$key]), |
95 | 98 | default => null |
96 | 99 | }; |
97 | | - $property->setValue($this, $value); |
| 100 | + $declaring = $property->getDeclaringClass()->getName(); |
| 101 | + $isOwn = ($declaring === $thisClass); |
| 102 | + if (!$isOwn && $property->isReadOnly()) { |
| 103 | + if ($property->isInitialized($this)) { |
| 104 | + return; |
| 105 | + } |
| 106 | + $assign = self::$classBoundSetter[$declaring] ??= Closure::bind( |
| 107 | + function (object $obj, string $prop, mixed $val): void { |
| 108 | + $obj->$prop = $val; |
| 109 | + }, |
| 110 | + null, |
| 111 | + $declaring |
| 112 | + ); |
| 113 | + $assign($this, $property->getName(), $value); |
| 114 | + } else { |
| 115 | + $property->setValue($this, $value); |
| 116 | + } |
98 | 117 | } catch (Exception $e) { |
99 | 118 | if ($msg = $e->getMessage()) { |
100 | 119 | throw new Exception("$key $msg"); |
|
0 commit comments