Skip to content

Commit

Permalink
feat: collect attributes if using property path mapping (#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi authored Sep 26, 2024
1 parent 419e172 commit 0e5beb4
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* feat: datetime transformation to int & float
* feat: class attributes are now stored in the metadata
* chore: rector run
* feat: collect attributes if using property path mapping

## 1.8.0

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Model;

use Symfony\Component\PropertyInfo\Type;

/**
* @internal
*/
final readonly class PropertyPathMetadata
{
/**
* @param class-string $class
* @param list<Type> $types
* @param list<object> $attributes
*/
public function __construct(
private string $propertyPath,
private string $class,
private ?string $property,
private array $types,
private array $attributes,
) {}

public function getPropertyPath(): string
{
return $this->propertyPath;
}

public function getClass(): string
{
return $this->class;
}

public function getProperty(): ?string
{
return $this->property;
}

/**
* @return list<Type>
*/
public function getTypes(): array
{
return $this->types;
}

/**
* @return list<object>
*/
public function getAttributes(): array
{
return $this->attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@
*/
final readonly class PropertyMetadataFactory
{
private PropertyPathAwarePropertyTypeExtractor $propertyPathAwarePropertyTypeExtractor;
private PropertyPathMetadataFactory $propertyPathAwarePropertyTypeExtractor;

public function __construct(
private PropertyReadInfoExtractorInterface $propertyReadInfoExtractor,
private PropertyWriteInfoExtractorInterface $propertyWriteInfoExtractor,
private PropertyTypeExtractorInterface $propertyTypeExtractor,
private TypeResolverInterface $typeResolver,
) {
$this->propertyPathAwarePropertyTypeExtractor = new PropertyPathAwarePropertyTypeExtractor(
$this->propertyPathAwarePropertyTypeExtractor = new PropertyPathMetadataFactory(
propertyTypeExtractor: $propertyTypeExtractor,
);
}
Expand All @@ -55,21 +55,23 @@ public function createSourcePropertyMetadata(
string $property,
bool $allowsDynamicProperties,
): SourcePropertyMetadata {
// property path

if ($this->isPropertyPath($property)) {
$types = $this->propertyPathAwarePropertyTypeExtractor->getTypes(
class: $class,
propertyPath: $property,
);
$propertyPathMetadata = $this->propertyPathAwarePropertyTypeExtractor
->getMetadata($class, $property);

return new SourcePropertyMetadata(
readMode: ReadMode::PropertyPath,
readName: $property,
readVisibility: Visibility::Public,
types: $types,
attributes: [],
types: $propertyPathMetadata->getTypes(),
attributes: $propertyPathMetadata->getAttributes(),
);
}

// normal, non property path

$readInfo = $this->propertyReadInfoExtractor
->getReadInfo($class, $property);

Expand Down Expand Up @@ -109,11 +111,13 @@ public function createTargetPropertyMetadata(
string $property,
bool $allowsDynamicProperties,
): TargetPropertyMetadata {
// property path

if ($this->isPropertyPath($property)) {
$types = $this->propertyPathAwarePropertyTypeExtractor->getTypes(
class: $class,
propertyPath: $property,
);
$propertyPathMetadata = $this->propertyPathAwarePropertyTypeExtractor
->getMetadata($class, $property);

$types = $propertyPathMetadata->getTypes();

return new TargetPropertyMetadata(
readMode: ReadMode::PropertyPath,
Expand All @@ -133,10 +137,12 @@ class: $class,
types: $types,
scalarType: $this->determineScalarType($types),
nullable: false,
attributes: [],
attributes: $propertyPathMetadata->getAttributes(),
);
}

// normal, non property path

$readInfo = $this->propertyReadInfoExtractor
->getReadInfo($class, $property);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Util;

use Rekalogika\Mapper\Transformer\Exception\PropertyPathAwarePropertyInfoExtractorException;
use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\Implementation\Model\PropertyPathMetadata;
use Rekalogika\Mapper\Util\ClassUtil;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
Expand All @@ -22,20 +24,19 @@
/**
* @internal
*/
final readonly class PropertyPathAwarePropertyTypeExtractor
final readonly class PropertyPathMetadataFactory
{
public function __construct(
private PropertyTypeExtractorInterface $propertyTypeExtractor,
) {}

/**
* @param class-string $class
* @return list<Type>
*/
public function getTypes(
public function getMetadata(
string $class,
string $propertyPath,
): array {
): PropertyPathMetadata {
$propertyPathObject = new PropertyPath($propertyPath);

/** @var \Iterator&PropertyPathIteratorInterface */
Expand All @@ -47,6 +48,10 @@ public function getTypes(

$currentType = null;

$currentProperty = null;

$lastClass = null;

/** @var list<Type>|null */
$types = null;

Expand Down Expand Up @@ -79,6 +84,8 @@ class: $class,
}

$currentPath .= '.' . $propertyPathPart;
$currentProperty = $propertyPathPart;
$lastClass = $currentClass;
$types = $this->propertyTypeExtractor
->getTypes($currentClass, $propertyPathPart);
}
Expand All @@ -98,6 +105,38 @@ class: $class,
}
}

return array_values($types ?? []);
if ($lastClass === null) {
throw new PropertyPathAwarePropertyInfoExtractorException(
message: \sprintf('Property path "%s" is empty', $propertyPath),
class: $class,
propertyPath: $propertyPath,
);
}

if (!class_exists($lastClass)) {
throw new PropertyPathAwarePropertyInfoExtractorException(
message: \sprintf('Class "%s" not found', $lastClass),
class: $class,
propertyPath: $propertyPath,
);
}

if ($currentProperty !== null) {
$attributes = ClassUtil::getPropertyAttributes(
class: $lastClass,
property: $currentProperty,
attributeClass: null,
);
} else {
$attributes = [];
}

return new PropertyPathMetadata(
propertyPath: $propertyPath,
class: $lastClass,
property: $currentProperty,
types: array_values($types ?? []),
attributes: $attributes,
);
}
}
27 changes: 27 additions & 0 deletions tests/src/Fixtures/MapPropertyPath/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Rekalogika\Mapper\Attribute\DateTimeOptions;

final class Book
{
Expand All @@ -23,11 +24,19 @@ final class Book
/**
* @var Collection<int,Chapter>
*/
#[SomeAttribute('book-chapters')]
private readonly Collection $chapters;

/**
* @var Collection<int,string>
*/
#[DateTimeOptions(format: 'm/d/Y H:i-s')]
private readonly Collection $publicationDates;

public function __construct()
{
$this->chapters = new ArrayCollection();
$this->publicationDates = new ArrayCollection();
}

public function getShelf(): ?Shelf
Expand Down Expand Up @@ -63,6 +72,24 @@ public function removeChapter(Chapter $chapter): void
}
}

/**
* @return Collection<int,string>
*/
public function getPublicationDates(): Collection
{
return $this->publicationDates;
}

public function addPublicationDate(string $publicationDate): void
{
$this->publicationDates->add($publicationDate);
}

public function removePublicationDate(string $publicationDate): void
{
$this->publicationDates->removeElement($publicationDate);
}

/**
* @return Collection<int,Chapter|Section>
*/
Expand Down
1 change: 1 addition & 0 deletions tests/src/Fixtures/MapPropertyPath/Chapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

final class Chapter
{
#[SomeAttribute('chapter-book')]
private ?Book $book = null;

private ?string $title = null;
Expand Down
1 change: 1 addition & 0 deletions tests/src/Fixtures/MapPropertyPath/Shelf.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

final class Shelf
{
#[SomeAttribute('shelf-library')]
private ?Library $library = null;

/**
Expand Down
27 changes: 27 additions & 0 deletions tests/src/Fixtures/MapPropertyPath/SomeAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\Fixtures\MapPropertyPath;

#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final readonly class SomeAttribute
{
public function __construct(
private string $value,
) {}

public function getValue(): string
{
return $this->value;
}
}
27 changes: 27 additions & 0 deletions tests/src/Fixtures/MapPropertyPathDto/Chapter2Dto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\Fixtures\MapPropertyPathDto;

use Rekalogika\Mapper\Attribute\DateTimeOptions;
use Rekalogika\Mapper\Attribute\Map;

final class Chapter2Dto
{
/**
* @var list<\DateTimeInterface>
*/
#[DateTimeOptions(timeZone: 'Asia/Jakarta')]
#[Map(property: 'book.publicationDates')]
public array $bookPublicationDates = [];
}
Loading

0 comments on commit 0e5beb4

Please sign in to comment.