odan
April 13, 2021, 9:01pm
1
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!
You can use the Closure strategy to create a custom conversion or you can check the suggestion in the repository:
laminas:4.2.x ← R4c00n:type-cast-strategy
opened 09:05AM - 04 Mar 21 UTC
| Q | A
|-------------- | ------
| Documentation | no
| Bugfix … | no
| BC Break | no
| New Feature | yes
| RFC | no
| QA | no
### Description
We quite often have the case that we need to do a type cast before we can hydrate a value.
The `TypeCastStrategy` provides named constructors to cast a value to `int`, `float` or `string`.
Do you think such a feature could be useful directly in this project? And if so, would you prefer a single strategy (like the current state of this PR) or multiple ones, like `IntStrategy`, `FloatStrategy` and `StringStrategy`?
Any comment or feedback is welcome.
odan
April 14, 2021, 7:53am
3
Hi @froschdesign . Thank you for the tip
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!
You must ensure that your object is always created or ends in a valid state.
odan
April 14, 2021, 10:45am
6
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;
}