当我引入属性类型提示时,为什么突然出现"Typed property must not be accessed before initialization"的错误?

154

我已更新我的类定义,以使用新引入的属性类型提示,就像这样:

class Foo {

    private int $id;
    private ?string $val;
    private DateTimeInterface $createdAt;
    private ?DateTimeInterface $updatedAt;

    public function __construct(int $id) {
        $this->id = $id;
    }


    public function getId(): int { return $this->id; }
    public function getVal(): ?string { return $this->val; }
    public function getCreatedAt(): ?DateTimeInterface { return $this->createdAt; }
    public function getUpdatedAt(): ?DateTimeInterface { return $this->updatedAt; }

    public function setVal(?string $val) { $this->val = $val; }
    public function setCreatedAt(DateTimeInterface $date) { $this->createdAt = $date; }
    public function setUpdatedAt(DateTimeInterface $date) { $this->updatedAt = $date; }
}

但是当我试图在Doctrine上保存实体时,我遇到了一个错误,提示:

必须在初始化之前不访问已声明类型的属性

这不仅发生在 $id$createdAt 上,还发生在可为空的属性 $value$updatedAt 上。

2个回答

251
由于PHP 7.4引入了属性的类型提示,因此为所有属性提供有效值尤为重要,以确保所有属性的值与其声明的类型相匹配。
一个从未被赋值的属性并不具有null值,而是处于未定义的状态,这将永远不会与任何声明的类型匹配。undefined !== null。
对于上述代码,如果你执行了:
$f = new Foo(1);
$f->getVal();

你会得到:
致命错误:未捕获的错误:无法在初始化之前访问类型化属性Foo::$val
因为在访问时,$val既不是字符串也不是null。
解决这个问题的方法是为所有与声明类型匹配的属性赋值。您可以将其作为属性的默认值或在构造过程中进行赋值,具体取决于您的偏好和属性的类型。
例如,对于上述情况,可以这样做:
class Foo {

    private int $id;
    private ?string $val = null; // <-- declaring default null value for the property
    private Collection $collection;
    private DateTimeInterface $createdAt;
    private ?DateTimeInterface $updatedAt;

    public function __construct(int $id) {
        // and on the constructor we set the default values for all the other 
        // properties, so now the instance is on a valid state
        $this->id = $id;
        $this->createdAt = new DateTimeImmutable();
        $this->updatedAt = new DateTimeImmutable();

        $this->collection = new ArrayCollection();
    }

现在所有的属性都将有一个有效的值,并且实例将处于有效状态。
当您依赖来自数据库的实体值时,这种情况可能经常发生。例如,自动生成的ID,或者创建和/或更新的值;这些通常是数据库的关注点。
对于自动生成的ID,推荐的前进方式是更改类型声明为:
private ?int $id = null

对于其余的部分,只需选择适当的属性类型的值。

7
换句话说,自从 PHP 7.4 版本开始,类型提示的类成员具备了空值安全性。 - James Bond
3
如果需要的话,您还可以使用isset()来安全地检查属性是否已初始化,以避免例如使用null进行初始化。 - Max

42

对于可空类型的属性,您需要使用以下语法:

private ?string $val = null;

否则会抛出致命错误。

由于这个概念会导致不必要的致命错误,我创建了一个错误报告https://bugs.php.net/bug.php?id=79620,但没有成功,至少我尝试了一下...


29
a) 这已经在另一个答案中得到了解决。 b) 这不是一个错误。这是出于(良好的)设计考虑。如果一个属性除了定义的类型之外可能还包含null,你应该明确说明它。我预计这样的报告会被立即拒绝。 - yivi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接