|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +namespace Liip\MetadataParser\Metadata; |
| 6 | + |
| 7 | +final class PropertyTypeUnion extends AbstractPropertyType |
| 8 | +{ |
| 9 | + private const DEFAULT_ORDER = 8; |
| 10 | + |
| 11 | + /** |
| 12 | + * @var array<string, string> |
| 13 | + */ |
| 14 | + private array $typeMap = []; |
| 15 | + |
| 16 | + private ?string $fieldName = null; |
| 17 | + |
| 18 | + /** |
| 19 | + * @var PropertyType[] |
| 20 | + */ |
| 21 | + private array $types; |
| 22 | + |
| 23 | + /** |
| 24 | + * @param PropertyType[] $types |
| 25 | + */ |
| 26 | + public function __construct(array $types, bool $nullable) |
| 27 | + { |
| 28 | + parent::__construct($nullable); |
| 29 | + |
| 30 | + $this->setTypes($types); |
| 31 | + } |
| 32 | + |
| 33 | + /** |
| 34 | + * @return PropertyType[] |
| 35 | + */ |
| 36 | + public function getTypes(): array |
| 37 | + { |
| 38 | + return $this->types; |
| 39 | + } |
| 40 | + |
| 41 | + /** |
| 42 | + * @param PropertyType[] $types |
| 43 | + */ |
| 44 | + public function setTypes(array $types): void |
| 45 | + { |
| 46 | + $this->types = $this->reorderTypes($types); |
| 47 | + } |
| 48 | + |
| 49 | + public function getTypeByClassName(string $className): ?PropertyTypeClass |
| 50 | + { |
| 51 | + foreach ($this->types as $type) { |
| 52 | + if (!$type instanceof PropertyTypeClass) { |
| 53 | + continue; |
| 54 | + } |
| 55 | + |
| 56 | + if ($type->getClassName() === $className) { |
| 57 | + return $type; |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + return null; |
| 62 | + } |
| 63 | + |
| 64 | + public function setTypeMap(array $typeMap): void |
| 65 | + { |
| 66 | + $this->typeMap = $typeMap; |
| 67 | + } |
| 68 | + |
| 69 | + public function getTypeMap(): array |
| 70 | + { |
| 71 | + return $this->typeMap; |
| 72 | + } |
| 73 | + |
| 74 | + public function setFieldName(string $fieldName): void |
| 75 | + { |
| 76 | + $this->fieldName = $fieldName; |
| 77 | + } |
| 78 | + |
| 79 | + public function getFieldName(): ?string |
| 80 | + { |
| 81 | + return $this->fieldName; |
| 82 | + } |
| 83 | + |
| 84 | + public function __toString(): string |
| 85 | + { |
| 86 | + $classTypes = array_map( |
| 87 | + static fn (PropertyType $type): string => $type->__toString(), |
| 88 | + $this->types |
| 89 | + ); |
| 90 | + |
| 91 | + return implode('|', $classTypes); |
| 92 | + } |
| 93 | + |
| 94 | + public function merge(PropertyType $other): PropertyType |
| 95 | + { |
| 96 | + if (!$other instanceof self) { |
| 97 | + throw new \UnexpectedValueException(\sprintf('Can\'t merge type %s with %s, they must be the same', self::class, \get_class($other))); |
| 98 | + } |
| 99 | + |
| 100 | + $mergedTypes = [...$this->getTypes(), ...$other->getTypes()]; |
| 101 | + |
| 102 | + $mergedPropertyType = new self($mergedTypes, $this->isNullable() && $other->isNullable()); |
| 103 | + |
| 104 | + $mergedTypeMap = [...$this->getTypeMap(), ...$other->getTypeMap()]; |
| 105 | + $mergedPropertyType->setTypeMap($mergedTypeMap); |
| 106 | + |
| 107 | + $fieldName = null; |
| 108 | + if (null === $other->getFieldName()) { |
| 109 | + $fieldName = $this->fieldName; |
| 110 | + } elseif (null === $this->getfieldName()) { |
| 111 | + $fieldName = $other->getFieldName(); |
| 112 | + } elseif ($other->getFieldName() !== $this->getfieldName()) { |
| 113 | + throw new \UnexpectedValueException(\sprintf('Can\'t merge type union type with field name %s with other union type with field name %s', $this->getFieldName(), $other->getFieldName())); |
| 114 | + } |
| 115 | + |
| 116 | + $mergedPropertyType->setFieldName($fieldName); |
| 117 | + |
| 118 | + return $mergedPropertyType; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Sorting the types by primitives first and then by class will make it easier when (de-)serializing the values. |
| 123 | + * |
| 124 | + * @param PropertyType[] $types |
| 125 | + * |
| 126 | + * @return PropertyType[] |
| 127 | + */ |
| 128 | + private function reorderTypes(array $types): array |
| 129 | + { |
| 130 | + uasort($types, static function (PropertyType $first, PropertyType $second) { |
| 131 | + $order = ['null' => 0, 'array' => 1, 'true' => 2, 'false' => 3, 'bool' => 4, 'int' => 5, 'float' => 6, 'string' => 7]; |
| 132 | + $firstTypeName = $first instanceof PropertyTypeIterable ? 'array' : null; |
| 133 | + $secondTypeName = $second instanceof PropertyTypeIterable ? 'array' : null; |
| 134 | + if ($first instanceof PropertyTypePrimitive) { |
| 135 | + $firstTypeName = $first->getTypeName(); |
| 136 | + } |
| 137 | + |
| 138 | + if ($second instanceof PropertyTypePrimitive) { |
| 139 | + $secondTypeName = $second->getTypeName(); |
| 140 | + } |
| 141 | + |
| 142 | + $firstOrder = $order[$firstTypeName] ?? self::DEFAULT_ORDER; |
| 143 | + $secondOrder = $order[$secondTypeName] ?? self::DEFAULT_ORDER; |
| 144 | + |
| 145 | + return $firstOrder <=> $secondOrder; |
| 146 | + }); |
| 147 | + |
| 148 | + return array_values($types); |
| 149 | + } |
| 150 | +} |
0 commit comments