github CuyZ/Valinor 2.1.0

latest releases: 2.1.2, 2.1.1
one month ago

Notable changes

Attribute converters

Note

Fetch common examples of mapping converters in the documentation.

Callable converters allow targeting any value during mapping, whereas attribute converters allow targeting a specific class or property for a more granular control.

To be detected by the mapper, an attribute class must be registered first by adding the AsConverter attribute to it.

Attributes must declare a method named map that follows the same rules as callable converters: a mandatory first parameter and an optional second callable parameter.

Below is an example of an attribute converter that converts string inputs to boolean values based on specific string inputs:

namespace My\App;

#[\CuyZ\Valinor\Mapper\AsConverter]
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class CastToBool
{
    /**
     * @param callable(mixed): bool $next
     */
    public function map(string $value, callable $next): bool
    {
        $value = match ($value) {
            'yes', 'on' => true,
            'no', 'off' => false,
            default => $value,
        };
        
        return $next($value);
    }
}

final class User
{
    public string $name;
    
    #[\My\App\CastToBool]
    public bool $isActive;
}

$user = (new \CuyZ\Valinor\MapperBuilder())
    ->mapper()
    ->map(User::class, [
        'name' => 'John Doe',
        'isActive' => 'yes',
    ]);

$user->name === 'John Doe';
$user->isActive === true;

Attribute converters can also be used on function parameters when mapping arguments:

function someFunction(string $name, #[\My\App\CastToBool] bool $isActive) {
    // …
};

$arguments = (new \CuyZ\Valinor\MapperBuilder())
    ->argumentsMapper()
    ->mapArguments(someFunction(...), [
        'name' => 'John Doe',
        'isActive' => 'yes',
    ]);

$arguments['name'] === 'John Doe';
$arguments['isActive'] === true;

When there is no control over the converter attribute class, it is possible to register it using the registerConverter method.

(new \CuyZ\Valinor\MapperBuilder())
    ->registerConverter(\Some\External\ConverterAttribute::class)
    ->mapper()
    ->map(…);

It is also possible to register attributes that share a common interface by giving the interface name to the registration method.

namespace My\App;

interface SomeAttributeInterface {}

#[\Attribute]
final class SomeAttribute implements \My\App\SomeAttributeInterface {}

#[\Attribute]
final class SomeOtherAttribute implements \My\App\SomeAttributeInterface {}

(new \CuyZ\Valinor\MapperBuilder())
    // Registers both `SomeAttribute` and `SomeOtherAttribute` attributes
    ->registerConverter(\My\App\SomeAttributeInterface::class)
    ->mapper()
    ->map(…);

Features

  • Introduce attribute converters for granular control during mapping (0a8c0d)

Bug Fixes

  • Properly detect invalid values returned by mapping converters (e80de7)
  • Properly extract = token when reading types (9a511d)
  • Use polyfill for array_find (540741)

Other

  • Mark exception as @internal (f3eace)

Don't miss a new Valinor release

NewReleases is sending notifications on new releases.