Skip to content

Commit

Permalink
feat: Added some attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykbaszak committed Aug 16, 2024
1 parent ee1df74 commit 7a3d5d7
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 0 deletions.
56 changes: 56 additions & 0 deletions src/Mapper/Application/Attribute/Accessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace PBaszak\UltraMapper\Mapper\Application\Attribute;

use Attribute;
use PBaszak\UltraMapper\Mapper\Application\Exception\UltraMapperAttributeValidationException;
use UltraMapper\Mapper\Application\Attribute\UltraMapperAttribute;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
final class Accessor extends UltraMapperAttribute
{
public const SETTER = 1;
public const GETTER = 2;

/**
* The accessor attribute allows you to define your own accessor function that will be executed during the mapping process.
*
* @param string $method The method name that will be executed. It must be a public method inside same class as property.
* @param int $type The type of the accessor. It can be either a setter or a getter.
*/
public function __construct(
private string $method,
private int $type = self::GETTER,
int $processType = UltraMapperAttribute::PROCESS_DENORMALIZATION | UltraMapperAttribute::PROCESS_NORMALIZATION | UltraMapperAttribute::PROCESS_TRANSFORMATION | UltraMapperAttribute::PROCESS_MAPPING,
array $options = [],
) {
parent::__construct($processType, $options);
}

public function method(): string
{
return $this->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.');
}
}
}
53 changes: 53 additions & 0 deletions src/Mapper/Application/Attribute/ApplyToCollectionItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace PBaszak\UltraMapper\Mapper\Application\Attribute;

use Attribute;
use PBaszak\UltraMapper\Mapper\Application\Exception\UltraMapperAttributeValidationException;
use UltraMapper\Mapper\Application\Attribute\UltraMapperAttribute;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
final class ApplyToCollectionItem extends UltraMapperAttribute
{
/**
* The apply to collection item attribute allows you to apply the attribute to the collection item.
*
* @param object[] $attributes the attributes that will be applied to the collection item
*/
public function __construct(
private array $attributes,
int $processType = UltraMapperAttribute::PROCESS_DENORMALIZATION | UltraMapperAttribute::PROCESS_NORMALIZATION | UltraMapperAttribute::PROCESS_TRANSFORMATION | UltraMapperAttribute::PROCESS_MAPPING,
array $options = [],
) {
parent::__construct($processType, $options);
}

/**
* Returns the attributes.
*
* @return object[] the attributes
*/
public function attributes(): array
{
return $this->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);
}
}
}
}
39 changes: 39 additions & 0 deletions src/Mapper/Application/Attribute/Constructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace PBaszak\UltraMapper\Mapper\Application\Attribute;

use Attribute;
use PBaszak\UltraMapper\Mapper\Application\Exception\UltraMapperAttributeValidationException;
use UltraMapper\Mapper\Application\Attribute\UltraMapperAttribute;

#[\Attribute(\Attribute::TARGET_METHOD)]
final class Constructor extends UltraMapperAttribute
{
/**
* The constructor attribute allows You to define static constructor
* method that will be executed during the mapping process.
*/
public function __construct(
int $processType = UltraMapperAttribute::PROCESS_DENORMALIZATION | UltraMapperAttribute::PROCESS_NORMALIZATION | UltraMapperAttribute::PROCESS_TRANSFORMATION | UltraMapperAttribute::PROCESS_MAPPING,
array $options = [],
) {
parent::__construct($processType, $options);
}

public function validate(\Reflector $reflection): void
{
if (!$reflection instanceof \ReflectionMethod) {
throw new UltraMapperAttributeValidationException('The constructor attribute can only be applied to methods.', 'Make sure that the function which initialize a validation do it correctly.');
}

if (!$reflection->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.');
}
}
}
81 changes: 81 additions & 0 deletions src/Mapper/Application/Attribute/Discriminator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace UltraMapper\Mapper\Application\Attribute;

use Attribute;
use PBaszak\UltraMapper\Mapper\Application\Exception\UltraMapperAttributeValidationException;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
final class Discriminator extends UltraMapperAttribute
{
public const DISCRIMINATOR_PROPERTY_IN_SAME_CLASS = 1;
public const DISCRIMINATOR_PROPERTY_IN_PARENT_CLASS = 2;

/**
* The discriminator attribute allows You to define how to solve multiple types of objects in one property.
*
* @param string $property the property name that will be used as a discriminator
* @param array<int|string, class-string> $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<int|string, class-string> 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().'.');
}
}
}
57 changes: 57 additions & 0 deletions src/Mapper/Application/Attribute/UltraMapperAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace UltraMapper\Mapper\Application\Attribute;

use PBaszak\UltraMapper\Mapper\Application\Exception\UltraMapperAttributeValidationException;

abstract class UltraMapperAttribute
{
public const PROCESS_DENORMALIZATION = 1;
public const PROCESS_NORMALIZATION = 2;
public const PROCESS_TRANSFORMATION = 4;
public const PROCESS_MAPPING = 8;

public const OPTION_GROUPS = 'groups';

/**
* @param int $processType the process type that the attribute should be applied to
* @param array<string, mixed> $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<string, mixed> 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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace PBaszak\UltraMapper\Mapper\Application\Exception;

use PBaszak\UltraMapper\Shared\Application\Exception\UltraMapperException;

class UltraMapperAttributeValidationException extends UltraMapperException
{
}

0 comments on commit 7a3d5d7

Please sign in to comment.