diff --git a/output.yaml b/output.yaml index ed5e053..56d00fd 100644 --- a/output.yaml +++ b/output.yaml @@ -1,92 +1,112 @@ main: PBaszak\DedicatedMapper\Tests\assets\Dummy classes: PBaszak\DedicatedMapper\Tests\assets\Dummy: - attributes: { } - className: PBaszak\DedicatedMapper\Tests\assets\Dummy - options: { } - properties: - id: - attributes: - - - class: Symfony\Component\Validator\Constraints\NotBlank - arguments: { } - instance: "\\Symfony\\Component\\Validator\\Constraints\\NotBlank::__set_state(array(\n 'payload' => NULL,\n 'message' => 'This value should not be blank.',\n 'allowNull' => false,\n 'normalizer' => NULL,\n))" - - - class: Symfony\Component\Serializer\Annotation\Groups - arguments: - - test - instance: "\\Symfony\\Component\\Serializer\\Annotation\\Groups::__set_state(array(\n 'groups' => \n array (\n 0 => 'test',\n ),\n))" - name: id - options: { } - type: - types: - - string - innerType: null - nullable: false - union: false - intersection: false - class: false - collection: false - simpleObject: false - name: - attributes: - - - class: Symfony\Component\Validator\Constraints\Length - arguments: - min: 3 - max: 255 - instance: "\\Symfony\\Component\\Validator\\Constraints\\Length::__set_state(array(\n 'payload' => NULL,\n 'maxMessage' => 'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.',\n 'minMessage' => 'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.',\n 'exactMessage' => 'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.',\n 'charsetMessage' => 'This value does not match the expected {{ charset }} charset.',\n 'max' => 255,\n 'min' => 3,\n 'charset' => 'UTF-8',\n 'normalizer' => NULL,\n 'countUnit' => 'codepoints',\n))" - - - class: Symfony\Component\Serializer\Annotation\Groups - arguments: - - test - instance: "\\Symfony\\Component\\Serializer\\Annotation\\Groups::__set_state(array(\n 'groups' => \n array (\n 0 => 'test',\n ),\n))" - name: name - options: { } - type: - types: - - string - innerType: null - nullable: false - union: false - intersection: false - class: false - collection: false - simpleObject: false - description: - attributes: { } - name: description - options: { } - type: - types: - - string - innerType: null - nullable: false - union: false - intersection: false - class: false - collection: false - simpleObject: false - _embedded: - attributes: - - - class: Symfony\Component\Validator\Constraints\Valid - arguments: { } - instance: "\\Symfony\\Component\\Validator\\Constraints\\Valid::__set_state(array(\n 'payload' => NULL,\n 'traverse' => true,\n))" - - - class: Symfony\Component\Serializer\Annotation\Groups - arguments: - - test - instance: "\\Symfony\\Component\\Serializer\\Annotation\\Groups::__set_state(array(\n 'groups' => \n array (\n 0 => 'test',\n ),\n))" - name: _embedded - options: { } - type: - types: - - PBaszak\DedicatedMapper\Tests\assets\EmbeddedDTO - innerType: null - nullable: false - union: false - intersection: false - class: true - collection: false - simpleObject: false + classType: PBaszak\DedicatedMapper\Reflection\Type\ClassType + reflection: + attributes: { } + className: PBaszak\DedicatedMapper\Tests\assets\Dummy + file: + path: /app/tests/assets/Dummy.php + hash: 24ba2ede85a4491930ed05f368b3816c + options: { } + properties: + id: + attributes: + - + class: Symfony\Component\Validator\Constraints\NotBlank + arguments: { } + instance: "\\Symfony\\Component\\Validator\\Constraints\\NotBlank::__set_state(array(\n 'payload' => NULL,\n 'message' => 'This value should not be blank.',\n 'allowNull' => false,\n 'normalizer' => NULL,\n))" + - + class: Symfony\Component\Serializer\Annotation\Groups + arguments: + - test + instance: "\\Symfony\\Component\\Serializer\\Annotation\\Groups::__set_state(array(\n 'groups' => \n array (\n 0 => 'test',\n ),\n))" + name: id + options: { } + type: + classType: PBaszak\DedicatedMapper\Reflection\Type\Type + types: + - string + innerType: null + nullable: false + union: false + intersection: false + class: false + collection: false + simpleObject: false + name: + attributes: + - + class: Symfony\Component\Validator\Constraints\Length + arguments: + min: 3 + max: 255 + instance: "\\Symfony\\Component\\Validator\\Constraints\\Length::__set_state(array(\n 'payload' => NULL,\n 'maxMessage' => 'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.',\n 'minMessage' => 'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.',\n 'exactMessage' => 'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.',\n 'charsetMessage' => 'This value does not match the expected {{ charset }} charset.',\n 'max' => 255,\n 'min' => 3,\n 'charset' => 'UTF-8',\n 'normalizer' => NULL,\n 'countUnit' => 'codepoints',\n))" + - + class: Symfony\Component\Serializer\Annotation\Groups + arguments: + - test + instance: "\\Symfony\\Component\\Serializer\\Annotation\\Groups::__set_state(array(\n 'groups' => \n array (\n 0 => 'test',\n ),\n))" + name: name + options: { } + type: + classType: PBaszak\DedicatedMapper\Reflection\Type\Type + types: + - string + innerType: null + nullable: false + union: false + intersection: false + class: false + collection: false + simpleObject: false + description: + attributes: { } + name: description + options: { } + type: + classType: PBaszak\DedicatedMapper\Reflection\Type\Type + types: + - string + innerType: null + nullable: false + union: false + intersection: false + class: false + collection: false + simpleObject: false + _embedded: + attributes: + - + class: Symfony\Component\Validator\Constraints\Valid + arguments: { } + instance: "\\Symfony\\Component\\Validator\\Constraints\\Valid::__set_state(array(\n 'payload' => NULL,\n 'traverse' => true,\n))" + - + class: Symfony\Component\Serializer\Annotation\Groups + arguments: + - test + instance: "\\Symfony\\Component\\Serializer\\Annotation\\Groups::__set_state(array(\n 'groups' => \n array (\n 0 => 'test',\n ),\n))" + name: _embedded + options: { } + type: + classType: PBaszak\DedicatedMapper\Reflection\Type\Type + types: + - \PBaszak\DedicatedMapper\Tests\assets\EmbeddedDTO + innerType: null + nullable: false + union: false + intersection: false + class: true + collection: false + simpleObject: false + type: + classType: PBaszak\DedicatedMapper\Reflection\Type\Type + types: + - \PBaszak\DedicatedMapper\Tests\assets\Dummy + innerType: null + nullable: false + union: false + intersection: false + class: true + collection: false + simpleObject: false diff --git a/src/Config.php b/src/Config.php index 99c5bf8..8f077d1 100644 --- a/src/Config.php +++ b/src/Config.php @@ -32,11 +32,10 @@ public function __construct(protected string $className) public function reflect(): self { ClassType::$classTypes = []; - (new TypeFactory(new ReflectionFactory()))->createClassType( - new \ReflectionClass($this->className), - null + $type = (new TypeFactory(new ReflectionFactory()))->createFromReflection( + new \ReflectionClass($this->className) ); - + ClassType::create($type); $this->classes = ClassType::$classTypes; return $this; diff --git a/src/Reflection/AttributeReflection.php b/src/Reflection/AttributeReflection.php index 9ba1c78..1c7b6ad 100644 --- a/src/Reflection/AttributeReflection.php +++ b/src/Reflection/AttributeReflection.php @@ -5,10 +5,8 @@ namespace PBaszak\DedicatedMapper\Reflection; use ArrayObject; -use PBaszak\DedicatedMapper\Reflection\Type\CollectionType; -use PBaszak\DedicatedMapper\Reflection\Type\SimpleObjectType; -class AttributeReflection +class AttributeReflection implements ReflectionInterface { public function toArray(): array { @@ -24,27 +22,27 @@ public function toArray(): array public function __construct( /** - * @var ClassReflection|CollectionType|PropertyReflection|SimpleObjectType $parent each attribute must have resource + * @var ClassReflection|PropertyReflection $parent each attribute must have resource */ - protected ClassReflection|CollectionType|PropertyReflection|SimpleObjectType $parent, + protected ClassReflection|PropertyReflection $parent, /** - * @var ArrayObject $attributes + * @var ArrayObject $attributes */ protected ArrayObject $attributes, ) { } /** - * @return ClassReflection|CollectionType|PropertyReflection|SimpleObjectType + * @return ClassReflection|PropertyReflection */ - public function getParent(): ClassReflection|CollectionType|PropertyReflection|SimpleObjectType + public function getParent(): ClassReflection|PropertyReflection { return $this->parent; } /** - * @return ArrayObject + * @return ArrayObject */ public function getAttributes(): ArrayObject { @@ -54,7 +52,7 @@ public function getAttributes(): ArrayObject /** * @param class-string $class * - * @return null|object{"class": string, "arguments": mixed[]} + * @return null|object{"class": string, "arguments": mixed[], "instance": ?object} */ public function getAttribute(string $class): ?object { diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index dd48a00..96710aa 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -6,17 +6,19 @@ use ArrayObject; use PBaszak\DedicatedMapper\Reflection\Type\ClassType; -use PBaszak\DedicatedMapper\Utils\ToArrayTrait; use ReflectionClass; -use Serializable; -class ClassReflection +class ClassReflection implements ReflectionInterface { public function toArray(): array { return [ 'attributes' => $this->attributes->toArray(), 'className' => $this->reflection?->getName(), + 'file' => [ + 'path' => $this->reflection?->getFileName(), + 'hash' => $this->reflection?->getFileName() ? md5_file($this->reflection->getFileName()) : null, + ], 'options' => $this->options->toArray(), 'properties' => array_map(fn (PropertyReflection $property) => $property->toArray(), $this->properties->getArrayCopy()), ]; diff --git a/src/Reflection/PropertyReflection.php b/src/Reflection/PropertyReflection.php index f36f150..5cfd284 100644 --- a/src/Reflection/PropertyReflection.php +++ b/src/Reflection/PropertyReflection.php @@ -5,9 +5,8 @@ namespace PBaszak\DedicatedMapper\Reflection; use PBaszak\DedicatedMapper\Reflection\Type\TypeInterface; -use PBaszak\DedicatedMapper\Utils\ToArrayTrait; -class PropertyReflection +class PropertyReflection implements ReflectionInterface { public function toArray(): array { diff --git a/src/Reflection/ReflectionFactory.php b/src/Reflection/ReflectionFactory.php index 2e47017..1abc437 100644 --- a/src/Reflection/ReflectionFactory.php +++ b/src/Reflection/ReflectionFactory.php @@ -50,7 +50,7 @@ public function createPropertyReflection(\ReflectionProperty $reflection, ClassR $attributes = new AttributeReflection($instance, $this->getAttributesFromReflection($reflection)); $ref->getProperty('attributes')->setValue($instance, $attributes); $ref->getProperty('options')->setValue($instance, new Options()); - $ref->getProperty('type')->setValue($instance, (new TypeFactory($this))->createType($instance)); + $ref->getProperty('type')->setValue($instance, (new TypeFactory($this))->createFromReflection($reflection, $instance)); return $instance; } diff --git a/src/Reflection/ReflectionInterface.php b/src/Reflection/ReflectionInterface.php new file mode 100644 index 0000000..fb0d7d7 --- /dev/null +++ b/src/Reflection/ReflectionInterface.php @@ -0,0 +1,10 @@ +getTypes() as $t) { - if (class_exists($t, false)) { - return true; - } - } + $classTypes = $type->getClassTypes(); + + return count($classTypes) === 1; } - public static function create(Type $type): TypeInterface + public static function create(Type $type): self { - + if (!self::supports($type)) { + throw new LogicException('Given Type does not support class type.'); + } + + $class = $type->getClassTypes()[0]; + $ref = new \ReflectionClass(self::class); + /** @var ClassType $instance */ + $instance = $ref->newInstanceWithoutConstructor(); + $ref->getProperty('type')->setValue($instance, $type); + $reflection = (new ReflectionFactory())->createClassReflection($class, $instance); + $ref->getProperty('reflection')->setValue($instance, $reflection); + + self::storeClassType($instance); + + return $instance; } public function toArray(): array { - return $this->reflection?->toArray(); + return [ + 'classType' => self::class, + 'reflection' => $this->reflection?->toArray(), + 'type' => $this->type->toArray(), + ]; } public function __construct( @@ -52,11 +69,11 @@ public function __construct( * @var ClassReflection $reflection */ protected ClassReflection $reflection, - + /** - * @var PropertyReflection|TypeInterface|null $parent if `null`, then it is root class + * @var Type $type */ - protected null|PropertyReflection|TypeInterface $parent = null, + protected Type $type, ) {} /** @@ -68,10 +85,10 @@ public function getReflection(): null|ClassReflection } /** - * @return PropertyReflection|TypeInterface|null + * @return Type */ - public function getParent(): null|PropertyReflection|TypeInterface + public function getType(): Type { - return $this->parent; + return $this->type; } } diff --git a/src/Reflection/Type/CollectionType.php b/src/Reflection/Type/CollectionType.php index 101d50a..e693788 100644 --- a/src/Reflection/Type/CollectionType.php +++ b/src/Reflection/Type/CollectionType.php @@ -14,16 +14,22 @@ class CollectionType implements TypeInterface public function toArray(): array { return [ - 'children' => array_map(fn (TypeInterface $child) => $child->toArray(), $this->children->getArrayCopy()), 'attributes' => $this->attributes->toArray(), + 'classType' => self::class, + 'type' => $this->type->toArray(), ]; } - public static function supports(PropertyReflection $property, Type $type, int $depth): bool + public static function supports(Type $type): bool { return $type->isCollection(); } + public static function create(Type $type): TypeInterface + { + + } + public function __construct( /** * @var null|PropertyReflection|TypeInterface $parent @@ -32,9 +38,9 @@ public function __construct( protected null|PropertyReflection|TypeInterface $parent, /** - * @var ArrayObject $children + * @var Type $type */ - protected ArrayObject $children, + protected Type $type, /** * @var AttributeReflection $attributes @@ -52,11 +58,11 @@ public function getParent(): null|PropertyReflection|TypeInterface } /** - * @return ArrayObject + * @return Type */ - public function getChildren(): ArrayObject + public function getType(): Type { - return $this->children; + return $this->type; } /** diff --git a/src/Reflection/Type/CompoundType.php b/src/Reflection/Type/CompoundType.php new file mode 100644 index 0000000..21176d0 --- /dev/null +++ b/src/Reflection/Type/CompoundType.php @@ -0,0 +1,50 @@ + $types + */ + protected array $types = []; + + public static function supports(Type $type): bool + { + return $type->isUnion(); + } + + public static function create(Type $type): TypeInterface + { + if (!self::supports($type)) { + throw new LogicException('Given Type does not support compound type.'); + } + + return new self($type); + } + + public function __construct( + protected Type $type, + ) { + if (!$type->isUnion()) { + throw new InvalidArgumentException('Given type is not a union type.'); + } + + foreach ($type->getTypes() as $type) { + $this->types[] = TypeFactory::create($type); + } + } + + public function toArray(): array + { + return [ + 'classType' => self::class, + 'type' => $this->type->toArray(), + ]; + } +} diff --git a/src/Reflection/Type/Type.php b/src/Reflection/Type/Type.php index 78f4b78..2470f0e 100644 --- a/src/Reflection/Type/Type.php +++ b/src/Reflection/Type/Type.php @@ -5,16 +5,15 @@ namespace PBaszak\DedicatedMapper\Reflection\Type; use PBaszak\DedicatedMapper\Reflection\PropertyReflection; -use PBaszak\DedicatedMapper\Utils\ToArrayTrait; use phpDocumentor\Reflection\Type as PhpDocumentorType; use ReflectionType; class Type implements TypeInterface { /** - * @var PropertyReflection|TypeInterface $parent each type must have resource + * @var null|PropertyReflection|TypeInterface $parent */ - protected PropertyReflection|TypeInterface $parent; + protected null|PropertyReflection|TypeInterface $parent; /** * @var array $types @@ -65,10 +64,21 @@ class Type implements TypeInterface * @var null|PhpDocumentorType $phpDocumentorReflectionType */ protected null|PhpDocumentorType $phpDocumentorReflectionType = null; - + + public static function supports(self $type): bool + { + return true; + } + + public static function create(Type $type): TypeInterface + { + return $type; + } + public function toArray(): array { return [ + 'classType' => self::class, 'types' => $this->types, 'innerType' => $this->innerType?->toArray(), 'nullable' => $this->nullable, @@ -80,30 +90,36 @@ public function toArray(): array ]; } - public static function supports(PropertyReflection $property, self $type, int $depth): bool + /** + * @return null|PropertyReflection|TypeInterface + */ + public function getParent(): null|PropertyReflection|TypeInterface { - return true; + return $this->parent; } - public static function create(PropertyReflection|TypeInterface $parent, Type $type): TypeInterface + /** + * @return array + */ + public function getTypes(): array { - return $type; + return $this->types; } /** - * @return CollectionType|PropertyReflection|SimpleObjectType + * @return array */ - public function getParent(): CollectionType|PropertyReflection|SimpleObjectType + public function getClassTypes(): array { - return $this->parent; + return array_filter($this->types, fn (string $type) => class_exists($type, false)); } /** * @return array */ - public function getTypes(): array + public function getScalarTypes(): array { - return $this->types; + return array_filter($this->types, fn (string $type) => !class_exists($type, false)); } /** diff --git a/src/Reflection/Type/TypeFactory.php b/src/Reflection/Type/TypeFactory.php index d87884c..d36f3cb 100644 --- a/src/Reflection/Type/TypeFactory.php +++ b/src/Reflection/Type/TypeFactory.php @@ -4,21 +4,23 @@ namespace PBaszak\DedicatedMapper\Reflection\Type; -use PBaszak\DedicatedMapper\Attribute\SimpleObject; use PBaszak\DedicatedMapper\Reflection\PropertyReflection; use PBaszak\DedicatedMapper\Reflection\ReflectionFactory; use phpDocumentor\Reflection\DocBlock\Tags\Var_; use phpDocumentor\Reflection\DocBlockFactory; +use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\Type as PhpDocumentorType; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\AbstractList; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\ContextFactory; use phpDocumentor\Reflection\Types\Intersection; use phpDocumentor\Reflection\Types\Null_; use phpDocumentor\Reflection\Types\Nullable; -use ReflectionIntersectionType; -use ReflectionNamedType; -use ReflectionUnionType; +use ReflectionClass; +use ReflectionProperty; +use ReflectionType; +use Reflector; use RuntimeException; class TypeFactory @@ -26,9 +28,10 @@ class TypeFactory protected ReflectionFactory $reflectionFactory; public const AVAILABLE_TYPES_BY_PRIORITY = [ - CollectionType::class, SimpleObjectType::class, + CollectionType::class, ClassType::class, + CompoundType::class, Type::class, ]; @@ -37,117 +40,154 @@ public function __construct(ReflectionFactory $reflectionFactory = null) $this->reflectionFactory = $reflectionFactory ?? new ReflectionFactory(); } - public function createClassType(\ReflectionClass $reflection, null|PropertyReflection|TypeInterface $parent): ClassType + public function createFromReflection(Reflector $reflection, null|PropertyReflection|TypeInterface $parent = null): TypeInterface { - $ref = new \ReflectionClass(ClassType::class); - /** @var ClassType $instance */ - $instance = $ref->newInstanceWithoutConstructor(); - $ref->getProperty('reflection')->setValue($instance, $this->reflectionFactory->createClassReflection($reflection->getName(), $instance)); - $ref->getProperty('parent')->setValue($instance, $parent); + if ($reflection instanceof ReflectionClass) { + return $this->createFromString($reflection->getName(), $reflection, $parent); + } + if ($reflection instanceof ReflectionProperty) { + if ($docComment = $reflection->getDocComment()) { + $phpDocType = $this->createFromDocComment($docComment, $reflection, $parent); + } + if ($typehint = $reflection->getType()) { + $reflectionType = $this->createFromString((string) $typehint, $reflection, $parent); + } - ClassType::storeClassType($instance); + if (!$docComment && !$typehint) { + $phpDocType = $this->createFromString('mixed', $reflection, $parent); + } - return $instance; + return $this->combineTypes(...array_filter([$phpDocType ?? null, $reflectionType ?? null])); + } + if ($reflection instanceof ReflectionType) { + return $this->createFromString((string) $reflection, $reflection, $parent); + } + + throw new RuntimeException('Unsupported reflection type.'); } - - public function createType(PropertyReflection $propertyInstanceWithoutType, int $depth = 0, ?Type $parentInstance = null): TypeInterface + + public function createFromDocComment(string $docComment, Reflector $reflection, null|PropertyReflection|TypeInterface $parent = null): TypeInterface { - if (null === ($ref = $propertyInstanceWithoutType->getReflection())) { - throw new \Exception('Reflection is null.'); + /** @var Var_[] $varTags */ + $varTags = DocBlockFactory::createInstance()->create( + $docComment, + (new ContextFactory)->createFromReflector($reflection) + )->getTagsByName('var'); + /** @var null|PhpDocumentorType $phpDocumentatorType */ + $phpDocumentatorType = $varTags[0]->getType() ?? null; + + if (null === $phpDocumentatorType) { + throw new RuntimeException('Property has no type. Docblock `@var` or typehint is required.'); } - $type = 0 === $depth ? $ref->getType() : null; - $phpDocumentatorType = null; - if (false !== ($docComment = $ref->getDocComment())) { - /** @var Var_[] $varTags */ - $varTags = DocBlockFactory::createInstance()->create( - $docComment, - (new ContextFactory)->createFromReflector($ref) - )->getTagsByName('var'); - - /** @var null|PhpDocumentorType $phpDocumentatorType */ - $phpDocumentatorType = $varTags[0]->getType() ?? null; - if ($phpDocumentatorType && $depth > 0) { - for ($i = 0; $i > $depth; $i++) { - /** @var AbstractList $phpDocumentatorType It means that we are in collection type */ - $phpDocumentatorType = $phpDocumentatorType->getValueType(); - } - } - } + return $this->createFromString((string) $phpDocumentatorType, $reflection, $parent); + } - if (null === $type && null === $phpDocumentatorType) { - throw new RuntimeException('Property has no type. Docblock `@var` or typehint is required.'); + /** + * @param string|class-string $type + * @param null|\ReflectionClass|ReflectionProperty|ReflectionType $reflection for Context to resolve namespaces + */ + public function createFromString(string $type, ?Reflector $reflection = null, null|PropertyReflection|TypeInterface $parent = null): TypeInterface + { + $reflection = class_exists($type, false) ? null : $reflection; + $resolver = new TypeResolver(new FqsenResolver()); + + $phpDocumentatorType = $resolver->resolve($type, $reflection ? (new ContextFactory)->createFromReflector($reflection) : null); + + $instance = $this->createFromPhpDocumentatorType($phpDocumentatorType, $parent); + + if ($reflection) { + $ref = new ReflectionClass(Type::class); + $ref->getProperty('reflectionType')->setValue( + $instance, + match (get_class($reflection)) { + ReflectionProperty::class => $reflection->getType(), + ReflectionType::class => $reflection, + default => null, + } + ); } + + return $instance; + } + + protected function createFromPhpDocumentatorType(PhpDocumentorType $type, null|PropertyReflection|TypeInterface $parent = null): TypeInterface + { $ref = new \ReflectionClass(Type::class); /** @var Type $instance */ $instance = $ref->newInstanceWithoutConstructor(); - $ref->getProperty('reflectionType')->setValue($instance, $type); - $ref->getProperty('phpDocumentorReflectionType')->setValue($instance, $phpDocumentatorType); - $ref->getProperty('parent')->setValue($instance, $parentInstance ?? $propertyInstanceWithoutType); - + $ref->getProperty('parent')->setValue($instance, $parent); + $types = []; - if (null !== $type) { - if ($type->allowsNull()) { - $types[] = 'null'; - $ref->getProperty('nullable')->setValue($instance, true); - } - if ($type instanceof ReflectionNamedType) { - $types[] = $type->getName(); - if (class_exists($type->getName(), false)) { - $ref->getProperty('class')->setValue($instance, true); - } - } - if ($type instanceof ReflectionUnionType) { - $types = array_merge($types, array_map(fn (ReflectionNamedType $t) => $t->getName(), $type->getTypes())); - $ref->getProperty('union')->setValue($instance, true); - } - if ($type instanceof ReflectionIntersectionType) { - $types = array_merge($types, array_map(fn (ReflectionNamedType $t) => $t->getName(), $type->getTypes())); - $ref->getProperty('intersection')->setValue($instance, true); - } + if ($type instanceof Nullable || $type instanceof Null_) { + $types[] = 'null'; + $ref->getProperty('nullable')->setValue($instance, true); + $type = $type->getActualType(); } - if (null !== $phpDocumentatorType) { - if ($phpDocumentatorType instanceof Nullable || $phpDocumentatorType instanceof Null_) { - $types[] = 'null'; - $ref->getProperty('nullable')->setValue($instance, true); - $phpDocumentatorType = $phpDocumentatorType->getActualType(); - } - if ($phpDocumentatorType instanceof Compound) { - $types = array_merge($types, array_map(fn (PhpDocumentorType $t) => (string) $t, $phpDocumentatorType->getIterator()->getArrayCopy())); - $ref->getProperty('union')->setValue($instance, true); - } elseif ($phpDocumentatorType instanceof Intersection) { - $types = array_merge($types, array_map(fn (PhpDocumentorType $t) => (string) $t, $phpDocumentatorType->getIterator()->getArrayCopy())); - $ref->getProperty('intersection')->setValue($instance, true); - } elseif ($phpDocumentatorType instanceof AbstractList) { - if (method_exists($phpDocumentatorType, 'getFqsen')) { - $fqsen = $phpDocumentatorType->getFqsen(); - if ($fqsen && class_exists((string) $fqsen, false)) { - $ref->getProperty('class')->setValue($instance, true); - } - } - $types[] = $fqsen ? (string) $fqsen : 'array'; - $ref->getProperty('collection')->setValue($instance, true); - $ref->getProperty('innerTypes')->setValue($instance, $this->createType($propertyInstanceWithoutType, $depth + 1, $instance)); - } else { - $t = (string) $phpDocumentatorType; - if (class_exists($t, false)) { - $types[] = $t; + if ($type instanceof Compound) { + $types = array_merge($types, array_map(fn (PhpDocumentorType $t) => (string) $t, $type->getIterator()->getArrayCopy())); + $ref->getProperty('union')->setValue($instance, true); + } elseif ($type instanceof Intersection) { + $types = array_merge($types, array_map(fn (PhpDocumentorType $t) => (string) $t, $type->getIterator()->getArrayCopy())); + $ref->getProperty('intersection')->setValue($instance, true); + } elseif ($type instanceof AbstractList) { + if (method_exists($type, 'getFqsen')) { + $fqsen = $type->{'getFqsen'}(); + if ($fqsen && class_exists((string) $fqsen, false)) { $ref->getProperty('class')->setValue($instance, true); } } + $types[] = $fqsen ? (string) $fqsen : 'array'; + $ref->getProperty('collection')->setValue($instance, true); + $ref->getProperty('innerTypes')->setValue($instance, $this->createFromPhpDocumentatorType($type->getValueType(), $instance)); + } else { + $t = (string) $type; + if (class_exists($t, false)) { + $ref->getProperty('class')->setValue($instance, true); + } + $types[] = $t; } $types = array_unique($types); $ref->getProperty('types')->setValue($instance, $types); - foreach (self::AVAILABLE_TYPES_BY_PRIORITY as $typeClass) { - if ($typeClass::supports($instance, $depth)) { - $instance = $typeClass::create($instance); - break; + return $this->resolveTypeInterface($instance); + } + + protected function combineTypes(TypeInterface ...$types): TypeInterface + { + $types = array_filter($types); + $types = array_map(fn(TypeInterface $type): Type => method_exists($type, 'getType') ? $type->{'getType'}() : $type, $types); + $ref = new \ReflectionClass(Type::class); + /** @var Type $instance */ + $instance = $ref->newInstanceWithoutConstructor(); + + foreach ($types as $type) { + $ref->getProperty('parent')->setValue($instance, $type->getParent()); + $instanceTypes = $ref->getProperty('types')->isInitialized($instance) ? $ref->getProperty('types')->getValue($instance) : []; + $ref->getProperty('types')->setValue($instance, array_merge($instanceTypes, $type->getTypes())); + $instanceInnerType = $ref->getProperty('innerType')->getValue($instance); + if (null === $instanceInnerType) { + $ref->getProperty('innerType')->setValue($instance, $type->getInnerType()); + } elseif (null !== $type->getInnerType()) { + $ref->getProperty('innerType')->setValue($instance, $this->combineTypes($instanceInnerType, $type->getInnerType())); } + $ref->getProperty('nullable')->setValue($instance, $type->isNullable() || $ref->getProperty('nullable')->getValue($instance)); + $ref->getProperty('union')->setValue($instance, $type->isUnion() || $ref->getProperty('union')->getValue($instance)); + $ref->getProperty('intersection')->setValue($instance, $type->isIntersection() || $ref->getProperty('intersection')->getValue($instance)); + $ref->getProperty('collection')->setValue($instance, $type->isCollection() || $ref->getProperty('collection')->getValue($instance)); + $ref->getProperty('class')->setValue($instance, $type->isClass() || $ref->getProperty('class')->getValue($instance)); + $ref->getProperty('simpleObject')->setValue($instance, $type->isSimpleObject() || $ref->getProperty('simpleObject')->getValue($instance)); + $ref->getProperty('phpDocumentorReflectionType')->setValue($instance, $type->getPhpDocumentorReflectionType() ?? $ref->getProperty('phpDocumentorReflectionType')->getValue($instance)); + $ref->getProperty('reflectionType')->setValue($instance, $type->getReflectionType() ?? $ref->getProperty('reflectionType')->getValue($instance)); } + + return $this->resolveTypeInterface($instance); + } - return $instance; + protected function resolveTypeInterface(Type $type): TypeInterface + { + return $type; } } diff --git a/src/Reflection/Type/TypeInterface.php b/src/Reflection/Type/TypeInterface.php index 62c85e4..9e6c4bb 100644 --- a/src/Reflection/Type/TypeInterface.php +++ b/src/Reflection/Type/TypeInterface.php @@ -4,11 +4,9 @@ namespace PBaszak\DedicatedMapper\Reflection\Type; -use PBaszak\DedicatedMapper\Reflection\PropertyReflection; - interface TypeInterface { public function toArray(): array; - public static function supports(Type $type): bool; - public static function create(Type $type): TypeInterface; + // public static function supports(Type $type): bool; + // public static function create(Type $type): TypeInterface; }