Skip to content

Commit

Permalink
feat: throws exception if the paired property does not exist in the o…
Browse files Browse the repository at this point in the history
…ther class if the `Map` attribute has `class` set
  • Loading branch information
priyadi committed Oct 11, 2024
1 parent 2924e87 commit bc9fe65
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* refactor: spin off `resolveTargetClass()` to separate class
* perf: proxy warming
* feat: process proxies of all classes in the inheritance chain
* feat: throws exception if the paired property does not exist in the other
class if the `Map` attribute has `class` set

## 1.10.0

Expand Down
44 changes: 44 additions & 0 deletions src/Transformer/Exception/PairedPropertyNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?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\Exception;

use Rekalogika\Mapper\Exception\ExceptionInterface;

/**
* @internal
*/
class PairedPropertyNotFoundException extends \LogicException implements ExceptionInterface
{
/**
* @param class-string $class
*/
public function __construct(
string $class,
string $property,
string $pairedClass,
string $pairedProperty,
) {
$message = \sprintf(
'Trying to map class "%s" property "%s" to class "%s" property "%s" according to the "Map" attribute, but the property "%s" is not found in class "%s"',
$class,
$property,
$pairedClass,
$pairedProperty,
$pairedProperty,
$pairedClass,
);

parent::__construct($message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Rekalogika\Mapper\Transformer\MetadataUtil\PropertyMappingResolver;

use Rekalogika\Mapper\Attribute\Map;
use Rekalogika\Mapper\Transformer\Exception\PairedPropertyNotFoundException;
use Rekalogika\Mapper\Transformer\MetadataUtil\PropertyMappingResolverInterface;
use Rekalogika\Mapper\Util\ClassUtil;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
Expand Down Expand Up @@ -43,6 +44,7 @@ public function getPropertiesToMap(
class: $targetClass,
property: $targetProperty,
pairedClass: $sourceClass,
pairedClassProperties: $sourceProperties,
);

$targetPropertyToSourceProperty[$targetProperty] = $sourceProperty;
Expand All @@ -53,6 +55,7 @@ class: $targetClass,
class: $sourceClass,
property: $sourceProperty,
pairedClass: $targetClass,
pairedClassProperties: $targetProperties,
);

$targetPropertyToSourceProperty[$targetProperty] = $sourceProperty;
Expand All @@ -64,6 +67,7 @@ class: $sourceClass,
class: $targetClass,
property: $targetProperty,
pairedClass: $sourceClass,
pairedClassProperties: $sourceProperties,
);

$targetPropertyToSourceProperty[$targetProperty] = $sourceProperty;
Expand All @@ -82,11 +86,13 @@ class: $targetClass,
/**
* @param class-string $class
* @param class-string $pairedClass
* @param list<string> $pairedClassProperties
*/
private function determinePairedProperty(
string $class,
string $property,
string $pairedClass,
array $pairedClassProperties,
): string {
$attributes = ClassUtil::getPropertyAttributes(
class: $class,
Expand All @@ -104,7 +110,21 @@ class: $class,
));

if (\count($attributesWithClass) >= 1) {
return $attributesWithClass[0]->property;
$pairedProperty = $attributesWithClass[0]->property;

if (
!$this->isPropertyPath($pairedProperty)
&& !\in_array($pairedProperty, $pairedClassProperties, true)
) {
throw new PairedPropertyNotFoundException(
class: $class,
property: $property,
pairedClass: $pairedClass,
pairedProperty: $pairedProperty,
);
}

return $pairedProperty;
}

// process attributes without pairedClass
Expand All @@ -125,7 +145,7 @@ class: $class,

/**
* @param class-string $class
* @return array<int,string>
* @return list<string>
*/
private function listProperties(
string $class,
Expand All @@ -134,4 +154,9 @@ private function listProperties(

return array_values($properties);
}

private function isPropertyPath(string $property): bool
{
return str_contains($property, '.') || str_contains($property, '[');
}
}
22 changes: 14 additions & 8 deletions tests/config/rekalogika-mapper/generated-mappings.php
Original file line number Diff line number Diff line change
Expand Up @@ -484,49 +484,55 @@
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on lines 90, 110
// tests/src/IntegrationTest/MapAttributeTest.php on lines 92, 112
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\OtherObject::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectDto::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 50
// tests/src/IntegrationTest/MapAttributeTest.php on line 52
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\ObjectExtendingSomeObjectDto::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 70
// tests/src/IntegrationTest/MapAttributeTest.php on line 72
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\ObjectOverridingSomeObjectDto::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 30
// tests/src/IntegrationTest/MapAttributeTest.php on line 32
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectDto::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 130
// tests/src/IntegrationTest/MapAttributeTest.php on line 141
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectWithInvalidTargetDto::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 132
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectWithUnpromotedConstructorDto::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 120
// tests/src/IntegrationTest/MapAttributeTest.php on line 122
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectDto::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\ObjectExtendingOtherObject::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on line 100
// tests/src/IntegrationTest/MapAttributeTest.php on line 102
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectDto::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\OtherObject::class
);

$mappingCollection->addObjectMapping(
// tests/src/IntegrationTest/MapAttributeTest.php on lines 40, 60, 80
// tests/src/IntegrationTest/MapAttributeTest.php on lines 42, 62, 82
source: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectDto::class,
target: \Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject::class
);
Expand Down
22 changes: 22 additions & 0 deletions tests/src/Fixtures/MapAttribute/SomeObjectWithInvalidTargetDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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\MapAttribute;

use Rekalogika\Mapper\Attribute\Map;

class SomeObjectWithInvalidTargetDto
{
#[Map(property: 'foo', class: SomeObject::class)]
public ?string $targetPropertyA = null;
}
10 changes: 10 additions & 0 deletions tests/src/IntegrationTest/MapAttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
use Rekalogika\Mapper\Tests\Fixtures\MapAttribute\OtherObject;
use Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObject;
use Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectDto;
use Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectWithInvalidTargetDto;
use Rekalogika\Mapper\Tests\Fixtures\MapAttribute\SomeObjectWithUnpromotedConstructorDto;
use Rekalogika\Mapper\Transformer\Exception\PairedPropertyNotFoundException;

class MapAttributeTest extends FrameworkTestCase
{
Expand Down Expand Up @@ -131,4 +133,12 @@ public function testMapAttributeToUnpromotedConstructorParameter(): void

$this->assertEquals('sourcePropertyA', $target->targetPropertyA);
}

public function testMapAttributeWithInvalidProperty(): void
{
$this->expectException(PairedPropertyNotFoundException::class);
$source = SomeObject::preinitialized();
$target = $this->mapper->map($source, SomeObjectWithInvalidTargetDto::class);

}
}

0 comments on commit bc9fe65

Please sign in to comment.