Compare commits

..

No commits in common. "master" and "v0.0.13" have entirely different histories.

25 changed files with 4588 additions and 262 deletions

View File

@ -8,8 +8,7 @@
"ext-iconv": "*",
"ext-json": "*",
"symfony/string": "^5.3",
"rinsvent/attribute-extractor": "^0.0",
"rinsvent/transformer": "^0."
"rinsvent/attribute-extractor": "^0.0"
},
"require-dev": {
"codeception/codeception": "^4.1",

4417
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
namespace Rinsvent\Data2DTO\Attribute;
#[\Attribute(\Attribute::IS_REPEATABLE|\Attribute::TARGET_ALL)]
#[\Attribute]
class DTOMeta
{
public function __construct(

View File

@ -2,7 +2,7 @@
namespace Rinsvent\Data2DTO\Attribute;
#[\Attribute(\Attribute::IS_REPEATABLE|\Attribute::TARGET_ALL)]
#[\Attribute]
class PropertyPath
{
public function __construct(

View File

@ -2,8 +2,8 @@
namespace Rinsvent\Data2DTO\Attribute;
#[\Attribute(\Attribute::IS_REPEATABLE|\Attribute::TARGET_ALL)]
class HandleTags
#[\Attribute]
class TagsResolver
{
public function __construct(
public string $method,

View File

@ -3,7 +3,7 @@
namespace Rinsvent\Data2DTO\Attribute;
/** @property string[] $tags */
#[\Attribute(\Attribute::IS_REPEATABLE|\Attribute::TARGET_ALL)]
#[\Attribute]
class VirtualProperty
{
public function __construct(

View File

@ -7,31 +7,22 @@ use Rinsvent\AttributeExtractor\ClassExtractor;
use Rinsvent\AttributeExtractor\PropertyExtractor;
use Rinsvent\Data2DTO\Attribute\DTOMeta;
use Rinsvent\Data2DTO\Attribute\PropertyPath;
use Rinsvent\Data2DTO\Attribute\HandleTags;
use Rinsvent\Data2DTO\Attribute\TagsResolver;
use Rinsvent\Data2DTO\Attribute\VirtualProperty;
use Rinsvent\Transformer\Transformer;
use Rinsvent\Transformer\Transformer\Meta;
use Rinsvent\Data2DTO\Resolver\TransformerResolverStorage;
use Rinsvent\Data2DTO\Transformer\Meta;
use Rinsvent\Data2DTO\Transformer\TransformerInterface;
use function Symfony\Component\String\u;
class Data2DtoConverter
{
private Transformer $transformer;
public function __construct()
{
$this->transformer = new Transformer();
}
public function getTags(array $data, object $object, array $tags = []): array
{
return $this->processTags($object, $data, $tags);
}
public function convert(array $data, object $object, array $tags = []): object
{
$tags = empty($tags) ? ['default'] : $tags;
$reflectionObject = new \ReflectionObject($object);
$tags = $this->processTags($object, $data, $tags);
$this->processClassTransformers($reflectionObject, $data, $tags);
if (is_object($data)) {
return $data;
@ -84,12 +75,8 @@ class Data2DtoConverter
/**
* Для виртуальных полей добавляем пустой масиив, чтобы заполнить поля дто
*/
protected function processVirtualProperty(
object $object,
\ReflectionProperty $property,
array $data,
array $tags
): bool {
protected function processVirtualProperty(object $object, \ReflectionProperty $property, array $data, array $tags): bool
{
$propertyExtractor = new PropertyExtractor($property->class, $property->getName());
if ($propertyExtractor->fetch(VirtualProperty::class)) {
$propertyValue = $this->getValue($object, $property);
@ -139,13 +126,8 @@ class Data2DtoConverter
/**
* Если это class, то рекурсивно заполняем дальше
*/
protected function processClass(
object $object,
ReflectionProperty $property,
string $preparedPropertyType,
&$value,
array $tags
): bool {
protected function processClass(object $object, ReflectionProperty $property, string $preparedPropertyType, &$value, array $tags): bool
{
if (class_exists($preparedPropertyType)) {
$propertyValue = $this->getValue($object, $property);
if (!is_array($value)) {
@ -207,14 +189,14 @@ class Data2DtoConverter
protected function processTags(object $object, array $data, array $tags): array
{
$classExtractor = new ClassExtractor($object::class);
/** @var HandleTags $tagsMeta */
if ($tagsMeta = $classExtractor->fetch(HandleTags::class)) {
/** @var TagsResolver $tagsMeta */
if ($tagsMeta = $classExtractor->fetch(TagsResolver::class)) {
if (method_exists($object, $tagsMeta->method)) {
$reflectionMethod = new \ReflectionMethod($object, $tagsMeta->method);
if (!$reflectionMethod->isPublic()) {
$reflectionMethod->setAccessible(true);
}
$methodTags = $reflectionMethod->invoke($object, ...[$data, $tags]);
$methodTags = $reflectionMethod->invoke($object, ...[$data]);
if (!$reflectionMethod->isPublic()) {
$reflectionMethod->setAccessible(false);
}
@ -235,7 +217,13 @@ class Data2DtoConverter
/** @var Meta $transformMeta */
while ($transformMeta = $classExtractor->fetch(Meta::class)) {
$transformMeta->returnType = $className;
$data = $this->transformer->transform($data, $transformMeta, $tags);
$filteredTags = array_diff($tags, $transformMeta->tags);
if (count($filteredTags) === count($tags)) {
continue;
}
$transformer = $this->grabTransformer($transformMeta);
$transformer->transform($data, $transformMeta);
}
}
@ -248,15 +236,27 @@ class Data2DtoConverter
$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->retrnType = $propertyType;
$transformMeta->returnType = $propertyType;
$transformMeta->allowsNull = $reflectionPropertyType->allowsNull();
$data = $this->transformer->transform($data, $transformMeta, $tags);
$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);
}
/**
* Если значение в $data = null, но поле не может его принять - пропустим
*/
@ -317,15 +317,12 @@ class Data2DtoConverter
private function getValue(object $object, \ReflectionProperty $property)
{
if (!$property->isPublic()) {
$property->setAccessible(true);
if (!$property->isInitialized($object)) {
return null;
}
if (!$property->isInitialized($object)) {
if (!$property->isPublic()) {
$property->setAccessible(false);
}
return null;
$property->setAccessible(true);
}
$value = $property->getValue($object);

View File

@ -0,0 +1,16 @@
<?php
namespace Rinsvent\Data2DTO\Resolver;
use Rinsvent\Data2DTO\Transformer\Meta;
use Rinsvent\Data2DTO\Transformer\TransformerInterface;
class SimpleResolver implements TransformerResolverInterface
{
public function resolve(Meta $meta): TransformerInterface
{
$metaClass = $meta::class;
$transformerClass = $metaClass . 'Transformer';
return new $transformerClass;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Rinsvent\Data2DTO\Resolver;
use Rinsvent\Data2DTO\Transformer\Meta;
use Rinsvent\Data2DTO\Transformer\TransformerInterface;
interface TransformerResolverInterface
{
public function resolve(Meta $meta): TransformerInterface;
}

View File

@ -0,0 +1,32 @@
<?php
namespace Rinsvent\Data2DTO\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];
}
}

15
src/Transformer/Meta.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace Rinsvent\Data2DTO\Transformer;
#[\Attribute]
abstract class Meta
{
public const TYPE = 'simple';
public ?string $returnType = null;
public ?bool $allowsNull = null;
public function __construct(
public array $tags = ['default']
) {}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Rinsvent\Data2DTO\Transformer;
interface TransformerInterface
{
public function transform(&$data, Meta $meta): void;
}

14
src/Transformer/Trim.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Rinsvent\Data2DTO\Transformer;
#[\Attribute]
class Trim extends Meta
{
public function __construct(
public array $tags = ['default'],
public string $characters = " \t\n\r\0\x0B"
) {
parent::__construct(...func_get_args());
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Rinsvent\Data2DTO\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);
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\Converter;
use Rinsvent\Data2DTO\Data2DtoConverter;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloClassTransformersRequest;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloClassTransformersRequest2;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloTagsRequest;
class ClassTransformersTest extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
// tests
public function testSuccessWithReturnData()
{
$data2DtoConverter = new Data2DtoConverter();
$data = [
'surname' => 'Surname1234',
'age' => 3,
];
$dto = $data2DtoConverter->convert($data, new HelloClassTransformersRequest);
$this->assertInstanceOf(HelloClassTransformersRequest::class, $dto);
$this->assertEquals(123454321, $dto->surname);
}
public function testSuccessWithReturnObject()
{
$data2DtoConverter = new Data2DtoConverter();
$data = [
'surname' => 'Surname1234',
'age' => 3,
];
$dto = $data2DtoConverter->convert($data, new HelloClassTransformersRequest2());
$this->assertInstanceOf(HelloClassTransformersRequest2::class, $dto);
$this->assertEquals(98789, $dto->surname);
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\Converter;
use Rinsvent\Data2DTO\Data2DtoConverter;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Bar;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\BuyRequest;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloRequest;
class DataObjectTest extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
// tests
public function testSuccessFillRequestData()
{
$data2DtoConverter = new Data2DtoConverter();
$buy = new BuyRequest();
$buy->phrase = 'Buy buy!!!';
$buy->length = 10;
$buy->isFirst = true;
$dto = $data2DtoConverter->convert([
'surname' => ' asdf',
'buy' => $buy
], new HelloRequest);
$this->assertInstanceOf(HelloRequest::class, $dto);
$this->assertEquals($buy, $dto->buy);
}
}

View File

@ -3,6 +3,9 @@
namespace Rinsvent\Data2DTO\Tests\Converter;
use Rinsvent\Data2DTO\Data2DtoConverter;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Bar;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\BuyRequest;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloRequest;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloTagsRequest;
class TagsTest extends \Codeception\Test\Unit
@ -24,8 +27,7 @@ class TagsTest extends \Codeception\Test\Unit
public function testSuccessFillRequestData()
{
$data2DtoConverter = new Data2DtoConverter();
$data = [
$dto = $data2DtoConverter->convert([
'surname' => 'Surname1234',
'fake_age' => 3,
'fake_age2' => 7,
@ -34,9 +36,7 @@ class TagsTest extends \Codeception\Test\Unit
'af234f',
'asdf33333'
],
];
$tags = $data2DtoConverter->getTags($data, new HelloTagsRequest);
$dto = $data2DtoConverter->convert($data, new HelloTagsRequest, $tags);
], new HelloTagsRequest);
$this->assertInstanceOf(HelloTagsRequest::class, $dto);
$this->assertEquals(7, $dto->age);

View File

@ -1,12 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Transformer\ClassData;
#[ClassData]
class HelloClassTransformersRequest
{
public string $surname;
public int $age;
}

View File

@ -1,12 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Transformer\ClassObject;
#[ClassObject]
class HelloClassTransformersRequest2
{
public string $surname;
public int $age;
}

View File

@ -4,7 +4,7 @@ namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest;
use Rinsvent\Data2DTO\Attribute\DTOMeta;
use Rinsvent\Data2DTO\Attribute\PropertyPath;
use Rinsvent\Transformer\Transformer\Trim;
use Rinsvent\Data2DTO\Transformer\Trim;
class HelloRequest
{
@ -18,16 +18,4 @@ class HelloRequest
public BuyRequest $buy;
#[DTOMeta(class: Bar::class)]
public BarInterface $bar;
private BuyRequest $buy2;
public function getBuy2(): BuyRequest
{
return $this->buy2;
}
public function setBuy2(BuyRequest $buy2): void
{
$this->buy2 = $buy2;
}
}

View File

@ -3,16 +3,16 @@
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest;
use Rinsvent\Data2DTO\Attribute\PropertyPath;
use Rinsvent\Data2DTO\Attribute\HandleTags;
use Rinsvent\Data2DTO\Attribute\TagsResolver;
#[HandleTags(method: 'getTags')]
#[TagsResolver(method: 'getTags')]
class HelloTagsRequest extends HelloRequest
{
#[PropertyPath('fake_age2', tags: ['surname-group'])]
public int $age;
public function getTags(array $data, array $tags)
public function getTags(array $data)
{
return 'Surname1234' === ($data['surname'] ?? null) ? ['surname-group'] : $tags;
return 'Surname1234' === ($data['surname'] ?? null) ? ['surname-group'] : ['default'];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Transformer;
use Rinsvent\Transformer\Transformer\Meta;
#[\Attribute]
class ClassData extends Meta
{
}

View File

@ -1,25 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Transformer;
use Rinsvent\Transformer\Transformer\Meta;
use Rinsvent\Transformer\Transformer\TransformerInterface;
class ClassDataTransformer implements TransformerInterface
{
/**
* @param array|null $data
* @param ClassData $meta
*/
public function transform(mixed $data, Meta $meta): mixed
{
if ($data === null) {
return $data;
}
if (isset($data['surname'])) {
$data['surname'] = '123454321';
}
return $data;
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Transformer;
use Rinsvent\Transformer\Transformer\Meta;
#[\Attribute]
class ClassObject extends Meta
{
}

View File

@ -1,24 +0,0 @@
<?php
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\Transformer;
use Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest\HelloClassTransformersRequest2;
use Rinsvent\Transformer\Transformer\Meta;
use Rinsvent\Transformer\Transformer\TransformerInterface;
class ClassObjectTransformer implements TransformerInterface
{
/**
* @param array|null $data
* @param ClassData $meta
*/
public function transform(mixed $data, Meta $meta): mixed
{
if ($data === null) {
return $data;
}
$object = new HelloClassTransformersRequest2();
$object->surname = '98789';
return $object;
}
}