Compare commits

...

6 Commits

Author SHA1 Message Date
a62a65babd feature: поправил зависимости 2022-04-02 14:35:35 +07:00
c1c7702b30 feature: вынес трансформер в отдельную библиотеку 2022-04-02 14:33:59 +07:00
d7facd9cc7 feature: обработал доступ к приватному свойству
расширил тест
2022-01-03 23:00:11 +07:00
Sipachev Igor
59f37226ee Поправил описание атрибутов 2021-08-23 12:39:41 +07:00
642dd026f1 Добавил тестов 2021-08-19 23:15:14 +07:00
Sipachev Igor
dabeee4a72 Переименовал TagsResolver
Починил тесты
Вынес модификацию тегов из конвертера.
В будущем можно добавить опционально
2021-08-19 12:44:58 +07:00
25 changed files with 259 additions and 4585 deletions

View File

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

4417
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
namespace Rinsvent\Data2DTO\Attribute;
#[\Attribute]
#[\Attribute(\Attribute::IS_REPEATABLE|\Attribute::TARGET_ALL)]
class DTOMeta
{
public function __construct(
@ -10,4 +10,4 @@ class DTOMeta
/** @var string[] $tags */
public array $tags = ['default']
) {}
}
}

View File

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

View File

@ -2,7 +2,7 @@
namespace Rinsvent\Data2DTO\Attribute;
#[\Attribute]
#[\Attribute(\Attribute::IS_REPEATABLE|\Attribute::TARGET_ALL)]
class PropertyPath
{
public function __construct(
@ -10,4 +10,4 @@ class PropertyPath
/** @var string[] $tags */
public array $tags = ['default']
) {}
}
}

View File

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

View File

@ -7,22 +7,31 @@ use Rinsvent\AttributeExtractor\ClassExtractor;
use Rinsvent\AttributeExtractor\PropertyExtractor;
use Rinsvent\Data2DTO\Attribute\DTOMeta;
use Rinsvent\Data2DTO\Attribute\PropertyPath;
use Rinsvent\Data2DTO\Attribute\TagsResolver;
use Rinsvent\Data2DTO\Attribute\HandleTags;
use Rinsvent\Data2DTO\Attribute\VirtualProperty;
use Rinsvent\Data2DTO\Resolver\TransformerResolverStorage;
use Rinsvent\Data2DTO\Transformer\Meta;
use Rinsvent\Data2DTO\Transformer\TransformerInterface;
use Rinsvent\Transformer\Transformer;
use Rinsvent\Transformer\Transformer\Meta;
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;
@ -75,8 +84,12 @@ 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);
@ -126,8 +139,13 @@ 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)) {
@ -189,8 +207,8 @@ class Data2DtoConverter
protected function processTags(object $object, array $data, array $tags): array
{
$classExtractor = new ClassExtractor($object::class);
/** @var TagsResolver $tagsMeta */
if ($tagsMeta = $classExtractor->fetch(TagsResolver::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()) {
@ -217,13 +235,7 @@ class Data2DtoConverter
/** @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);
$data = $this->transformer->transform($data, $transformMeta, $tags);
}
}
@ -236,27 +248,15 @@ 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->returnType = $propertyType;
$transformMeta->retrnType = $propertyType;
$transformMeta->allowsNull = $reflectionPropertyType->allowsNull();
$transformer = $this->grabTransformer($transformMeta);
$transformer->transform($data, $transformMeta);
$data = $this->transformer->transform($data, $transformMeta, $tags);
}
}
protected function grabTransformer(Meta $meta): TransformerInterface
{
$storage = TransformerResolverStorage::getInstance();
$resolver = $storage->get($meta::TYPE);
return $resolver->resolve($meta);
}
/**
* Если значение в $data = null, но поле не может его принять - пропустим
*/
@ -317,14 +317,17 @@ class Data2DtoConverter
private function getValue(object $object, \ReflectionProperty $property)
{
if (!$property->isInitialized($object)) {
return null;
}
if (!$property->isPublic()) {
$property->setAccessible(true);
}
if (!$property->isInitialized($object)) {
if (!$property->isPublic()) {
$property->setAccessible(false);
}
return null;
}
$value = $property->getValue($object);
if (!$property->isPublic()) {

View File

@ -1,16 +0,0 @@
<?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

@ -1,11 +0,0 @@
<?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

@ -1,32 +0,0 @@
<?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];
}
}

View File

@ -1,15 +0,0 @@
<?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

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

View File

@ -1,14 +0,0 @@
<?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

@ -1,18 +0,0 @@
<?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

@ -0,0 +1,51 @@
<?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

@ -0,0 +1,43 @@
<?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,9 +3,6 @@
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
@ -27,7 +24,8 @@ class TagsTest extends \Codeception\Test\Unit
public function testSuccessFillRequestData()
{
$data2DtoConverter = new Data2DtoConverter();
$dto = $data2DtoConverter->convert([
$data = [
'surname' => 'Surname1234',
'fake_age' => 3,
'fake_age2' => 7,
@ -36,7 +34,9 @@ class TagsTest extends \Codeception\Test\Unit
'af234f',
'asdf33333'
],
], new HelloTagsRequest);
];
$tags = $data2DtoConverter->getTags($data, new HelloTagsRequest);
$dto = $data2DtoConverter->convert($data, new HelloTagsRequest, $tags);
$this->assertInstanceOf(HelloTagsRequest::class, $dto);
$this->assertEquals(7, $dto->age);

View File

@ -0,0 +1,12 @@
<?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

@ -0,0 +1,12 @@
<?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\Data2DTO\Transformer\Trim;
use Rinsvent\Transformer\Transformer\Trim;
class HelloRequest
{
@ -18,4 +18,16 @@ 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,9 +3,9 @@
namespace Rinsvent\Data2DTO\Tests\unit\Converter\fixtures\FillTest;
use Rinsvent\Data2DTO\Attribute\PropertyPath;
use Rinsvent\Data2DTO\Attribute\TagsResolver;
use Rinsvent\Data2DTO\Attribute\HandleTags;
#[TagsResolver(method: 'getTags')]
#[HandleTags(method: 'getTags')]
class HelloTagsRequest extends HelloRequest
{
#[PropertyPath('fake_age2', tags: ['surname-group'])]

View File

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

View File

@ -0,0 +1,25 @@
<?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

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

View File

@ -0,0 +1,24 @@
<?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;
}
}