diff --git a/composer.json b/composer.json index 0a68485..e93db2f 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "ext-json": "*", "symfony/string": "^5.3", "rinsvent/attribute-extractor": "^0.0", + "rinsvent/transformer": "^0.0", "symfony/property-access": "^6.0" }, "require-dev": { diff --git a/src/Attribute/DataPath.php b/src/Attribute/DataPath.php deleted file mode 100644 index f550b21..0000000 --- a/src/Attribute/DataPath.php +++ /dev/null @@ -1,13 +0,0 @@ -map = $map ?? $this->baseMap; } diff --git a/src/Dto2DataConverter.php b/src/Dto2DataConverter.php index c960454..e966d6d 100644 --- a/src/Dto2DataConverter.php +++ b/src/Dto2DataConverter.php @@ -1,321 +1,168 @@ propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() ->disableExceptionOnInvalidPropertyPath() + ->enableMagicMethods() ->getPropertyAccessor(); + $this->transformer = new Transformer(); } - public function getTags(object $object, array $tags = []): array + public function convert($data, string $schemaClass): array { - return $this->processTags($object, $tags); - } - - public function convert($data, array $tags = []): array - { - if (!is_object($data) && !is_iterable($data)) { - throw new \InvalidArgumentException(); + $schema = new $schemaClass(); + if (!$schema instanceof Schema) { + throw new \InvalidArgumentException( + 'Schema should be instance of Rinsvent\DTO2Data\Attribute\Schema' + ); } - return is_iterable($data) - ? $this->convertArray($data, $tags) - : $this->convertObject($data, $tags); + return $this->convertByMap($data, $schema->map); } - public function convertArray(iterable $items, array $tags = []): array + private function convertByMap($data, array $map): array { $result = []; - foreach ($items as $item) { - if (!is_object($item)) { - throw new \InvalidArgumentException(); + if (is_iterable($data)) { + foreach ($data as $item) { + $result[] = $this->processItem($item, $map); } - $result[] = $this->convertObject($item, $tags); + } else { + $result = $this->processItem($data, $map); } return $result; } - public function convertObject(object $object, array $tags = []): array - { - $tags = empty($tags) ? ['default'] : $tags; - $schema = $this->grabSchema($object, $tags); - return $this->convertObjectByMap($object, $schema->map, $tags); - } - - public function convertObjectByMap(object $object, array $map, array $tags = []): array + private function processItem($data, array $map): array { - $data = []; - $reflectionObject = new \ReflectionObject($object); - foreach ($map as $key => $propertyInfo) { + $result = []; + foreach ($map as $key => $item) { try { - $sourceName = is_array($propertyInfo) ? $key : $propertyInfo; - $value = $this->grabValue($object, $sourceName, $tags); - - // Если нет карты, то не сериализуем. - if (is_iterable($value)) { - $childMap = is_array($propertyInfo) ? $propertyInfo : null; - $value = $this->convertArrayByMap($value, $childMap, $tags); - } elseif (is_object($value) && is_array($propertyInfo)) { - $value = $this->convertObjectByMap($value, $propertyInfo, $tags); + switch (true) { + // key -> propertyPath (===). + case is_int($key) && is_string($item): + $result[$item] = $this->grabValue($data, $item); + $result[$item] = $this->transform($data, $item, $result[$item]); + break; + // key -> propertyPath (!==) + case is_string($key) && is_string($item): + $result[$key] = $this->grabValue($data, $item); + $result[$key] = $this->transform($data, $item, $result[$key]); + break; + // key -> recursive data processing + case is_string($key) && is_array($item): + $result[$key] = $this->convertByMap($this->grabValue($data, $key), $item); + break; + // key -> virtual field + case is_string($key) && is_callable($item): + $result[$key] = call_user_func_array($item, [$data]); + break; + // key -> data processing with transformer + case $item instanceof Meta: + case (new $item) instanceof Meta: + $meta = is_string($item) ? new $item : $item; + $result[$key] = $this->transformer->transform($data, $meta); + break; + // key -> recursive data processing with other schema + case $item instanceof Schema: + case (new $item) instanceof Schema: + $schemaClass = is_object($item) ? $item::class : $item; + $result[$key] = $this->convert($data[$key] ?? null, $schemaClass); + break; + default: + $result[$key] = null; } - - $this->processIterationTransformers($object, $sourceName, $value, $tags); - - $dataPath = $this->grabIterationDataPath($object, $sourceName, $tags); - $data[$dataPath] = $value; - } catch (\Throwable $e) { - continue; + } catch (\Throwable) { + $result[$key] = null; } } - $this->processClassTransformers($reflectionObject, $data, $tags); - return $data; - } - - public function convertArrayByMap($data, ?array $map, array $tags = []): ?array - { - $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, string $sourceName, array $tags) - { - if (!method_exists($object, $sourceName) && !property_exists($object, $sourceName)) { - return $this->getValueByPath($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; - } - return $this->getValueByPath($object, $propertyPath->path); - } - return $this->getValueByPath($object, $sourceName); + return $result; } - public function processIterationTransformers(object $object, string $sourceName, &$value, array $tags): void + private function transform(object|array|null $data, string $path, mixed $value): mixed { - if (property_exists($object, $sourceName)) { - $reflectionSource = new ReflectionProperty($object, $sourceName); - $this->processTransformers($reflectionSource, $value, $tags); - } - $getter = $this->grabGetterName($object, $sourceName); - if ($getter) { - $reflectionSource = new ReflectionMethod($object, $getter); - $this->processMethodTransformers($reflectionSource, $value, $tags); + $metas = $this->grabTransformMetas($data, $path); + foreach ($metas as $meta) { + $value = $this->transformer->transform($value, $meta); } + return $value; } - public function grabIterationDataPath(object $object, string $sourceName, array $tags): string + private function grabValue(object|array|null $value, string $path): mixed { - $getter = $this->grabGetterName($object, $sourceName); - if ($getter) { - $reflectionSource = new ReflectionMethod($object, $getter); - $dataPath = $this->grabMethodDataPath($reflectionSource, $tags); - if ($dataPath) { - return $dataPath; - } - } - if (property_exists($object, $sourceName)) { - $reflectionSource = new ReflectionProperty($object, $sourceName); - $dataPath = $this->grabDataPath($reflectionSource, $tags); + if (null === $value) { + return null; } - return $dataPath ?? $sourceName; + $path = is_array($value) && 0 === mb_substr_count($path, '.') && + false === mb_strpos($path, '[') + ? "[{$path}]" + : $path; + return $this->propertyAccessor->getValue($value, $path); } /** - * Получаем теги для обработки + * @return Meta[] */ - protected function processTags(object $object, array $tags): array + private function grabTransformMetas(mixed $data, string $propertyPath): array { - $classExtractor = new ClassExtractor($object::class); - /** @var HandleTags $tagsMeta */ - if ($tagsMeta = $classExtractor->fetch(HandleTags::class)) { - if (method_exists($object, $tagsMeta->method)) { - $reflectionMethod = new ReflectionMethod($object, $tagsMeta->method); - if (!$reflectionMethod->isPublic()) { - $reflectionMethod->setAccessible(true); - } - $methodTags = $reflectionMethod->invoke($object, ...[$tags]); - if (!$reflectionMethod->isPublic()) { - $reflectionMethod->setAccessible(false); - } - return $methodTags; - } - } - - return $tags; - } - - /** - * Трнансформируем на уровне класса - */ - 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; + $result = []; + $propertyName = $propertyPath; + $propertyPathParts = explode('.', $propertyPath); + if (count($propertyPathParts) > 1) { + $propertyName = array_shift($propertyPathParts); + $pathToObject = implode('.', $propertyPathParts); + $object = $this->grabValue($data, $pathToObject); + if (!is_object($object)) { + return []; } - - $transformer = $this->grabTransformer($transformMeta); - $transformer->transform($data, $transformMeta); } - } - /** - * Трнансформируем на уровне свойст объекта - */ - protected function processTransformers(\ReflectionProperty $property, &$data, array $tags): void - { - $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; - } - /** @var \ReflectionNamedType $reflectionPropertyType */ - $reflectionPropertyType = $property->getType(); - $propertyType = $reflectionPropertyType->getName(); - $transformMeta->returnType = $propertyType; - $transformMeta->allowsNull = $reflectionPropertyType->allowsNull(); - $transformer = $this->grabTransformer($transformMeta); - $transformer->transform($data, $transformMeta); + if (!is_object($data)) { + return []; } - } - 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; + if (property_exists($data, $propertyName)) { + $reflectionProperty = new ReflectionProperty($data, $propertyName); + $methodExtractor = new PropertyExtractor($reflectionProperty->class, $propertyName); + while ($meta = $methodExtractor->fetch(Meta::class)) { + $result[] = $meta; } - /** @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); - } - - private function getValueByPath($data, string $path) - { - try { - return $this->propertyAccessor->getValue($data, $path); - } catch (\Throwable $e) { - return null; - } - } - - 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; + if ($result) { + return $result; } - 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; + foreach (['get', 'has', 'is'] as $prefix) { + $methodName = $prefix . ucfirst($propertyName); + if (method_exists($data, $methodName)) { + $reflectionMethod = new \ReflectionMethod($data, $methodName); + $methodExtractor = new MethodExtractor($reflectionMethod->class, $methodName); + while ($meta = $methodExtractor->fetch(Meta::class)) { + $result[] = $meta; + } } - return $dataPath->path; - } - - 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; + if ($result) { + return $result; } - return $dataPath->path; } - return null; - } - - private function grabGetterName(object $object, string $sourceName): ?string - { - $prefixes = ['get', 'has', 'is']; - foreach ($prefixes as $prefix) { - if (method_exists($object, $prefix . ucfirst($sourceName))) { - return $prefix . ucfirst($sourceName); - } - } - return null; + return []; } } diff --git a/src/Resolver/SimpleResolver.php b/src/Resolver/SimpleResolver.php deleted file mode 100644 index c758182..0000000 --- a/src/Resolver/SimpleResolver.php +++ /dev/null @@ -1,16 +0,0 @@ -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]; - } -} \ No newline at end of file diff --git a/src/Transformer/DateTimeFormat.php b/src/Transformer/DateTimeFormat.php deleted file mode 100644 index 527387f..0000000 --- a/src/Transformer/DateTimeFormat.php +++ /dev/null @@ -1,14 +0,0 @@ -format($meta->format); - } -} \ No newline at end of file diff --git a/src/Transformer/Meta.php b/src/Transformer/Meta.php deleted file mode 100644 index b68f3b0..0000000 --- a/src/Transformer/Meta.php +++ /dev/null @@ -1,15 +0,0 @@ -characters); - } -} \ No newline at end of file diff --git a/tests/unit/Converter/FillTest.php b/tests/unit/Converter/FillTest.php index ccf9179..b8823b2 100644 --- a/tests/unit/Converter/FillTest.php +++ b/tests/unit/Converter/FillTest.php @@ -9,6 +9,7 @@ use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\BuyRequest; use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\Collection; use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\CollectionItem; use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\HelloRequest; +use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\HelloSchema; use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\UUID; class FillTest extends \Codeception\Test\Unit @@ -107,7 +108,7 @@ class FillTest extends \Codeception\Test\Unit $helloRequest->setPcreatedAt(new \DateTimeImmutable('2020-05-21 13:36:22')); - $dto = $dto2DataConverter->convert($helloRequest); + $dto = $dto2DataConverter->convert($helloRequest, HelloSchema::class); // codecept_debug(json_encode($dto)); $this->assertEquals([ @@ -134,7 +135,14 @@ class FillTest extends \Codeception\Test\Unit "name" => "Sapkovsky" ] ], - "authors3" => [], + "authors3" => [ + [ + "name" => "Tolkien" + ], + [ + "name" => "Sapkovsky" + ] + ], "buy" => [ "phrase" => "Buy buy!!!", "length" => 10, @@ -180,7 +188,14 @@ class FillTest extends \Codeception\Test\Unit "name" => "Sapkovsky" ] ], - "pauthors3" => [], + "pauthors3" => [ + [ + "name" => "Tolkien" + ], + [ + "name" => "Sapkovsky" + ] + ], "pbuy" => [ "phrase" => "Buy buy!!!", "length" => 10, diff --git a/tests/unit/Converter/TagsTest.php b/tests/unit/Converter/TagsTest.php deleted file mode 100644 index f5babb4..0000000 --- a/tests/unit/Converter/TagsTest.php +++ /dev/null @@ -1,39 +0,0 @@ -surname = ' Surname1234'; - $helloTagsRequest->age = 3; - $helloTagsRequest->emails = [ - 'sfdgsa', - 'af234f', - 'asdf33333' - ]; - $tags = $dto2DataConverter->getTags($helloTagsRequest); - $this->assertEquals(['surname-group'], $tags); - } -} diff --git a/tests/unit/Converter/fixtures/FillTest/HelloRequest.php b/tests/unit/Converter/fixtures/FillTest/HelloRequest.php index 846db83..eab9984 100644 --- a/tests/unit/Converter/fixtures/FillTest/HelloRequest.php +++ b/tests/unit/Converter/fixtures/FillTest/HelloRequest.php @@ -2,17 +2,16 @@ namespace Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest; -use Rinsvent\DTO2Data\Attribute\DataPath; use Rinsvent\DTO2Data\Attribute\PropertyPath; -use Rinsvent\DTO2Data\Transformer\DateTimeFormat; -use Rinsvent\DTO2Data\Transformer\Trim; +use Rinsvent\Transformer\Transformer\DateTimeFormat; +use Rinsvent\Transformer\Transformer\ToString; +use Rinsvent\Transformer\Transformer\Trim; #[HelloSchema] class HelloRequest { #[Trim] public string $surname; - #[DataPath('fake_age')] public int $age; public array $emails; public array $authors; @@ -20,7 +19,7 @@ class HelloRequest public array $authors3; public BuyRequest $buy; public BarInterface $bar; - #[PropertyPath(path: 'uuid.id')] + #[ToString()] public UUID $uuid; public Collection $collection; #[DateTimeFormat] @@ -28,7 +27,6 @@ class HelloRequest #[Trim] private string $psurname; - #[DataPath('pfake_age')] private int $page; private array $pemails; private array $pauthors; @@ -36,7 +34,7 @@ class HelloRequest private array $pauthors3; private BuyRequest $pbuy; private BarInterface $pbar; - #[PropertyPath(path: 'puuid.id')] + #[ToString()] private UUID $puuid; private Collection $pcollection; #[DateTimeFormat] @@ -164,7 +162,6 @@ class HelloRequest } #[Trim] - #[DataPath('fake_Pdevdo')] public function getPdevdo(): string { return ' getPdevdo'; diff --git a/tests/unit/Converter/fixtures/FillTest/HelloSchema.php b/tests/unit/Converter/fixtures/FillTest/HelloSchema.php index b11129b..1914e22 100644 --- a/tests/unit/Converter/fixtures/FillTest/HelloSchema.php +++ b/tests/unit/Converter/fixtures/FillTest/HelloSchema.php @@ -9,7 +9,7 @@ class HelloSchema extends Schema { public ?array $baseMap = [ 'surname', - 'age', + 'fake_age' => 'age', 'emails', 'authors' => [ 'name', @@ -33,7 +33,7 @@ class HelloSchema extends Schema 'createdAt', 'psurname', - 'page', + 'pfake_age' => 'page', 'pemails', 'pauthors' => [ 'name', @@ -56,6 +56,6 @@ class HelloSchema extends Schema ], 'pcreatedAt', - 'pdevdo' + 'fake_Pdevdo' => 'pdevdo' ]; } diff --git a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassData.php b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassData.php index f8352f7..77967ac 100644 --- a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassData.php +++ b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassData.php @@ -2,7 +2,7 @@ namespace Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\Transformer; -use Rinsvent\DTO2Data\Transformer\Meta; +use Rinsvent\Transformer\Transformer\Meta; #[\Attribute] class ClassData extends Meta diff --git a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassDataTransformer.php b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassDataTransformer.php index 774e87b..c0c8664 100644 --- a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassDataTransformer.php +++ b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassDataTransformer.php @@ -2,8 +2,8 @@ namespace Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\Transformer; -use Rinsvent\DTO2Data\Transformer\Meta; -use Rinsvent\DTO2Data\Transformer\TransformerInterface; +use Rinsvent\Transformer\Transformer\Meta; +use Rinsvent\Transformer\Transformer\TransformerInterface; class ClassDataTransformer implements TransformerInterface { @@ -11,14 +11,15 @@ class ClassDataTransformer implements TransformerInterface * @param array|null $data * @param ClassData $meta */ - public function transform(&$data, Meta $meta): void + public function transform(mixed $data, Meta $meta): mixed { if ($data === null) { - return; + return $data; } if (isset($data['surname'])) { $data['surname'] = '123454321'; } + return $data; } } diff --git a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObject.php b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObject.php index 48e8edd..c070b63 100644 --- a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObject.php +++ b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObject.php @@ -2,7 +2,7 @@ namespace Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\Transformer; -use Rinsvent\DTO2Data\Transformer\Meta; +use Rinsvent\Transformer\Transformer\Meta; #[\Attribute] class ClassObject extends Meta diff --git a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObjectTransformer.php b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObjectTransformer.php index 3c4c97a..6d2bfc0 100644 --- a/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObjectTransformer.php +++ b/tests/unit/Converter/fixtures/FillTest/Transformer/ClassObjectTransformer.php @@ -3,8 +3,8 @@ namespace Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\Transformer; use Rinsvent\DTO2Data\Tests\unit\Converter\fixtures\FillTest\HelloClassTransformersRequest2; -use Rinsvent\DTO2Data\Transformer\Meta; -use Rinsvent\DTO2Data\Transformer\TransformerInterface; +use Rinsvent\Transformer\Transformer\Meta; +use Rinsvent\Transformer\Transformer\TransformerInterface; class ClassObjectTransformer implements TransformerInterface { @@ -12,13 +12,14 @@ class ClassObjectTransformer implements TransformerInterface * @param array|null $data * @param ClassData $meta */ - public function transform(&$data, Meta $meta): void + public function transform(mixed $data, Meta $meta): mixed { if ($data === null) { - return; + return $data; } $object = new HelloClassTransformersRequest2(); $object->surname = '98789'; $data = $object; + return $data; } } diff --git a/tests/unit/Converter/fixtures/FillTest/UUID.php b/tests/unit/Converter/fixtures/FillTest/UUID.php index cac0f47..838ee6c 100644 --- a/tests/unit/Converter/fixtures/FillTest/UUID.php +++ b/tests/unit/Converter/fixtures/FillTest/UUID.php @@ -11,4 +11,9 @@ class UUID { $this->id = $id; } + + public function __toString(): string + { + return $this->id; + } }