Skip to content

Fixed converter stack order causes attribute converters to be skipped #710

@clxmpfeffermann

Description

@clxmpfeffermann

Hi @romm

I am using converters to map $_POST data to strongly typed objects. Since all values in the $_POST array are strings, I manually convert them to their respective scalar type using converter functions. Here is a stripped down example:

class MyClass {
    public function __construct(
        public int $value,
    ) {}
}

$result = (new \CuyZ\Valinor\MapperBuilder())
    // Convert string to int
    ->registerConverter(function (string $value, callable $next): int {
        $result = filter_var($value, \FILTER_VALIDATE_INT);
            if ($result === false) {
                throw MessageBuilder::newError('Invalid integer format.')->build();
            }
            return $next($result);
    })
    ->mapper()
    ->map(MyClass::class, ['value' => '42']);

echo $result->value; // 42

This works fine. However, any attribute converters that I define on the property are skipped:

#[\CuyZ\Valinor\Mapper\AsConverter]
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class ClampToSeven {
    public function map(int $value, callable $next): int {
        return $next(min($value, 7));
    }
}

class MyClass {
    public function __construct(
        #[ClampToSeven]
        public int $value,
    ) {}
}

$result = (new \CuyZ\Valinor\MapperBuilder())
    // Convert string to int
    ->registerConverter(function (string $value, callable $next): int {
        $result = filter_var($value, \FILTER_VALIDATE_INT);
            if ($result === false) {
                throw MessageBuilder::newError('Invalid integer format.')->build();
            }
            return $next($result);
    })
    ->mapper()
    ->map(MyClass::class, ['value' => '42']);

echo $result->value; // expected 7, actual 42

I believe the reason for this behavior lies in the the order of the converter stack:

$stack = [
...array_map(
// @phpstan-ignore method.notFound (we know the `map` method exists)
static fn (AttributeDefinition $attribute) => $attribute->instantiate()->map(...),
$converterAttributes->toArray(),
),
...$this->converterContainer->converters(),
];

The attribute converters are on the top of the stack above the function converters. When the ClampToSeven converter is popped from the stack, the shell value is still the string '42' and the converter is therefore not executed. For my example to work as expected, ClampToSeven should run after the value was converted from string to int.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions