Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
91.03% |
71 / 78 |
|
72.73% |
8 / 11 |
CRAP | |
0.00% |
0 / 1 |
| ApieSerializerContext | |
91.03% |
71 / 78 |
|
72.73% |
8 / 11 |
42.22 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| denormalizeFromTypehint | |
87.50% |
14 / 16 |
|
0.00% |
0 / 1 |
13.33 | |||
| denormalizeFromParameter | |
80.00% |
16 / 20 |
|
0.00% |
0 / 1 |
9.65 | |||
| denormalizeFromMethod | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
8 | |||
| denormalizeChildElement | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| normalizeAgain | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| normalizeChildElement | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| createChildContext | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
| visit | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| getParentState | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getContext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | namespace Apie\Serializer\Context; |
| 3 | |
| 4 | use Apie\Core\Attributes\Context; |
| 5 | use Apie\Core\Context\ApieContext; |
| 6 | use Apie\Core\ContextConstants; |
| 7 | use Apie\Core\Exceptions\IndexNotFoundException; |
| 8 | use Apie\Core\Exceptions\InvalidTypeException; |
| 9 | use Apie\Core\Lists\ItemHashmap; |
| 10 | use Apie\Core\Lists\ItemList; |
| 11 | use Apie\Core\Metadata\Concerns\UseContextKey; |
| 12 | use Apie\Core\TypeUtils; |
| 13 | use Apie\Core\Utils\ConverterUtils; |
| 14 | use Apie\Serializer\Exceptions\ValidationException; |
| 15 | use Apie\Serializer\FieldFilters\FieldFilterInterface; |
| 16 | use Apie\Serializer\FieldFilters\NoFiltering; |
| 17 | use Apie\Serializer\Relations\EmbedRelationInterface; |
| 18 | use Apie\Serializer\Relations\NoRelationEmbedded; |
| 19 | use Apie\Serializer\Serializer; |
| 20 | use Apie\TypeConverter\Exceptions\CanNotConvertObjectToUnionException; |
| 21 | use Exception; |
| 22 | use ReflectionIntersectionType; |
| 23 | use ReflectionMethod; |
| 24 | use ReflectionNamedType; |
| 25 | use ReflectionParameter; |
| 26 | use ReflectionType; |
| 27 | use ReflectionUnionType; |
| 28 | |
| 29 | final class ApieSerializerContext |
| 30 | { |
| 31 | use UseContextKey; |
| 32 | |
| 33 | private ?ApieSerializerContext $parentState = null; |
| 34 | |
| 35 | public function __construct(private Serializer $serializer, private ApieContext $apieContext) |
| 36 | { |
| 37 | } |
| 38 | |
| 39 | public function denormalizeFromTypehint(mixed $input, ReflectionType|null $typehint): mixed |
| 40 | { |
| 41 | if ($input === null && (!$typehint || $typehint->allowsNull())) { |
| 42 | return null; |
| 43 | } |
| 44 | if ($typehint instanceof ReflectionIntersectionType) { |
| 45 | throw new InvalidTypeException($typehint, 'ReflectionNamedType|ReflectionUnionType|null'); |
| 46 | } |
| 47 | if ($typehint instanceof ReflectionUnionType) { |
| 48 | $exceptions = []; |
| 49 | foreach ($typehint->getTypes() as $type) { |
| 50 | try { |
| 51 | return $this->serializer->denormalizeNewObject($input, $type->getName(), $this->apieContext); |
| 52 | } catch (Exception $exception) { |
| 53 | $exceptions[$type->getName()] = $exception; |
| 54 | } |
| 55 | } |
| 56 | throw new CanNotConvertObjectToUnionException($input, $exceptions, $typehint); |
| 57 | } |
| 58 | if ($typehint instanceof ReflectionNamedType) { |
| 59 | // edge case, should probably work differently then this |
| 60 | if ($input === '' && $typehint->allowsNull() && !TypeUtils::allowEmptyString($typehint) && $this->apieContext->hasContext(ContextConstants::CMS)) { |
| 61 | return null; |
| 62 | } |
| 63 | return $this->serializer->denormalizeNewObject($input, $typehint->getName(), $this->apieContext); |
| 64 | } |
| 65 | return $this->serializer->denormalizeNewObject($input, 'mixed', $this->apieContext); |
| 66 | } |
| 67 | |
| 68 | public function denormalizeFromParameter(ItemHashmap $input, ReflectionParameter $parameter): mixed |
| 69 | { |
| 70 | $key = $parameter->getName(); |
| 71 | $type = $parameter->getType(); |
| 72 | if ($parameter->getAttributes(Context::class)) { |
| 73 | $contextKey = $this->getContextKey($this->apieContext, $parameter, false); |
| 74 | $contextValue = $this->apieContext->getContext( |
| 75 | $contextKey, |
| 76 | !$parameter->isDefaultValueAvailable() |
| 77 | ) ?? $parameter->getDefaultValue(); |
| 78 | if ($type) { |
| 79 | return ConverterUtils::dynamicCast($contextValue, $type); |
| 80 | } |
| 81 | return $contextValue; |
| 82 | } |
| 83 | if (!$parameter->isOptional() && !isset($input[$key])) { |
| 84 | throw new IndexNotFoundException($key); |
| 85 | } |
| 86 | $defaultValue = null; |
| 87 | if ($parameter->isDefaultValueAvailable()) { |
| 88 | $defaultValue = $parameter->getDefaultValue(); |
| 89 | } |
| 90 | if ($type === null || ((string) $type) === 'mixed' || !isset($input[$key])) { |
| 91 | return $input[$key] ?? $defaultValue; |
| 92 | } |
| 93 | $newContext = $this->visit($key); |
| 94 | return $newContext->denormalizeFromTypehint($input[$key], $type); |
| 95 | } |
| 96 | |
| 97 | public function denormalizeFromMethod(mixed $input, ReflectionMethod $method): array |
| 98 | { |
| 99 | if (! ($input instanceof ItemHashmap)) { |
| 100 | $input = $this->serializer->denormalizeNewObject($input, ItemHashmap::class, $this->apieContext); |
| 101 | } |
| 102 | $result = []; |
| 103 | $validationErrors = []; |
| 104 | // this construction is for performance reasons as it maintains only one try catch context. |
| 105 | $todo = $method->getParameters(); |
| 106 | while (!empty($todo)) { |
| 107 | try { |
| 108 | while (!empty($todo)) { |
| 109 | $parameter = array_shift($todo); |
| 110 | if ($parameter->isVariadic()) { |
| 111 | foreach (($input[$parameter->name] ?? []) as $variadicValue) { |
| 112 | $copy = $input; |
| 113 | $copy[$parameter->name] = $variadicValue; |
| 114 | $result[] = $this->denormalizeFromParameter($copy, $parameter); |
| 115 | } |
| 116 | } else { |
| 117 | $result[] = $this->denormalizeFromParameter($input, $parameter); |
| 118 | } |
| 119 | } |
| 120 | } catch (Exception $error) { |
| 121 | assert(isset($parameter)); |
| 122 | $validationErrors[$parameter->name] = $error; |
| 123 | } |
| 124 | } |
| 125 | if (!empty($validationErrors)) { |
| 126 | throw ValidationException::createFromArray($validationErrors); |
| 127 | } |
| 128 | return $result; |
| 129 | } |
| 130 | |
| 131 | public function denormalizeChildElement(string $key, mixed $input, string $desiredType): mixed |
| 132 | { |
| 133 | $newContext = $this->createChildContext($key); |
| 134 | return $this->serializer->denormalizeNewObject($input, $desiredType, $newContext); |
| 135 | } |
| 136 | |
| 137 | public function normalizeAgain(mixed $object, bool $forceDefaultNormalization = false): string|int|float|bool|ItemList|ItemHashmap|null |
| 138 | { |
| 139 | return $this->serializer->normalize($object, $this->apieContext, $forceDefaultNormalization); |
| 140 | } |
| 141 | |
| 142 | public function normalizeChildElement(string $key, mixed $object): string|int|float|bool|ItemList|ItemHashmap|null |
| 143 | { |
| 144 | $newContext = $this->createChildContext($key); |
| 145 | return $this->serializer->normalize($object, $newContext); |
| 146 | } |
| 147 | |
| 148 | private function createChildContext(string $key): ApieContext |
| 149 | { |
| 150 | $context = $this->apieContext; |
| 151 | $hierarchy = []; |
| 152 | if ($context->hasContext('hierarchy')) { |
| 153 | $hierarchy = $context->getContext('hierarchy'); |
| 154 | } |
| 155 | $hierarchy[] = $key; |
| 156 | $fieldFilter = $context->getContext(FieldFilterInterface::class, false) ? : new NoFiltering(); |
| 157 | $context = $context->withContext(FieldFilterInterface::class, $fieldFilter->followField($key)); |
| 158 | |
| 159 | $relationFilter = $context->getContext(EmbedRelationInterface::class, false) ? : new NoRelationEmbedded(); |
| 160 | $context = $context->withContext(EmbedRelationInterface::class, $relationFilter->followField($key)); |
| 161 | |
| 162 | return $context->withContext('hierarchy', $hierarchy); |
| 163 | } |
| 164 | |
| 165 | public function visit(string $key): self |
| 166 | { |
| 167 | $childContext = $this->createChildContext($key); |
| 168 | $res = new ApieSerializerContext($this->serializer, $childContext); |
| 169 | $res->parentState = $this; |
| 170 | return $res; |
| 171 | } |
| 172 | |
| 173 | public function getParentState(): ?self |
| 174 | { |
| 175 | return $this->parentState; |
| 176 | } |
| 177 | |
| 178 | public function getContext(): ApieContext |
| 179 | { |
| 180 | return $this->apieContext; |
| 181 | } |
| 182 | } |