|
12 | 12 | use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; |
13 | 13 | use Symfony\Component\PropertyInfo\PropertyWriteInfo; |
14 | 14 | use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; |
| 15 | +use Symfony\Component\TypeInfo\Type; |
| 16 | +use Symfony\Component\TypeInfo\TypeIdentifier; |
15 | 17 |
|
16 | 18 | /** |
17 | 19 | * @internal |
@@ -74,6 +76,142 @@ private function getConstructorParameters(string $class): iterable |
74 | 76 | } |
75 | 77 |
|
76 | 78 | public function getReadAccessor(string $class, string $property, bool $allowExtraProperties = false): ?ReadAccessorInterface |
| 79 | + { |
| 80 | + // split to first dot for nested properties |
| 81 | + $exploded = explode('.', $property, 2); |
| 82 | + |
| 83 | + if (2 === \count($exploded)) { |
| 84 | + [$parent, $child] = $exploded; |
| 85 | + } else { |
| 86 | + return $this->doGetReadAccessor($class, $property, $allowExtraProperties); |
| 87 | + } |
| 88 | + |
| 89 | + if ('array' === $class) { |
| 90 | + $type = Type::array(); |
| 91 | + } elseif (\stdClass::class === $class) { |
| 92 | + $type = Type::object(\stdClass::class); |
| 93 | + } else { |
| 94 | + $type = $this->sourceTypeExtractor->getType($class, $parent); |
| 95 | + } |
| 96 | + |
| 97 | + $parentAccessor = $this->doGetReadAccessor($class, $parent, $allowExtraProperties); |
| 98 | + |
| 99 | + if ($type instanceof Type\ObjectType) { |
| 100 | + /** @var class-string $childClass */ |
| 101 | + $childClass = $type->getClassName(); |
| 102 | + $childAccessor = $this->getReadAccessor($childClass, $child, $allowExtraProperties); |
| 103 | + } elseif ($type?->isIdentifiedBy(TypeIdentifier::ARRAY)) { |
| 104 | + $childAccessor = $this->getReadAccessor('array', $child, $allowExtraProperties); |
| 105 | + } else { |
| 106 | + return null; |
| 107 | + } |
| 108 | + |
| 109 | + if (null === $parentAccessor || null === $childAccessor) { |
| 110 | + return null; |
| 111 | + } |
| 112 | + |
| 113 | + return new NestedReadAccessor($parentAccessor, $childAccessor); |
| 114 | + } |
| 115 | + |
| 116 | + public function getWriteMutator(string $source, string $target, string $property, array $context = [], bool $allowExtraProperties = false): ?WriteMutatorInterface |
| 117 | + { |
| 118 | + // split to last dot for nested properties |
| 119 | + $lastDotPosition = strrpos($property, '.'); |
| 120 | + |
| 121 | + if (false === $lastDotPosition) { |
| 122 | + return $this->doGetWriteMutator($target, $property, $context, $allowExtraProperties); |
| 123 | + } |
| 124 | + |
| 125 | + $parent = substr($property, 0, $lastDotPosition); |
| 126 | + $child = substr($property, $lastDotPosition + 1); |
| 127 | + $accessor = $this->getReadAccessor($target, $parent, $allowExtraProperties); |
| 128 | + |
| 129 | + if ($target === 'array') { |
| 130 | + $lastAccessorType = Type::array(); |
| 131 | + } elseif (\stdClass::class === $target) { |
| 132 | + $lastAccessorType = Type::object(\stdClass::class); |
| 133 | + } else { |
| 134 | + $lastAccessorType = $this->findLastTypeForAccessor($target, $parent); |
| 135 | + } |
| 136 | + |
| 137 | + if (null === $lastAccessorType) { |
| 138 | + return null; |
| 139 | + } |
| 140 | + |
| 141 | + if ($lastAccessorType instanceof Type\ObjectType) { |
| 142 | + /** @var class-string|'array' $targetClass */ |
| 143 | + $targetClass = $lastAccessorType->getClassName(); |
| 144 | + } elseif ($lastAccessorType->isIdentifiedBy(TypeIdentifier::ARRAY)) { |
| 145 | + $targetClass = 'array'; |
| 146 | + } else { |
| 147 | + return null; |
| 148 | + } |
| 149 | + |
| 150 | + $mutator = $this->doGetWriteMutator($targetClass, $child, $context, $allowExtraProperties); |
| 151 | + |
| 152 | + if (null === $accessor || null === $mutator) { |
| 153 | + return null; |
| 154 | + } |
| 155 | + |
| 156 | + return new NestedWriteMutator($accessor, $mutator); |
| 157 | + } |
| 158 | + |
| 159 | + public function getCheckExists(string $class, string $property): bool |
| 160 | + { |
| 161 | + if ('array' === $class || \stdClass::class === $class) { |
| 162 | + return true; |
| 163 | + } |
| 164 | + |
| 165 | + return false; |
| 166 | + } |
| 167 | + |
| 168 | + public function getGroups(string $class, string $property): ?array |
| 169 | + { |
| 170 | + return null; |
| 171 | + } |
| 172 | + |
| 173 | + public function getDateTimeFormat(PropertyMetadataEvent $propertyMetadataEvent): string |
| 174 | + { |
| 175 | + if (null !== $propertyMetadataEvent->dateTimeFormat) { |
| 176 | + return $propertyMetadataEvent->dateTimeFormat; |
| 177 | + } |
| 178 | + |
| 179 | + if (null !== $propertyMetadataEvent->mapperMetadata->dateTimeFormat) { |
| 180 | + return $propertyMetadataEvent->mapperMetadata->dateTimeFormat; |
| 181 | + } |
| 182 | + |
| 183 | + return $this->configuration->dateTimeFormat; |
| 184 | + } |
| 185 | + |
| 186 | + /** |
| 187 | + * @param class-string $source |
| 188 | + */ |
| 189 | + private function findLastTypeForAccessor(string $source, string $property): ?Type |
| 190 | + { |
| 191 | + $exploded = explode('.', $property, 2); |
| 192 | + |
| 193 | + if (2 === \count($exploded)) { |
| 194 | + [$parent, $child] = $exploded; |
| 195 | + } else { |
| 196 | + return $this->sourceTypeExtractor->getType($source, $property); |
| 197 | + } |
| 198 | + |
| 199 | + $parentType = $this->sourceTypeExtractor->getType($source, $parent); |
| 200 | + |
| 201 | + if ($parentType instanceof Type\ObjectType) { |
| 202 | + /** @var class-string $className */ |
| 203 | + $className = $parentType->getClassName(); |
| 204 | + |
| 205 | + return $this->findLastTypeForAccessor($className, $child); |
| 206 | + } |
| 207 | + |
| 208 | + return $parentType; |
| 209 | + } |
| 210 | + |
| 211 | + /** |
| 212 | + * @param class-string|'array' $class |
| 213 | + */ |
| 214 | + private function doGetReadAccessor(string $class, string $property, bool $allowExtraProperties = false): ?ReadAccessorInterface |
77 | 215 | { |
78 | 216 | if ('array' === $class) { |
79 | 217 | return new ArrayReadAccessor($property); |
@@ -107,7 +245,11 @@ public function getReadAccessor(string $class, string $property, bool $allowExtr |
107 | 245 | ); |
108 | 246 | } |
109 | 247 |
|
110 | | - public function getWriteMutator(string $source, string $target, string $property, array $context = [], bool $allowExtraProperties = false): ?WriteMutatorInterface |
| 248 | + /** |
| 249 | + * @param class-string|'array' $target |
| 250 | + * @param array<string, mixed> $context |
| 251 | + */ |
| 252 | + private function doGetWriteMutator(string $target, string $property, array $context = [], bool $allowExtraProperties = false): ?WriteMutatorInterface |
111 | 253 | { |
112 | 254 | $writeInfo = $this->writeInfoExtractor->getWriteInfo($target, $property, $context); |
113 | 255 |
|
@@ -147,31 +289,4 @@ public function getWriteMutator(string $source, string $target, string $property |
147 | 289 |
|
148 | 290 | return new PropertyWriteMutator($writeInfo->getName(), PropertyReadInfo::VISIBILITY_PUBLIC !== $writeInfo->getVisibility()); |
149 | 291 | } |
150 | | - |
151 | | - public function getCheckExists(string $class, string $property): bool |
152 | | - { |
153 | | - if ('array' === $class || \stdClass::class === $class) { |
154 | | - return true; |
155 | | - } |
156 | | - |
157 | | - return false; |
158 | | - } |
159 | | - |
160 | | - public function getGroups(string $class, string $property): ?array |
161 | | - { |
162 | | - return null; |
163 | | - } |
164 | | - |
165 | | - public function getDateTimeFormat(PropertyMetadataEvent $propertyMetadataEvent): string |
166 | | - { |
167 | | - if (null !== $propertyMetadataEvent->dateTimeFormat) { |
168 | | - return $propertyMetadataEvent->dateTimeFormat; |
169 | | - } |
170 | | - |
171 | | - if (null !== $propertyMetadataEvent->mapperMetadata->dateTimeFormat) { |
172 | | - return $propertyMetadataEvent->mapperMetadata->dateTimeFormat; |
173 | | - } |
174 | | - |
175 | | - return $this->configuration->dateTimeFormat; |
176 | | - } |
177 | 292 | } |
0 commit comments