4

I'm sure that question has been asked numerous times but I can't seem to find a good/satisfying answer so please bare with me.

Using PHP 7.4+, I tend to type everything I can. But I have some problems with Doctrine entities properties.

If I type everything correctly, I usually get a lot of errors like this one.

Typed property App\Entity\User::$createdAt must not be accessed before initialization

A code sample for that type of error would look something like this

/**
 * @var DateTimeInterface
 * @ORM\Column(type="datetime")
 */
protected DateTimeInterface $createdAt;

So, I used to make the property nullable even though the database field is not. So it would look something like this.

/**
 * @var DateTimeInterface|null
 * @ORM\Column(type="datetime")
 */
protected ?DateTimeInterface $createdAt = null;

But, now I have another problem. I decided to implement a static code analyzer in my project and now I'm using PHPStan. So now, when I scan my code I get errors like that one.


Line src/Entity/Trait/TimestampableEntityPropertiesTrait.php (in context of class App\Entity\Article)


16 Property App\Entity\Article::$createdAt type mapping mismatch: property can contain DateTimeInterface|null but database expects DateTimeInterface.


So, what would be the right way to handle this type of situation?

Any advice would be greatly appreciated.

EDIT

I should have mentioned that sometimes, I don't want to/can't initialize the property in the constructor since I don't have the correct values just yet.

Julien B.
  • 3,023
  • 2
  • 18
  • 33
  • Do you initialize the property in the constructor? I'd hope that phpstan would realize that this is before accessing it. – Barmar Oct 15 '21 at 03:33
  • @Barmar I edited my question but no, this is the point of my question. In the example I have, I'm dealing with a datetime "createdAt" so it doesn't really matter, I could've initialize it in the constructor. But let's say I have another datetime (that I don't have the value for just yet) or a string or any other type. Should I initialize it with invalid values and risk to persist them just because it has to have a value? – Julien B. Oct 15 '21 at 03:39
  • This seems like a problem with the type checker, if it can't deal with the fact that you can't initialize variables statically to an appropriate type. – Barmar Oct 15 '21 at 03:41

2 Answers2

3

I'm not sure if this is a bad practice, but it turned out I only had to remove that check from phpstan configuration.

# phpstan.neon
parameters:
  doctrine:
    allowNullablePropertyForRequiredField: true

EDIT:

After some digging, I realized I should be using a DTO which would allow a null value, and then transfer it to my entity once ready (and valid). This way, my entity is always valid and I do not risk flushing some invalid data in the DB.

Julien B.
  • 3,023
  • 2
  • 18
  • 33
  • And what's the point in requiring input, while then permitting the nullability (not requiring it)? From a barely logical point of view, one probably can have it either way - but both ways combined appear self-defeating. – Martin Zeitler Oct 17 '21 at 03:16
  • Well, I do that only with some properties in entities and I do want to type to avoid errors (like a string being set on a Datetime field or something like that). – Julien B. Oct 17 '21 at 03:22
1

phpstan probably isn't that wrong about the code smell ...
the actual Doctrine annotation would be nullable=true:

/**
 * @ORM\Column(type="datetime", nullable=true)
 */

Then the ORM would stop complaining about the unexpected NULL value.
Without it, created_at would have NOT NULL set; therefore it's "required".

The point is, that when it's nullable=true, then it's not required anymore.
And when it's not required anymore, phpstan would also stop to complain.

While on the other hand, when letting phpstan ignore these conflicts on the application level with allowNullablePropertyForRequiredField: true, this has zero impact on the underlying database, which would reject the record, based upon the annotations used to generate the database table.

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • This is wrong, I don't want the data to be nullable in the database, I want it to be "temporarily nullable" in PHP. – Julien B. Oct 17 '21 at 03:32
  • This isn't possible with an annotated model; besides it would be pointless, to have an abstraction layer, which doesn't represent the underlying reality accurately. You might label the only reliable approach as "wrong" ...because this might not be the only code smell. As stated, with ORM you can have it either way, but not both ways. Listen to `phpstan` ... – Martin Zeitler Oct 17 '21 at 03:57
  • Unless the column would be `nullable`, you'd have to manually `ALTER TABLE`, in order not to drop records... this defeats the idea of using an ORM: having a code-defined abstraction of the data-source. One still can check on the application level, if the value is present. – Martin Zeitler Oct 17 '21 at 04:03
  • If something is "wrong", then it's ORM models, which cannot be saved at any given time... which is the reason, why `phpstan` correctly reports the issue. Ignoring it won't make it any better. – Martin Zeitler Oct 17 '21 at 04:16