Symfony 5 迁移至PHP 7.4 类型化实体可空属性是否需初始化为null?
PHP7.4下Doctrine实体类型属性初始化问题解答
这个报错的本质是PHP7.4新引入的严格类型属性规则,和老版本PHP“没赋值的属性默认等于null”的隐式逻辑完全不一样——只要属性声明了明确类型,不管允不允许为null,只要没显式赋值就直接读,就会抛「Typed property App\Entity\Xxx::$field must not be accessed before initialization」的错误。
可空属性的初始化写法
你琢磨的把private ?string $name;改成private ?string $name = null、把private ?int $id;改成private ?int $id = null的思路是完全对的。
- 光写
private ?string $name;的话,这个属性的状态是“未初始化”,哪怕它的类型本身允许null,直接读还是会触发上面的报错; - 只有显式赋个null的初值,这个可空属性才算被系统判定为初始化完成,读的时候才会正常返回null,这也是Doctrine对实体主键字段的标准写法——毕竟主键值本来就要等Doctrine存库的时候才生成,新建实体的时候根本不可能有值。
- 只要是标记了可空的属性,不管你业务上觉得这个字段会不会为空,都得按这个规则显式初始化null,不然保不齐什么时候访问到未初始化的字段就报错。
设计上本应非空、但setter/getter还允许null的属性怎么处理
这类属性别为了躲报错就直接给个null初值完事:
- 现在你用PhpStorm自动标了可空类型,本质是因为你现有的setter/getter没做类型限制,允许传null、返回null。如果这个字段本来设计上就不该为空,正确做法是把属性的可空标记去掉,同步调整setter的入参类型、getter的返回类型,不让null值进来。调整完之后你根本不需要给它赋null初值,只要保证新建实体的时候,不管是构造函数传参、还是类里给默认值,这个属性在被访问的时候一定有合法值就行。
- 要是你暂时不想动老的setter/getter逻辑,那就留着可空标记,老老实实加个
= null的初值,至少不会触发未初始化的报错。
常见特殊类型的处理规则
DateTime类属性
和普通属性的规则没区别:
- 如果是可空的DateTime(比如
private ?DateTimeInterface $expireAt),必须加= null的初值,不然新建实体没赋值的时候直接读就报错; - 如果是设计上非空的DateTime,比如存创建时间、更新时间的字段,直接在构造函数里给个默认值(比如
$this->updatedAt = new DateTime())就行,保证任何时候访问这个属性都有个可用的对象,根本不需要初始化成null。
实体间的关联属性
同样按可空性判断:
- 允许为空的对一关联(比如有些用户没绑定上级),就按可空属性的规则加
= null初值; - 设计上必须有关联的对一属性,要么在构造函数里强制传关联实体当参数完成初始化,要么保证业务逻辑新建实体的时候一定给这个关联字段赋值,别让它停在未初始化状态;
- 对多的关联就按下面集合类的规则处理。
集合类属性
你之前写的构造函数里初始化ArrayCollection的代码必须留着:
- 你现在声明的
private Collection $absences是没有可空标记的非空类型,按PHP7.4的规则,这个属性在被访问前必须有明确的非null赋值,构造函数里$this->absences = new ArrayCollection()刚好满足这个要求; - 这也是Doctrine一直推荐的写法——避免你直接在空的属性上调
add()、remove()这种集合方法触发空值错误。 - 千万别图省事把这个集合属性改成
private ?Collection $absences = null,这类属性从设计上就要求永远是个可以操作的集合对象,根本不存在null的合法状态,这么改反而会平白多出一堆没必要的空值判断。
内容的提问来源于stack exchange,提问作者eronn
相关产品推荐
相关产品推荐

