Valinor/tests/Integration/Mapping/Attribute/ObjectBuilderStrategyMappingTest.php
Romain Canon ecafba3b21 feat!: introduce method to register constructors used during mapping
It is now mandatory to explicitly register custom constructors —
including named constructors — that can be used by the mapper. The
former automatic registration of named constructor feature doesn't
work anymore.

BREAKING CHANGE: existing code must list all named constructors that
were previously automatically used by the mapper, and registerer them
using the method `MapperBuilder::registerConstructor()`.

The method `MapperBuilder::bind()` has been deprecated, the method above
should be used instead.

```php
final class SomeClass
{
    public static function namedConstructor(string $foo): self
    {
        // …
    }
}

(new \CuyZ\Valinor\MapperBuilder())
    ->registerConstructor(
        SomeClass::namedConstructor(...),
        // …or for PHP < 8.1:
        [SomeClass::class, 'namedConstructor'],
    )
    ->mapper()
    ->map(SomeClass::class, [
        // …
    ]);
```
2022-03-24 13:03:55 +01:00

130 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Attribute;
use Attribute;
use CuyZ\Valinor\Attribute\StaticMethodConstructor;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Object\Exception\TooManyObjectBuilderFactoryAttributes;
use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
use CuyZ\Valinor\Tests\Fake\Mapper\Object\FakeObjectBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use RuntimeException;
final class ObjectBuilderStrategyMappingTest extends IntegrationTest
{
public function test_object_builder_attribute_is_used(): void
{
try {
$result = $this->mapperBuilder->mapper()->map(ObjectWithBuilderStrategyAttribute::class, [
'foo' => 'foo',
'bar' => 'bar',
]);
} catch (MappingError $error) {
$this->mappingFail($error);
}
self::assertSame('foo', $result->foo);
self::assertSame('bar', $result->bar);
self::assertTrue($result->staticConstructorCalled);
}
public function test_named_constructor_throwing_exception_is_caught_by_mapper(): void
{
try {
$this->mapperBuilder->mapper()->map(ObjectWithFailingBuilderStrategyAttribute::class, []);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];
self::assertSame('some exception', (string)$error);
}
}
public function test_repeated_object_builder_factory_attributes_throws_exception(): void
{
$factoryClass = ObjectBuilderFactory::class;
$objectClass = ObjectWithSeveralBuilderStrategyAttributes::class;
$this->expectException(TooManyObjectBuilderFactoryAttributes::class);
$this->expectExceptionCode(1634044714);
$this->expectExceptionMessage("Only one attribute of type `$factoryClass` is allowed, class `$objectClass` contains 2.");
$this->mapperBuilder->mapper()->map($objectClass, 'foo');
}
}
/**
* @Annotation
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class ForeignAttribute
{
}
/**
* @Annotation
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class ObjectBuilderStrategyAttribute implements ObjectBuilderFactory
{
public function for(ClassDefinition $class, $source): ObjectBuilder
{
return new FakeObjectBuilder();
}
}
/**
* @ForeignAttribute
* @StaticMethodConstructor("create")
*/
#[ForeignAttribute]
#[StaticMethodConstructor('create')]
final class ObjectWithBuilderStrategyAttribute
{
public bool $staticConstructorCalled = false;
public string $foo;
public string $bar;
private function __construct(string $foo, string $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}
public static function create(string $foo, string $bar = 'optional value'): self
{
$instance = new self($foo, $bar);
$instance->staticConstructorCalled = true;
return $instance;
}
}
/**
* @StaticMethodConstructor("failingConstructor")
*/
#[StaticMethodConstructor('failingConstructor')]
final class ObjectWithFailingBuilderStrategyAttribute
{
public static function failingConstructor(): self
{
throw new RuntimeException('some exception');
}
}
/**
* @ObjectBuilderStrategyAttribute
* @ObjectBuilderStrategyAttribute
*/
#[ObjectBuilderStrategyAttribute]
#[ObjectBuilderStrategyAttribute]
final class ObjectWithSeveralBuilderStrategyAttributes
{
}