Добавил property accessor
добавил тесты проработка логики работы фич
This commit is contained in:
parent
c16d2e7e61
commit
43bc10d24f
@ -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
Normal file
4698
composer.lock
generated
Normal file
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;
|
||||
try {
|
||||
$sourceName = is_array($propertyInfo) ? $key : $propertyInfo;
|
||||
|
||||
if (!method_exists($object, $sourceName) && !property_exists($object, $sourceName)) {
|
||||
$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);
|
||||
|
||||
if ($canSkip && !is_scalar($value) && null !== $value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dataPath = $this->grabIterationDataPath($object, $sourceName, $tags);
|
||||
$data[$dataPath] = $value;
|
||||
} catch (\Throwable $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
if ($canSkip && !is_scalar($value) && null !== $value) {
|
||||
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);
|
||||
}
|
||||
return $this->getValue($object, $reflectionSource);
|
||||
if (!method_exists($object, $sourceName) && !property_exists($object, $sourceName)) {
|
||||
return $this->getValueByPath($object, $sourceName);
|
||||
}
|
||||
|
||||
return null;
|
||||
$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);
|
||||
}
|
||||
|
||||
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…
Reference in New Issue
Block a user