I seem to have a bit of a misunderstanding of PHP's Typed properties and uninitialized state.
Imagine that a REST-like service gets the following JSON object:
{
"firstName": "some name",
"lastName": "some last name",
"groupId": 0,
"dateOfBirth": "2000-01-01"
}
I would much prefer to have a DTO look something like this:
class Person {
private string $firstName;
private string $lastName;
private int $groupId;
private \DateTime $dateOfBirth;
// All the getters/setters, cannot have __construct due to serializer limitation
}
However, since any of these message properties may be omitted (by mistake, not a valid case), my deserialization would leave some of the fields in an unintialized state.
So, this sucks. I guess I have a couple of options:
- Declare all of them
nullable and initialize tonull(yuck) - Initialize scalar properties to their respective defaults (0, '', etc)), and object properties to
null(declare themnullable for that purpose).
Let's say I went for option #2:
class Person {
private string $firstName = '';
private string $lastName = '';
private int $groupId = 0;
private ?\DateTime $dateOfBirth = null;
// The rest
}
In a different part of the code, I have something like this:
function doSomethingWithDate(\DateTime $dateTime): string{
return ...; // does not really matter
}
...
doSomethingWithDate($person->getDateOfBirth());
...
My IDE screams with the warning:
Expected parameter of type '\DateTime', '\DateTime|null' provided
It is obvious why - getter says "hey, I can be nullable", but the method says "no, no" to that.
But what should I do to "convince" it that this is a valid scenario?
Should I have a separate set of DTOs - one for an unsafe state and another for a safe? Seems unlikely...
How would you approach this "problem"?
Update
As much as my question sounded vague and weird (I know it did :D), I'd like to elaborate on it a bit.
- My 2 internal systems communicate via internal Redis streams.
- One is a legacy code (php56-based), and the other one is php81-based
- Since the communication is internal, I totally ignored the validation aspect
- During the testing, the legacy system mistakenly sent a malformed object (did not contain any properties)
- The deserializer on the other side did the job but got the totally uninitialized object and my Redis consumer started spinning in circles attempting to process it
- After reaching the
failednumber of attempts it tried to push it to thefailedqueue, but it could not due to the serialization of uninitialized properties. - My Redis consumer got "stuck" at that very message forever.
Person::getDateOfBirth()should either return a date, or throw an exception.TypeErrorSymfony/serializer. I like the idea "check object integrity after serialization", after all, that makes a lot of sense in general, but, AFAIK, no existing validator covers theisset($property). I guess I would have to write it myself.