Добавил property accessor

добавил тесты
проработка логики работы фич
master
Rinsvent 3 years ago
parent c16d2e7e61
commit 43bc10d24f
  1. 3
      composer.json
  2. 4698
      composer.lock
  3. 187
      src/Dto2DataConverter.php
  4. 93
      tests/unit/Converter/FillTest.php
  5. 144
      tests/unit/Converter/fixtures/FillTest/HelloRequest.php
  6. 28
      tests/unit/Converter/fixtures/FillTest/HelloSchema.php

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

4698
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -14,9 +14,20 @@ use Rinsvent\DTO2Data\Attribute\Schema;
use Rinsvent\DTO2Data\Resolver\TransformerResolverStorage;
use Rinsvent\DTO2Data\Transformer\Meta;
use Rinsvent\DTO2Data\Transformer\TransformerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class Dto2DataConverter
{
private PropertyAccessorInterface $propertyAccessor;
public function __construct()
{
$this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->disableExceptionOnInvalidPropertyPath()
->getPropertyAccessor();
}
public function getTags(object $object, array $tags = []): array
{
return $this->processTags($object, $tags);
@ -54,47 +65,42 @@ class Dto2DataConverter
public function convertObjectByMap(object $object, array $map, array $tags = []): array
{
$data = [];
$reflectionObject = new \ReflectionObject($object);
foreach ($map as $key => $propertyInfo) {
$sourceName = is_array($propertyInfo) ? $key : $propertyInfo;
if (!method_exists($object, $sourceName) && !property_exists($object, $sourceName)) {
continue;
}
try {
$sourceName = is_array($propertyInfo) ? $key : $propertyInfo;
$value = $this->grabValue($object, $sourceName, $tags);
$canSkip = false;
// Если нет карты, то не сериализуем.
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);
} elseif (!is_scalar($value) && null !== $value) {
$canSkip = true;
}
$value = $this->grabValue($object, $sourceName, $tags);
$canSkip = false;
// Если нет карты, то не сериализуем.
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);
} elseif (!is_scalar($value) && null !== $value) {
$canSkip = true;
}
$this->processIterationTransformers($object, $sourceName, $value, $tags);
$this->processIterationTransformers($object, $sourceName, $value, $tags);
if ($canSkip && !is_scalar($value) && null !== $value) {
continue;
}
if ($canSkip && !is_scalar($value) && null !== $value) {
$dataPath = $this->grabIterationDataPath($object, $sourceName, $tags);
$data[$dataPath] = $value;
} catch (\Throwable $e) {
continue;
}
$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)) {
@ -113,45 +119,47 @@ class Dto2DataConverter
return $tempValue;
}
protected function grabValue(object $object, $sourceName, array $tags)
protected function grabValue(object $object, string $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;
}
return $this->getValueByPath($object, $propertyPath->path);
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->getValue($object, $reflectionSource);
return $this->getValueByPath($object, $propertyPath->path);
}
return null;
return $this->getValueByPath($object, $sourceName);
}
public function processIterationTransformers(object $object, string $sourceName, &$value, array $tags): void
{
if (method_exists($object, $sourceName)) {
$reflectionSource = new ReflectionMethod($object, $sourceName);
$this->processMethodTransformers($reflectionSource, $value, $tags);
} elseif (property_exists($object, $sourceName)) {
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);
}
}
public function grabIterationDataPath(object $object, string $sourceName, array $tags): string
{
if (method_exists($object, $sourceName)) {
$reflectionSource = new ReflectionMethod($object, $sourceName);
$getter = $this->grabGetterName($object, $sourceName);
if ($getter) {
$reflectionSource = new ReflectionMethod($object, $getter);
$dataPath = $this->grabMethodDataPath($reflectionSource, $tags);
} elseif (property_exists($object, $sourceName)) {
if ($dataPath) {
return $dataPath;
}
}
if (property_exists($object, $sourceName)) {
$reflectionSource = new ReflectionProperty($object, $sourceName);
$dataPath = $this->grabDataPath($reflectionSource, $tags);
}
@ -254,71 +262,11 @@ class Dto2DataConverter
private function getValueByPath($data, string $path)
{
$parts = explode('.', $path);
$length = count($parts);
$i = 1;
foreach ($parts as $part) {
// Если получили скалярное значение но прошли не весь путь, то вернем 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)) {
try {
return $this->propertyAccessor->getValue($data, $path);
} catch (\Throwable $e) {
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
@ -367,4 +315,15 @@ class Dto2DataConverter
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;
}
}

@ -18,14 +18,6 @@ class FillTest extends \Codeception\Test\Unit
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
// tests
public function testSuccessFillRequestData()
{
@ -80,6 +72,41 @@ class FillTest extends \Codeception\Test\Unit
$helloRequest->collection = $collection;
$helloRequest->createdAt = new \DateTimeImmutable('2020-05-21 13:36:22');
// private
$helloRequest->setPsurname(' asdf');
$helloRequest->setPage(3);
$helloRequest->setPemails([
'sfdgsa',
'af234f',
'asdf33333'
]);
$helloRequest->setPauthors([
$author1,
$author2
]);
$helloRequest->setPauthors2([
[
"name" => "Tolkien"
],
[
"name" => "Sapkovsky"
]
]);
$helloRequest->setPauthors3([
[
"name" => "Tolkien"
],
[
"name" => "Sapkovsky"
]
]);
$helloRequest->setPbuy($buy);
$helloRequest->setPbar($bar);
$helloRequest->setPuuid(new UUID('qwerqw-qwerqwe-werqw-qwerqw'));
$helloRequest->setPcollection($collection);
$helloRequest->setPcreatedAt(new \DateTimeImmutable('2020-05-21 13:36:22'));
$dto = $dto2DataConverter->convert($helloRequest);
// codecept_debug(json_encode($dto));
@ -128,7 +155,55 @@ class FillTest extends \Codeception\Test\Unit
'value' => '2',
],
],
'createdAt' => '2020-05-21T13:36:22+00:00'
'createdAt' => '2020-05-21T13:36:22+00:00',
"psurname" => "asdf",
"pfake_age" => 3,
"pemails" => [
"sfdgsa",
"af234f",
"asdf33333"
],
"pauthors" => [
[
"name" => "Tolkien"
],
[
"name" => "Sapkovsky"
]
],
"pauthors2" => [
[
"name" => "Tolkien"
],
[
"name" => "Sapkovsky"
]
],
"pauthors3" => [],
"pbuy" => [
"phrase" => "Buy buy!!!",
"length" => 10,
"isFirst" => true
],
"pbar" => [
"barField" => 32
],
'puuid' => 'qwerqw-qwerqwe-werqw-qwerqw',
'pcollection' => [
[
'value' => '3',
],
[
'value' => '1',
],
[
'value' => '2',
],
],
'pcreatedAt' => '2020-05-21T13:36:22+00:00',
'fake_Pdevdo' => 'getPdevdo',
], $dto);
}
}

