Compare commits
No commits in common. 'master' and 'c16d2e7e61b02c9a07633a1f5ad8ff723948c20c' have entirely different histories.
master
...
c16d2e7e61
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Attribute; |
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
||||||
|
class DataPath |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
public string $path, |
||||||
|
/** @var string[] $tags */ |
||||||
|
public array $tags = ['default'] |
||||||
|
) {} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Attribute; |
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
||||||
|
class HandleTags |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
public string $method, |
||||||
|
) {} |
||||||
|
} |
@ -1,25 +1,17 @@ |
|||||||
<?php |
<?php |
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
namespace Rinsvent\DTO2Data\Attribute; |
namespace Rinsvent\DTO2Data\Attribute; |
||||||
|
|
||||||
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
||||||
class Schema |
class Schema |
||||||
{ |
{ |
||||||
protected array $baseMap = []; |
public ?array $baseMap = null; |
||||||
|
|
||||||
public function __construct( |
public function __construct( |
||||||
public array $map = [], |
public ?array $map = null, |
||||||
|
/** @var string[] $tags */ |
||||||
|
public array $tags = ['default'] |
||||||
) { |
) { |
||||||
} |
$this->map = $map ?? $this->baseMap; |
||||||
|
|
||||||
public function getMap(): array |
|
||||||
{ |
|
||||||
return $this->baseMap ?: $this->map; |
|
||||||
} |
|
||||||
|
|
||||||
public function getTags(mixed $data = null): array |
|
||||||
{ |
|
||||||
return ['default']; |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,168 +1,370 @@ |
|||||||
<?php |
<?php |
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
namespace Rinsvent\DTO2Data; |
namespace Rinsvent\DTO2Data; |
||||||
|
|
||||||
|
use ReflectionMethod; |
||||||
use ReflectionProperty; |
use ReflectionProperty; |
||||||
|
use Rinsvent\AttributeExtractor\ClassExtractor; |
||||||
use Rinsvent\AttributeExtractor\MethodExtractor; |
use Rinsvent\AttributeExtractor\MethodExtractor; |
||||||
use Rinsvent\AttributeExtractor\PropertyExtractor; |
use Rinsvent\AttributeExtractor\PropertyExtractor; |
||||||
|
use Rinsvent\DTO2Data\Attribute\DataPath; |
||||||
|
use Rinsvent\DTO2Data\Attribute\HandleTags; |
||||||
|
use Rinsvent\DTO2Data\Attribute\PropertyPath; |
||||||
use Rinsvent\DTO2Data\Attribute\Schema; |
use Rinsvent\DTO2Data\Attribute\Schema; |
||||||
use Rinsvent\Transformer\Transformer; |
use Rinsvent\DTO2Data\Resolver\TransformerResolverStorage; |
||||||
use Rinsvent\Transformer\Transformer\Meta; |
use Rinsvent\DTO2Data\Transformer\Meta; |
||||||
use Symfony\Component\PropertyAccess\PropertyAccess; |
use Rinsvent\DTO2Data\Transformer\TransformerInterface; |
||||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; |
|
||||||
|
|
||||||
class Dto2DataConverter |
class Dto2DataConverter |
||||||
{ |
{ |
||||||
private PropertyAccessorInterface $propertyAccessor; |
public function getTags(object $object, array $tags = []): array |
||||||
private Transformer $transformer; |
|
||||||
|
|
||||||
public function __construct() |
|
||||||
{ |
{ |
||||||
$this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() |
return $this->processTags($object, $tags); |
||||||
->disableExceptionOnInvalidPropertyPath() |
|
||||||
->enableMagicMethods() |
|
||||||
->getPropertyAccessor(); |
|
||||||
$this->transformer = new Transformer(); |
|
||||||
} |
} |
||||||
|
|
||||||
public function convert($data, string $schemaClass): array |
public function convert($data, array $tags = []): array |
||||||
{ |
{ |
||||||
$schema = new $schemaClass(); |
if (!is_object($data) && !is_iterable($data)) { |
||||||
if (!$schema instanceof Schema) { |
throw new \InvalidArgumentException(); |
||||||
throw new \InvalidArgumentException( |
|
||||||
'Schema should be instance of Rinsvent\DTO2Data\Attribute\Schema' |
|
||||||
); |
|
||||||
} |
} |
||||||
return $this->convertByMap($data, $schema->getMap(), $schema->getTags($data)); |
return is_iterable($data) |
||||||
|
? $this->convertArray($data, $tags) |
||||||
|
: $this->convertObject($data, $tags); |
||||||
} |
} |
||||||
|
|
||||||
private function convertByMap($data, array $map, array $tags): array |
public function convertArray(iterable $items, array $tags = []): array |
||||||
{ |
{ |
||||||
$result = []; |
$result = []; |
||||||
if (is_iterable($data)) { |
foreach ($items as $item) { |
||||||
foreach ($data as $item) { |
if (!is_object($item)) { |
||||||
$result[] = $this->processItem($item, $map, $tags); |
throw new \InvalidArgumentException(); |
||||||
} |
} |
||||||
} else { |
$result[] = $this->convertObject($item, $tags); |
||||||
$result = $this->processItem($data, $map, $tags); |
|
||||||
} |
} |
||||||
return $result; |
return $result; |
||||||
} |
} |
||||||
|
|
||||||
private function processItem($data, array $map, array $tags): array |
public function convertObject(object $object, array $tags = []): array |
||||||
{ |
{ |
||||||
$result = []; |
$tags = empty($tags) ? ['default'] : $tags; |
||||||
foreach ($map as $key => $item) { |
$schema = $this->grabSchema($object, $tags); |
||||||
try { |
return $this->convertObjectByMap($object, $schema->map, $tags); |
||||||
switch (true) { |
} |
||||||
// key -> propertyPath (===). |
|
||||||
case is_int($key) && is_string($item): |
public function convertObjectByMap(object $object, array $map, array $tags = []): array |
||||||
$result[$item] = $this->grabValue($data, $item); |
{ |
||||||
$result[$item] = $this->transform($data, $item, $result[$item], $tags); |
$data = []; |
||||||
break; |
|
||||||
// key -> propertyPath (!==) |
$reflectionObject = new \ReflectionObject($object); |
||||||
case is_string($key) && is_string($item): |
foreach ($map as $key => $propertyInfo) { |
||||||
$result[$key] = $this->grabValue($data, $item); |
$sourceName = is_array($propertyInfo) ? $key : $propertyInfo; |
||||||
$result[$key] = $this->transform($data, $item, $result[$key], $tags); |
|
||||||
break; |
if (!method_exists($object, $sourceName) && !property_exists($object, $sourceName)) { |
||||||
// key -> recursive data processing |
continue; |
||||||
case is_string($key) && is_array($item): |
} |
||||||
$result[$key] = $this->convertByMap($this->grabValue($data, $key), $item, $tags); |
|
||||||
break; |
$value = $this->grabValue($object, $sourceName, $tags); |
||||||
// key -> virtual field |
|
||||||
case is_string($key) && is_callable($item): |
$canSkip = false; |
||||||
$result[$key] = call_user_func_array($item, [$data]); |
// Если нет карты, то не сериализуем. |
||||||
break; |
if (is_iterable($value)) { |
||||||
// key -> data processing with transformer |
$childMap = is_array($propertyInfo) ? $propertyInfo : null; |
||||||
case $item instanceof Meta: |
$value = $this->convertArrayByMap($value, $childMap, $tags); |
||||||
case (new $item) instanceof Meta: |
} elseif (is_object($value) && is_array($propertyInfo)) { |
||||||
$meta = is_string($item) ? new $item : $item; |
$value = $this->convertObjectByMap($value, $propertyInfo, $tags); |
||||||
$result[$key] = $this->transformer->transform($data, $meta); |
} elseif (!is_scalar($value) && null !== $value) { |
||||||
break; |
$canSkip = true; |
||||||
// key -> recursive data processing with other schema |
} |
||||||
case $item instanceof Schema: |
|
||||||
case (new $item) instanceof Schema: |
$this->processIterationTransformers($object, $sourceName, $value, $tags); |
||||||
$schemaClass = is_object($item) ? $item::class : $item; |
|
||||||
$result[$key] = $this->convert($data[$key] ?? null, $schemaClass); |
if ($canSkip && !is_scalar($value) && null !== $value) { |
||||||
break; |
continue; |
||||||
default: |
} |
||||||
$result[$key] = null; |
|
||||||
|
$dataPath = $this->grabIterationDataPath($object, $sourceName, $tags); |
||||||
|
$data[$dataPath] = $value; |
||||||
|
} |
||||||
|
|
||||||
|
$this->processClassTransformers($reflectionObject, $data, $tags); |
||||||
|
|
||||||
|
return $data; |
||||||
|
} |
||||||
|
|
||||||
|
public function convertArrayByMap($data, ?array $map, array $tags = []): ?array |
||||||
|
{ |
||||||
|
// $isAssociative = count($data) && !array_key_exists(0, $data); |
||||||
|
|
||||||
|
$tempValue = []; |
||||||
|
foreach ($data as $key => $item) { |
||||||
|
if (is_scalar($item)) { |
||||||
|
$tempValue[$key] = $item; |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (is_iterable($item) && $map) { |
||||||
|
$tempValue[$key] = $this->convertArrayByMap($item, $map, $tags); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (is_object($item) && $map) { |
||||||
|
$tempValue[$key] = $this->convertObjectByMap($item, $map, $tags); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
return $tempValue; |
||||||
|
} |
||||||
|
|
||||||
|
protected function grabValue(object $object, $sourceName, array $tags) |
||||||
|
{ |
||||||
|
if (method_exists($object, $sourceName)) { |
||||||
|
$reflectionSource = new ReflectionMethod($object, $sourceName); |
||||||
|
return $this->getMethodValue($object, $reflectionSource); |
||||||
|
} elseif (property_exists($object, $sourceName)) { |
||||||
|
$reflectionSource = new ReflectionProperty($object, $sourceName); |
||||||
|
$propertyExtractor = new PropertyExtractor($object::class, $sourceName); |
||||||
|
/** @var PropertyPath $propertyPath */ |
||||||
|
while ($propertyPath = $propertyExtractor->fetch(PropertyPath::class)) { |
||||||
|
$filteredTags = array_diff($tags, $propertyPath->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
} |
} |
||||||
} catch (\Throwable) { |
return $this->getValueByPath($object, $propertyPath->path); |
||||||
$result[$key] = null; |
|
||||||
} |
} |
||||||
|
return $this->getValue($object, $reflectionSource); |
||||||
} |
} |
||||||
return $result; |
|
||||||
|
return null; |
||||||
} |
} |
||||||
|
|
||||||
private function transform(object|array|null $data, string $path, mixed $value, array $tags): mixed |
public function processIterationTransformers(object $object, string $sourceName, &$value, array $tags): void |
||||||
{ |
{ |
||||||
$metas = $this->grabTransformMetas($data, $path); |
if (method_exists($object, $sourceName)) { |
||||||
foreach ($metas as $meta) { |
$reflectionSource = new ReflectionMethod($object, $sourceName); |
||||||
$value = $this->transformer->transform($value, $meta, $tags); |
$this->processMethodTransformers($reflectionSource, $value, $tags); |
||||||
|
} elseif (property_exists($object, $sourceName)) { |
||||||
|
$reflectionSource = new ReflectionProperty($object, $sourceName); |
||||||
|
$this->processTransformers($reflectionSource, $value, $tags); |
||||||
} |
} |
||||||
return $value; |
|
||||||
} |
} |
||||||
|
|
||||||
private function grabValue(object|array|null $value, string $path): mixed |
public function grabIterationDataPath(object $object, string $sourceName, array $tags): string |
||||||
{ |
{ |
||||||
if (null === $value) { |
if (method_exists($object, $sourceName)) { |
||||||
return null; |
$reflectionSource = new ReflectionMethod($object, $sourceName); |
||||||
|
$dataPath = $this->grabMethodDataPath($reflectionSource, $tags); |
||||||
|
} elseif (property_exists($object, $sourceName)) { |
||||||
|
$reflectionSource = new ReflectionProperty($object, $sourceName); |
||||||
|
$dataPath = $this->grabDataPath($reflectionSource, $tags); |
||||||
} |
} |
||||||
$path = is_array($value) && 0 === mb_substr_count($path, '.') && |
return $dataPath ?? $sourceName; |
||||||
false === mb_strpos($path, '[') |
|
||||||
? "[{$path}]" |
|
||||||
: $path; |
|
||||||
return $this->propertyAccessor->getValue($value, $path); |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* @return Meta[] |
* Получаем теги для обработки |
||||||
*/ |
*/ |
||||||
private function grabTransformMetas(mixed $data, string $propertyPath): array |
protected function processTags(object $object, array $tags): array |
||||||
{ |
{ |
||||||
$result = []; |
$classExtractor = new ClassExtractor($object::class); |
||||||
$propertyName = $propertyPath; |
/** @var HandleTags $tagsMeta */ |
||||||
$propertyPathParts = explode('.', $propertyPath); |
if ($tagsMeta = $classExtractor->fetch(HandleTags::class)) { |
||||||
if (count($propertyPathParts) > 1) { |
if (method_exists($object, $tagsMeta->method)) { |
||||||
$propertyName = array_shift($propertyPathParts); |
$reflectionMethod = new ReflectionMethod($object, $tagsMeta->method); |
||||||
$pathToObject = implode('.', $propertyPathParts); |
if (!$reflectionMethod->isPublic()) { |
||||||
$object = $this->grabValue($data, $pathToObject); |
$reflectionMethod->setAccessible(true); |
||||||
if (!is_object($object)) { |
} |
||||||
return []; |
$methodTags = $reflectionMethod->invoke($object, ...[$tags]); |
||||||
|
if (!$reflectionMethod->isPublic()) { |
||||||
|
$reflectionMethod->setAccessible(false); |
||||||
|
} |
||||||
|
return $methodTags; |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
if (!is_object($data)) { |
return $tags; |
||||||
return []; |
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Трнансформируем на уровне класса |
||||||
|
*/ |
||||||
|
protected function processClassTransformers(\ReflectionObject $object, &$data, array $tags): void |
||||||
|
{ |
||||||
|
$className = $object->getName(); |
||||||
|
$classExtractor = new ClassExtractor($className); |
||||||
|
/** @var Meta $transformMeta */ |
||||||
|
while ($transformMeta = $classExtractor->fetch(Meta::class)) { |
||||||
|
$transformMeta->returnType = $className; |
||||||
|
$filteredTags = array_diff($tags, $transformMeta->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$transformer = $this->grabTransformer($transformMeta); |
||||||
|
$transformer->transform($data, $transformMeta); |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
if (property_exists($data, $propertyName)) { |
/** |
||||||
$reflectionProperty = new ReflectionProperty($data, $propertyName); |
* Трнансформируем на уровне свойст объекта |
||||||
$methodExtractor = new PropertyExtractor($reflectionProperty->class, $propertyName); |
*/ |
||||||
while ($meta = $methodExtractor->fetch(Meta::class)) { |
protected function processTransformers(\ReflectionProperty $property, &$data, array $tags): void |
||||||
$result[] = $meta; |
{ |
||||||
|
$propertyName = $property->getName(); |
||||||
|
$propertyExtractor = new PropertyExtractor($property->class, $propertyName); |
||||||
|
/** @var Meta $transformMeta */ |
||||||
|
while ($transformMeta = $propertyExtractor->fetch(Meta::class)) { |
||||||
|
$filteredTags = array_diff($tags, $transformMeta->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
} |
} |
||||||
if ($result) { |
/** @var \ReflectionNamedType $reflectionPropertyType */ |
||||||
return $result; |
$reflectionPropertyType = $property->getType(); |
||||||
|
$propertyType = $reflectionPropertyType->getName(); |
||||||
|
$transformMeta->returnType = $propertyType; |
||||||
|
$transformMeta->allowsNull = $reflectionPropertyType->allowsNull(); |
||||||
|
$transformer = $this->grabTransformer($transformMeta); |
||||||
|
$transformer->transform($data, $transformMeta); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function processMethodTransformers(ReflectionMethod $method, &$data, array $tags): void |
||||||
|
{ |
||||||
|
$methodName = $method->getName(); |
||||||
|
$methodExtractor = new MethodExtractor($method->class, $methodName); |
||||||
|
/** @var Meta $transformMeta */ |
||||||
|
while ($transformMeta = $methodExtractor->fetch(Meta::class)) { |
||||||
|
$filteredTags = array_diff($tags, $transformMeta->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
} |
} |
||||||
|
/** @var \ReflectionNamedType $reflectionMethodType */ |
||||||
|
$reflectionMethodType = $method->getReturnType(); |
||||||
|
$methodType = $reflectionMethodType->getName(); |
||||||
|
$transformMeta->returnType = $methodType; |
||||||
|
$transformMeta->allowsNull = $reflectionMethodType->allowsNull(); |
||||||
|
$transformer = $this->grabTransformer($transformMeta); |
||||||
|
$transformer->transform($data, $transformMeta); |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function grabTransformer(Meta $meta): TransformerInterface |
||||||
|
{ |
||||||
|
$storage = TransformerResolverStorage::getInstance(); |
||||||
|
$resolver = $storage->get($meta::TYPE); |
||||||
|
return $resolver->resolve($meta); |
||||||
|
} |
||||||
|
|
||||||
foreach (['get', 'has', 'is'] as $prefix) { |
private function getValueByPath($data, string $path) |
||||||
$methodName = $prefix . ucfirst($propertyName); |
{ |
||||||
if (method_exists($data, $methodName)) { |
$parts = explode('.', $path); |
||||||
$reflectionMethod = new \ReflectionMethod($data, $methodName); |
$length = count($parts); |
||||||
$methodExtractor = new MethodExtractor($reflectionMethod->class, $methodName); |
$i = 1; |
||||||
while ($meta = $methodExtractor->fetch(Meta::class)) { |
foreach ($parts as $part) { |
||||||
$result[] = $meta; |
// Если получили скалярное значение но прошли не весь путь, то вернем null |
||||||
|
if (is_scalar($data) && $i < $length) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
// Если объекс реализует ArrayAccess, то получаем значение и идем дальше |
||||||
|
if (is_object($data) && $data instanceof \ArrayAccess) { |
||||||
|
$data = $data[$part] ?? null; |
||||||
|
continue; |
||||||
|
} |
||||||
|
// Если объект, то достаем значение |
||||||
|
if (is_object($data)) { |
||||||
|
if (!property_exists($data, $part)) { |
||||||
|
return null; |
||||||
} |
} |
||||||
|
$property = new ReflectionProperty($data, $part); |
||||||
|
$data = $this->getValue($data, $property); |
||||||
|
continue; |
||||||
|
} |
||||||
|
// Если массив, то достаем занчение и идем дальше |
||||||
|
if (is_array($data)) { |
||||||
|
$data = $data[$part] ?? null; |
||||||
|
continue; |
||||||
|
} |
||||||
|
$i++; |
||||||
|
} |
||||||
|
|
||||||
|
return $data; |
||||||
|
} |
||||||
|
|
||||||
|
private function getValue(object $object, \ReflectionProperty $property) |
||||||
|
{ |
||||||
|
if (!$property->isPublic()) { |
||||||
|
$property->setAccessible(true); |
||||||
|
} |
||||||
|
|
||||||
|
if (!$property->isInitialized($object)) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$value = $property->getValue($object); |
||||||
|
|
||||||
|
if (!$property->isPublic()) { |
||||||
|
$property->setAccessible(false); |
||||||
|
} |
||||||
|
|
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
private function getMethodValue(object $object, ReflectionMethod $method) |
||||||
|
{ |
||||||
|
if (!$method->isPublic()) { |
||||||
|
$method->setAccessible(true); |
||||||
|
} |
||||||
|
|
||||||
|
$value = $method->invoke($object); |
||||||
|
|
||||||
|
if (!$method->isPublic()) { |
||||||
|
$method->setAccessible(false); |
||||||
|
} |
||||||
|
|
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
private function grabSchema(object $object, array $tags): ?Schema |
||||||
|
{ |
||||||
|
$classExtractor = new ClassExtractor($object::class); |
||||||
|
/** @var Schema $schema */ |
||||||
|
while ($schema = $classExtractor->fetch(Schema::class)) { |
||||||
|
$filteredTags = array_diff($tags, $schema->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
return $schema; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private function grabDataPath(\ReflectionProperty $property, array $tags): ?string |
||||||
|
{ |
||||||
|
$propertyName = $property->getName(); |
||||||
|
$propertyExtractor = new PropertyExtractor($property->class, $propertyName); |
||||||
|
/** @var DataPath $schema */ |
||||||
|
while ($dataPath = $propertyExtractor->fetch(DataPath::class)) { |
||||||
|
$filteredTags = array_diff($tags, $dataPath->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
} |
} |
||||||
if ($result) { |
return $dataPath->path; |
||||||
return $result; |
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private function grabMethodDataPath(ReflectionMethod $method, array $tags): ?string |
||||||
|
{ |
||||||
|
$methodName = $method->getName(); |
||||||
|
$methodExtractor = new MethodExtractor($method->class, $methodName); |
||||||
|
/** @var DataPath $schema */ |
||||||
|
while ($dataPath = $methodExtractor->fetch(DataPath::class)) { |
||||||
|
$filteredTags = array_diff($tags, $dataPath->tags); |
||||||
|
if (count($filteredTags) === count($tags)) { |
||||||
|
continue; |
||||||
} |
} |
||||||
|
return $dataPath->path; |
||||||
} |
} |
||||||
|
|
||||||
return []; |
return null; |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Resolver; |
||||||
|
|
||||||
|
use Rinsvent\DTO2Data\Transformer\Meta; |
||||||
|
use Rinsvent\DTO2Data\Transformer\TransformerInterface; |
||||||
|
|
||||||
|
class SimpleResolver implements TransformerResolverInterface |
||||||
|
{ |
||||||
|
public function resolve(Meta $meta): TransformerInterface |
||||||
|
{ |
||||||
|
$metaClass = $meta::class; |
||||||
|
$transformerClass = $metaClass . 'Transformer'; |
||||||
|
return new $transformerClass; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Resolver; |
||||||
|
|
||||||
|
use Rinsvent\DTO2Data\Transformer\Meta; |
||||||
|
use Rinsvent\DTO2Data\Transformer\TransformerInterface; |
||||||
|
|
||||||
|
interface TransformerResolverInterface |
||||||
|
{ |
||||||
|
public function resolve(Meta $meta): TransformerInterface; |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Resolver; |
||||||
|
|
||||||
|
class TransformerResolverStorage |
||||||
|
{ |
||||||
|
private array $items = []; |
||||||
|
|
||||||
|
public static function getInstance(): self |
||||||
|
{ |
||||||
|
static $instance = null; |
||||||
|
|
||||||
|
if ($instance) { |
||||||
|
return $instance; |
||||||
|
} |
||||||
|
|
||||||
|
$instance = new self(); |
||||||
|
$instance->add('simple', new SimpleResolver()); |
||||||
|
|
||||||
|
return $instance; |
||||||
|
} |
||||||
|
|
||||||
|
public function add(string $code, TransformerResolverInterface $transformerResolver): void |
||||||
|
{ |
||||||
|
$this->items[$code] = $transformerResolver; |
||||||
|
} |
||||||
|
|
||||||
|
public function get(string $code): TransformerResolverInterface |
||||||
|
{ |
||||||
|
return $this->items[$code]; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Transformer; |
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
||||||
|
class DateTimeFormat extends Meta |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
public array $tags = ['default'], |
||||||
|
public string $format = \DateTimeInterface::ATOM |
||||||
|
) { |
||||||
|
parent::__construct(...func_get_args()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Transformer; |
||||||
|
|
||||||
|
class DateTimeFormatTransformer implements TransformerInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @param \DateTimeImmutable|\DateTime|null $data |
||||||
|
* @param DateTimeFormat $meta |
||||||
|
*/ |
||||||
|
public function transform(&$data, Meta $meta): void |
||||||
|
{ |
||||||
|
if ($data === null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
$data = $data->format($meta->format); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Transformer; |
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
||||||
|
abstract class Meta |
||||||
|
{ |
||||||
|
public const TYPE = 'simple'; |
||||||
|
public ?string $returnType = null; |
||||||
|
public ?bool $allowsNull = null; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
public array $tags = ['default'] |
||||||
|
) {} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Transformer; |
||||||
|
|
||||||
|
interface TransformerInterface |
||||||
|
{ |
||||||
|
public function transform(&$data, Meta $meta): void; |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Transformer; |
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_ALL|\Attribute::IS_REPEATABLE)] |
||||||
|
class Trim extends Meta |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
public array $tags = ['default'], |
||||||
|
public string $characters = " \t\n\r\0\x0B" |
||||||
|
) { |
||||||
|
parent::__construct(...func_get_args()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Transformer; |
||||||
|
|
||||||
|
class TrimTransformer implements TransformerInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @param string|null $data |
||||||
|
* @param Trim $meta |
||||||
|
*/ |
||||||
|
public function transform(&$data, Meta $meta): void |
||||||
|
{ |
||||||
|
if ($data === null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
$data = trim($data, $meta->characters); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Tests\Converter; |
||||||
|
|
||||||
|
use Rinsvent\DTO2Data\Dto2DataConverter; |
||||||
|
use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\HelloTagsRequest; |
||||||
|
|
||||||
|
class TagsTest extends \Codeception\Test\Unit |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var \UnitTester |
||||||
|
*/ |
||||||
|
protected $tester; |
||||||
|
|
||||||
|
protected function _before() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
protected function _after() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// tests |
||||||
|
public function testSuccessFillRequestData() |
||||||
|
{ |
||||||
|
$dto2DataConverter = new Dto2DataConverter(); |
||||||
|
|
||||||
|
$helloTagsRequest = new HelloTagsRequest(); |
||||||
|
$helloTagsRequest->surname = ' Surname1234'; |
||||||
|
$helloTagsRequest->age = 3; |
||||||
|
$helloTagsRequest->emails = [ |
||||||
|
'sfdgsa', |
||||||
|
'af234f', |
||||||
|
'asdf33333' |
||||||
|
]; |
||||||
|
$tags = $dto2DataConverter->getTags($helloTagsRequest); |
||||||
|
$this->assertEquals(['surname-group'], $tags); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest; |
||||||
|
|
||||||
|
use Rinsvent\DTO2Data\Attribute\DataPath; |
||||||
|
use Rinsvent\DTO2Data\Attribute\HandleTags; |
||||||
|
|
||||||
|
#[HandleTags(method: 'getTags')] |
||||||
|
class HelloTagsRequest extends HelloRequest |
||||||
|
{ |
||||||
|
#[DataPath('fake_age2', tags: ['surname-group'])] |
||||||
|
public int $age; |
||||||
|
|
||||||
|
public function getTags(array $tags) |
||||||
|
{ |
||||||
|
return 'Surname1234' === trim(($this->surname ?? '')) ? ['surname-group'] : $tags; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue