diff --git a/src/Mapper/Application/Attribute/Accessor.php b/src/Mapper/Application/Attribute/Accessor.php new file mode 100644 index 0000000..209558a --- /dev/null +++ b/src/Mapper/Application/Attribute/Accessor.php @@ -0,0 +1,56 @@ +method; + } + + public function type(): int + { + return $this->type; + } + + public function validate(\Reflector $reflection): void + { + if (!$reflection instanceof \ReflectionProperty) { + throw new UltraMapperAttributeValidationException('The accessor attribute can only be applied to properties.', 'Make sure that the function which initialize a validation do it correctly.'); + } + + if (!method_exists($class = $reflection->getDeclaringClass()->getName(), $this->method)) { + throw new UltraMapperAttributeValidationException(sprintf('The method %s does not exist.', $this->method), 'Make sure that the method '.$this->method.' exists in the class '.$class.'.'); + } + + if (self::SETTER !== $this->type && self::GETTER !== $this->type) { + throw new UltraMapperAttributeValidationException(sprintf('The type %d is not supported.', $this->type), 'The type must be either a setter or a getter.'); + } + } +} diff --git a/src/Mapper/Application/Attribute/ApplyToCollectionItem.php b/src/Mapper/Application/Attribute/ApplyToCollectionItem.php new file mode 100644 index 0000000..9467a6e --- /dev/null +++ b/src/Mapper/Application/Attribute/ApplyToCollectionItem.php @@ -0,0 +1,53 @@ +attributes; + } + + public function validate(\Reflector $reflection): void + { + if (!$reflection instanceof \ReflectionProperty) { + throw new UltraMapperAttributeValidationException('The apply to collection item attribute can only be applied to properties.', 'Make sure that the function which initialize a validation do it correctly.'); + } + + if (empty($this->attributes)) { + throw new UltraMapperAttributeValidationException('The apply to collection item attribute must have at least one attribute.', 'You should remove unused '.__CLASS__.' attribute in '.$reflection->getDeclaringClass()->getName().'::'.$reflection->getName().' property.'); + } + + foreach ($this->attributes as $attribute) { + if ($attribute instanceof UltraMapperAttribute) { + $attribute->validate($reflection); + } + } + } +} diff --git a/src/Mapper/Application/Attribute/Constructor.php b/src/Mapper/Application/Attribute/Constructor.php new file mode 100644 index 0000000..3fffbf6 --- /dev/null +++ b/src/Mapper/Application/Attribute/Constructor.php @@ -0,0 +1,39 @@ +isStatic()) { + throw new UltraMapperAttributeValidationException('The constructor attribute can only be applied to static methods.', 'Make sure that the method '.$reflection->getDeclaringClass()->getName().'::'.$reflection->getName().'() is static.'); + } + + if (!$reflection->isPublic()) { + throw new UltraMapperAttributeValidationException('The constructor attribute can only be applied to public methods.', 'Make sure that the method '.$reflection->getDeclaringClass()->getName().'::'.$reflection->getName().'() is public.'); + } + } +} diff --git a/src/Mapper/Application/Attribute/Discriminator.php b/src/Mapper/Application/Attribute/Discriminator.php new file mode 100644 index 0000000..427e4d1 --- /dev/null +++ b/src/Mapper/Application/Attribute/Discriminator.php @@ -0,0 +1,81 @@ + $map the map of the discriminator values to the class names + * @param int $propertySource where to look for the discriminator property + */ + public function __construct( + private string $property, + private array $map, + private int $propertySource = self::DISCRIMINATOR_PROPERTY_IN_SAME_CLASS, + int $processType = UltraMapperAttribute::PROCESS_DENORMALIZATION | UltraMapperAttribute::PROCESS_NORMALIZATION | UltraMapperAttribute::PROCESS_TRANSFORMATION | UltraMapperAttribute::PROCESS_MAPPING, + array $options = [], + ) { + parent::__construct($processType, $options); + } + + public function property(): string + { + return $this->property; + } + + /** + * Returns the map. + * + * @return array the map + */ + public function map(): array + { + return $this->map; + } + + public function propertySource(): int + { + return $this->propertySource; + } + + public function validate(\Reflector $reflection): void + { + if (!$reflection instanceof \ReflectionProperty) { + throw new UltraMapperAttributeValidationException('The discriminator attribute can only be applied to properties.', 'Make sure that the function which initialize a validation do it correctly.'); + } + + if (!in_array($this->propertySource, [self::DISCRIMINATOR_PROPERTY_IN_SAME_CLASS, self::DISCRIMINATOR_PROPERTY_IN_PARENT_CLASS])) { + throw new UltraMapperAttributeValidationException('The property source must be either in the same class or in the parent class.', 'Make sure that the property source is either in the same class or in the parent class.'); + } + + if (empty($this->map)) { + throw new UltraMapperAttributeValidationException('The map must have at least one entry.', 'Make sure that the map has at least one entry.'); + } + + foreach ($this->map as $discriminatorValue => $class) { + if (!class_exists($class, false)) { + throw new UltraMapperAttributeValidationException(sprintf('The class %s does not exist.', $class), 'Make sure that the class '.$class.' exists.'); + } + + if (self::DISCRIMINATOR_PROPERTY_IN_SAME_CLASS === $this->propertySource && !property_exists($class, $this->property)) { + throw new UltraMapperAttributeValidationException(sprintf('The property %s does not exist in the class %s.', $this->property, $class), 'Make sure that the property '.$this->property.' exists in the class '.$class.'.'); + } + } + + if (self::DISCRIMINATOR_PROPERTY_IN_PARENT_CLASS === $this->propertySource && !property_exists($reflection->getDeclaringClass()->getName(), $this->property)) { + throw new UltraMapperAttributeValidationException(sprintf('The property %s does not exist in the parent class %s.', $this->property, $reflection->getDeclaringClass()->getName()), 'Make sure that the property '.$this->property.' exists in the class '.$reflection->getDeclaringClass()->getName().'.'); + } + } +} diff --git a/src/Mapper/Application/Attribute/UltraMapperAttribute.php b/src/Mapper/Application/Attribute/UltraMapperAttribute.php new file mode 100644 index 0000000..fa303e2 --- /dev/null +++ b/src/Mapper/Application/Attribute/UltraMapperAttribute.php @@ -0,0 +1,57 @@ + $options The options used by Your code or the UltraMapper. For example, the `groups` option + * allows you to specify the groups that the attribute should be applied to. + */ + protected function __construct( + protected int $processType = self::PROCESS_DENORMALIZATION | self::PROCESS_NORMALIZATION | self::PROCESS_TRANSFORMATION | self::PROCESS_MAPPING, + protected array $options = [], + ) { + } + + /** + * Returns the process type. + * + * @return int the process type + */ + public function processType(): int + { + return $this->processType; + } + + /** + * Returns the options. + * + * @return array the options + */ + public function options(): array + { + return $this->options; + } + + /** + * Validates the attribute. + * + * @param \Reflector $reflection the reflection object that the attribute is attached to + * + * @throws UltraMapperAttributeValidationException + */ + abstract public function validate(\Reflector $reflection): void; +} diff --git a/src/Mapper/Application/Exception/UltraMapperAttributeValidationException.php b/src/Mapper/Application/Exception/UltraMapperAttributeValidationException.php new file mode 100644 index 0000000..c9155c9 --- /dev/null +++ b/src/Mapper/Application/Exception/UltraMapperAttributeValidationException.php @@ -0,0 +1,11 @@ +