@ -25,4 +25,148 @@ class HelloRequest
public Collection $collection;
#[DateTimeFormat]
public \DateTimeImmutable $createdAt;
#[Trim]
private string $psurname;
#[DataPath('pfake_age')]
private int $page;
private array $pemails;
private array $pauthors;
private array $pauthors2;
private array $pauthors3;
private BuyRequest $pbuy;
private BarInterface $pbar;
#[PropertyPath(path: 'puuid.id')]
private UUID $puuid;
private Collection $pcollection;
#[DateTimeFormat]
private \DateTimeImmutable $pcreatedAt;
public function getPsurname(): string
{
return $this->psurname;
}
public function setPsurname(string $psurname): self
{
$this->psurname = $psurname;
return $this;
}
public function getPage(): int
{
return $this->page;
}
public function setPage(int $page): self
{
$this->page = $page;
return $this;
}
public function getPemails(): array
{
return $this->pemails;
}
public function setPemails(array $pemails): self
{
$this->pemails = $pemails;
return $this;
}
public function getPauthors(): array
{
return $this->pauthors;
}
public function setPauthors(array $pauthors): self
{
$this->pauthors = $pauthors;
return $this;
}
public function getPauthors2(): array
{
return $this->pauthors2;
}
public function setPauthors2(array $pauthors2): self
{
$this->pauthors2 = $pauthors2;
return $this;
}
public function getPauthors3(): array
{
return $this->pauthors3;
}
public function setPauthors3(array $pauthors3): self
{
$this->pauthors3 = $pauthors3;
return $this;
}
public function getPbuy(): BuyRequest
{
return $this->pbuy;
}
public function setPbuy(BuyRequest $pbuy): self
{
$this->pbuy = $pbuy;
return $this;
}
public function getPuuid(): UUID
{
return $this->puuid;
}
public function setPuuid(UUID $puuid): self
{
$this->puuid = $puuid;
return $this;
}
public function getPbar(): BarInterface
{
return $this->pbar;
}
public function setPbar(BarInterface $pbar): self
{
$this->pbar = $pbar;
return $this;
}
public function getPcollection(): Collection
{
return $this->pcollection;
}
public function setPcollection(Collection $pcollection): self
{
$this->pcollection = $pcollection;
return $this;
}
public function getPcreatedAt(): \DateTimeImmutable
{
return $this->pcreatedAt;
}
public function setPcreatedAt(\DateTimeImmutable $pcreatedAt): self
{
$this->pcreatedAt = $pcreatedAt;
return $this;
}
#[Trim]
#[DataPath('fake_Pdevdo')]
public function getPdevdo(): string
{
return ' getPdevdo';
}
}

@ -30,6 +30,32 @@ class HelloSchema extends Schema
'collection' => [
'value'
],
'createdAt'
'createdAt',
'psurname',
'page',
'pemails',
'pauthors' => [
'name',
],
'pauthors2' => [
'name',
],
'pauthors3',
'pbuy' => [
'phrase',
'length',
'isFirst',
],
'pbar' => [
'barField'
],
'puuid',
'pcollection' => [
'value'
],
'pcreatedAt',
'pdevdo'
];
}

Loading…
Cancel
Save