Skip to content

Commit

Permalink
#68 add nullsafe to Configuration and nullSafety to PropertyMappingPo…
Browse files Browse the repository at this point in the history
…pulator

add test for dot operator
add debug functionality to php docker container for testing
  • Loading branch information
mike4git committed Mar 18, 2024
1 parent 7873b53 commit 95455df
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 9 deletions.
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
timeout: 10s

php:
image: pimcore/pimcore:php8.2-latest
image: pimcore/pimcore:php8.2-debug-latest
volumes:
- ./:/var/www/html/
environment:
Expand Down
7 changes: 5 additions & 2 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ private function addConverterSection(ArrayNodeDefinition $rootNode): void
->arrayPrototype()
->beforeNormalization()
->ifNull()
->then(fn () => ['source' => null, 'default' => null])
->then(fn () => ['source' => null, 'default' => null, 'nullsafe' => null])
->end()
->beforeNormalization()
->ifString()
->then(fn (string $v) => ['source' => $v, 'default' => null])
->then(fn (string $v) => ['source' => $v, 'default' => null, 'nullsafe' => false])
->end()
->children()
->scalarNode('source')
Expand All @@ -69,6 +69,9 @@ private function addConverterSection(ArrayNodeDefinition $rootNode): void
->scalarNode('default')
->defaultValue(null)
->end()
->scalarNode('nullsafe')
->defaultValue(false)
->end()
->end()
->end()
->end()
Expand Down
6 changes: 6 additions & 0 deletions src/DependencyInjection/NeustaConverterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public function loadInternal(array $mergedConfig, ContainerBuilder $container):
private function registerConverterConfiguration(string $id, array $config, ContainerBuilder $container): void
{
foreach ($config['properties'] ?? [] as $targetProperty => $sourceConfig) {
$nullSafety = false;
if (str_ends_with($targetProperty, '?')) {
$nullSafety = true;
$targetProperty = substr($targetProperty, 0, \strlen($targetProperty) - 1);
}
$config['populators'][] = $propertyPopulatorId = "{$id}.populator.{$targetProperty}";
$container->register($propertyPopulatorId, PropertyMappingPopulator::class)
->setArguments([
Expand All @@ -50,6 +55,7 @@ private function registerConverterConfiguration(string $id, array $config, Conta
'$defaultValue' => $sourceConfig['default'] ?? null,
'$mapper' => null,
'$accessor' => new Reference('property_accessor'),
'$nullSafety' => $sourceConfig['nullsafe'] ?? $nullSafety,
]);
}

Expand Down
5 changes: 4 additions & 1 deletion src/Populator/PropertyMappingPopulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public function __construct(
private mixed $defaultValue = null,
?\Closure $mapper = null,
?PropertyAccessorInterface $accessor = null,
private bool $nullSafety = false,
) {
$this->mapper = $mapper ?? static fn ($v) => $v;
$this->accessor = $accessor ?? PropertyAccess::createPropertyAccessor();
Expand All @@ -44,7 +45,9 @@ public function populate(object $target, object $source, ?object $ctx = null): v
try {
$value = $this->accessor->getValue($source, $this->sourceProperty) ?? $this->defaultValue;

$this->accessor->setValue($target, $this->targetProperty, ($this->mapper)($value, $ctx));
if (!$this->nullSafety || (null !== $value)) {
$this->accessor->setValue($target, $this->targetProperty, ($this->mapper)($value, $ctx));
}
} catch (\Throwable $exception) {
throw new PopulationException($this->sourceProperty, $this->targetProperty, $exception);
}
Expand Down
37 changes: 37 additions & 0 deletions tests/Converter/GenericExtendedConverterIntegrationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Converter;

use Neusta\ConverterBundle\Converter;
use Neusta\ConverterBundle\Converter\Context\GenericContext;
use Neusta\ConverterBundle\Tests\Fixtures\Model\Person;
use Neusta\ConverterBundle\Tests\Fixtures\Model\User;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class GenericExtendedConverterIntegrationTest extends KernelTestCase
{
/** @var Converter<User, Person, GenericContext> */
private Converter $converter;

protected function setUp(): void
{
parent::setUp();
$this->converter = self::getContainer()->get('test.person.converter.extended');
}

public function testConvert_with_null_safety_property(): void
{
// Test Fixture
$source = (new User())
->setFullName(null)
->setAgeInYears(null);

// Test Execution
$target = $this->converter->convert($source);

// Test Assertion
self::assertEquals('Hans Herrmann', $target->getFullName());
}
}
14 changes: 14 additions & 0 deletions tests/Fixtures/Model/Person.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class Person

private ?PersonAddress $address = null;

private ?string $placeOfResidence = null;

/** @var array<string> */
private array $favouriteMovies;

Expand Down Expand Up @@ -75,6 +77,18 @@ public function setAddress(?PersonAddress $address): self
return $this;
}

public function getPlaceOfResidence(): ?string
{
return $this->placeOfResidence;
}

public function setPlaceOfResidence(?string $placeOfResidence): self
{
$this->placeOfResidence = $placeOfResidence;

return $this;
}

public function getFavouriteMovies(): array
{
return $this->favouriteMovies;
Expand Down
10 changes: 5 additions & 5 deletions tests/Fixtures/Model/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ class User
private string $firstname;
private string $lastname;
private ?string $fullName;
private int $ageInYears;
private ?int $ageInYears;
private string $email;
private Address $address;
private ?Address $address;

/** @var array<string> */
private array $favouriteMovies;
Expand Down Expand Up @@ -73,12 +73,12 @@ public function setFullName(?string $fullName): self
return $this;
}

public function getAgeInYears(): int
public function getAgeInYears(): ?int
{
return $this->ageInYears;
}

public function setAgeInYears($ageInYears): self
public function setAgeInYears(?int $ageInYears): self
{
$this->ageInYears = $ageInYears;

Expand All @@ -100,7 +100,7 @@ public function getAddress(): Address
return $this->address;
}

public function setAddress(Address $address): self
public function setAddress(?Address $address): self
{
$this->address = $address;

Expand Down
60 changes: 60 additions & 0 deletions tests/Populator/PropertyMappingPopulatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Neusta\ConverterBundle\Tests\Populator;

use Neusta\ConverterBundle\Populator\PropertyMappingPopulator;
use Neusta\ConverterBundle\Tests\Fixtures\Model\Address;
use Neusta\ConverterBundle\Tests\Fixtures\Model\Person;
use Neusta\ConverterBundle\Tests\Fixtures\Model\User;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -35,4 +36,63 @@ public function test_populate_default_value(): void

self::assertSame('default', $person->getFullName());
}

public function test_populate_null_safety(): void
{
$populator = new PropertyMappingPopulator(
'fullName',
'fullName',
null,
null,
null,
true
);
$user = (new User())->setFullName(null);
$person = new Person();
$person->setFullName('old Name');

$populator->populate($person, $user);

self::assertSame('old Name', $person->getFullName());
}

public function test_populate_with_dot_operator(): void
{
$populator = new PropertyMappingPopulator(
'placeOfResidence',
'address.city',
null,
null,
null,
true
);
$user = (new User())->setAddress((new Address())->setCity('Bremen'));

$person = new Person();

$populator->populate($person, $user);

self::assertSame('Bremen', $person->getPlaceOfResidence());
}

// This functionality will be automatically possible with Symfony 6.2 or higher.
// public function test_populate_with_dot_operator_and_null_safety(): void
// {
// $populator = new PropertyMappingPopulator(
// 'placeOfResidence',
// 'address?.city',
// null,
// null,
// null,
// true
// );
// $user = (new User())->setAddress(null);
//
// $person = new Person();
// $person->setPlaceOfResidence('Old City');
//
// $populator->populate($person, $user);
//
// self::assertSame('Old City', $person->getPlaceOfResidence());
// }
}
9 changes: 9 additions & 0 deletions tests/app/config/packages/neusta_converter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ neusta_converter:
# group: ~ # same property name
# locale: language # different property names

test.person.converter.extended:
target_factory: Neusta\ConverterBundle\Tests\Fixtures\Model\PersonFactory
properties:
fullName:
source: fullName
default: 'Hans Herrmann'
nullsafe: true
age?: ageInYears

test.contactnumber.converter:
target_factory: Neusta\ConverterBundle\Tests\Fixtures\Model\ContactNumberFactory
properties:
Expand Down

0 comments on commit 95455df

Please sign in to comment.