Valinor/docs/pages/mapping/inferring-interfaces.md
Romain Canon 1b0ff39af6 feat!: handle exhaustive list of interface inferring
It is now mandatory to list all possible class-types that can be
inferred by the mapper. This change is a step towards the library being
able to deliver powerful new features such as compiling a mapper for
better performance.

BREAKING CHANGE: the existing calls to `MapperBuilder::infer` that could
return several class-names must now add a signature to the callback. The
callbacks that require no parameter and always return the same
class-name can remain unchanged.

For instance:

```php
$builder = (new \CuyZ\Valinor\MapperBuilder())
    // Can remain unchanged
    ->infer(SomeInterface::class, fn () => SomeImplementation::class);
```

```php
$builder = (new \CuyZ\Valinor\MapperBuilder())
    ->infer(
        SomeInterface::class,
        fn (string $type) => match($type) {
            'first' => ImplementationA::class,
            'second' => ImplementationB::class,
            default => throw new DomainException("Unhandled `$type`.")
        }
    )
    // …should be modified with:
    ->infer(
        SomeInterface::class,
        /** @return class-string<ImplementationA|ImplementationB> */
        fn (string $type) => match($type) {
            'first' => ImplementationA::class,
            'second' => ImplementationB::class,
            default => throw new DomainException("Unhandled `$type`.")
        }
    );
```
2022-06-17 18:03:27 +02:00

1.8 KiB

Inferring interfaces

When the mapper meets an interface, it needs to understand which implementation (a class that implements this interface) will be used — this information must be provided in the mapper builder, using the method infer().

The callback given to this method must return the name of a class that implements the interface. Any arguments can be required by the callback; they will be mapped properly using the given source.

If the callback can return several class names, it needs to provide a return signature with the list of all class-strings that can be returned (see below).

$mapper = (new \CuyZ\Valinor\MapperBuilder())
    ->infer(UuidInterface::class, fn () => MyUuid::class)
    ->infer(
        SomeInterface::class, 
        /** @return class-string<FirstImplementation|SecondImplementation> */
        fn (string $type) => match($type) {
            'first' => FirstImplementation::class,
            'second' => SecondImplementation::class,
            default => throw new DomainException("Unhandled type `$type`.")
        }
    )->mapper();

// Will return an instance of `FirstImplementation`
$mapper->map(SomeInterface::class, [
    'type' => 'first',
    'uuid' => 'a6868d61-acba-406d-bcff-30ecd8c0ceb6',
    'someString' => 'foo',
]);

// Will return an instance of `SecondImplementation`
$mapper->map(SomeInterface::class, [
    'type' => 'second',
    'uuid' => 'a6868d61-acba-406d-bcff-30ecd8c0ceb6',
    'someInt' => 42,
]);

interface SomeInterface {}

final class FirstImplementation implements SomeInterface
{
    public readonly UuidInterface $uuid;

    public readonly string $someString;
}

final class SecondImplementation implements SomeInterface
{
    public readonly UuidInterface $uuid;

    public readonly int $someInt;
}