Laminas-hydrator - Typed property

Hello!

I try to transform a PDO or QueryBuilder result set into an DTO (object with typed properties).

final class FooData
{
    public ?int $id = null;

    // other properties...
}

Example usage:


// Result set from the database
$rows = [
    [
        'id' => '1',
        // ...
    ],
    [
        'id' => '2',
        // ...
    ],
];

$strategy = new \Laminas\Hydrator\Strategy\CollectionStrategy(
    new \Laminas\Hydrator\ObjectPropertyHydrator(),
    FooData::class
);

$result = $strategy->hydrate($items);

Error message:

Typed property FooData::$id must be int or null, string used

in src\Strategy\CollectionStrategy.php Line 103

The code works if I remove the type from the properties. But that’s not what I want.
So how could this be accomplished without this error?

Hello and welcome to your forums! :smiley:

You can use the Closure strategy to create a custom conversion or you can check the suggestion in the repository:

Any comment or feedback is welcome.

Hi @froschdesign. Thank you for the tip :slight_smile:

Looks like the TypeCastStrategy #54 feature is not merged yet.
I’ve tried the ClosureStrategy. For very complex use cases, this would be a good way to go.
I have found a very simple solution for collections (arrays) with scalar types:

$hydrator = new ReflectionHydrator();
$hydrator->setNamingStrategy(new UnderscoreNamingStrategy());
$strategy = new CollectionStrategy($hydrator, $class);

$result = $strategy->hydrate($items);

This works, because the ReflectionProperty::setValue is able to cast scalar types automatically. Here is a demo: Online PHP editor | output for fou3O

Edit: The downside is that I now get this error message when I try to read undefined properties that were not in the source array (result set).

Typed property FooData::$firstName 
must not be accessed before initialization

And you can help here: test it in your application and add your feedback to the pull request. This helps to speed up the process.
Thanks in advance! :+1:

1 Like

You must ensure that your object is always created or ends in a valid state.

Indeed. The easiest way seems to be to define a default value for each property.

Example:

final class FooData
{
    public ?int $id = null;

    public ?string $username = null;

    public string $firstName = '';

    public string $lastName =  '';

    public bool $enabled =  false;
}