PHP 7.4+中出现的错误:"Typed property must not be accessed before initialization"

7

我正在使用 PHP 7.4 和属性类型提示。

假设我有一个类 A,它有几个私有属性。当我使用 \SoapClient、Doctrine ORM 或任何绕过构造函数直接使用反射获取/设置属性的工具来实例化一个类时,我会遇到错误 PHP Fatal error: Uncaught Error: Typed property A::$id must not be accessed before initialization in

<?php

declare(strict_types=1);

class A
{
    private int $id;
    private string $name;

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

    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

$a = (new \ReflectionClass(A::class))->newInstanceWithoutConstructor();

var_dump($a->getId()); // Fatal error: Uncaught Error: Typed property A::$id must not be accessed before initialization in ...

我可以通过将属性声明为可空并默认设置为空值来减轻此问题。

<?php

declare(strict_types=1);

class A
{
    private ?int $id      = null;
    private ?string $name = null;

    public function __construct(?int $id, ?string $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }
}

$a = (new \ReflectionClass(A::class))->newInstanceWithoutConstructor();

var_dump($a->getId()); // NULL
var_dump($a->getName()); // NULL

然而,我不喜欢这种解决方案。我的类的目的是遵守领域规范并将领域限制封装在类设计中。在这种情况下,属性name不应该为空。潜在地,我可以将属性name声明为空字符串,但这似乎也不是一个干净的解决方案。
<?php

declare(strict_types=1);

class A
{
    private ?int $id     = null;
    private string $name = '';

    public function __construct(?int $id, string $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

$a = (new \ReflectionClass(A::class))->newInstanceWithoutConstructor();

var_dump($a->getId()); // NULL
var_dump($a->getName()); // ''

$idProperty = new \ReflectionProperty($a, 'id');
$idProperty->setAccessible(true);
if (null === $idProperty->getValue($a)) {
    $idProperty->setValue($a, 1001);
}

$nameProperty = new \ReflectionProperty($a, 'name');
$nameProperty->setAccessible(true);
if ('' === $nameProperty->getValue($a)) {
    $nameProperty->setValue($a, 'Name');
}

var_dump($a->getId()); // 1001
var_dump($a->getName()); // Name

我的问题是:有没有一种方法可以保持良好的类设计并避免遇到“Typed property must not be accessed before initialization”错误?如果没有,解决这个问题的首选方法是什么?(例如将所有属性定义为可空null或将字符串属性定义为空字符串等)


你在声明之前就访问了它。所以最简单的方法是使用默认值进行声明,例如 private int $id = 0;。如果不这样做,初始值将为NULL。 - Markus Zeller
但从领域的角度来看,这将使类状态无效,因为0不是一个合适的ID。看起来非常hacky。 - Mikhail Prosalov
我认为使整数能够为NULL更加危险并且违反了类型安全性,即使这是可能的,在我看来。 - Markus Zeller
是的,这离理想还很遥远。我不喜欢将其默认设置为可空或0。我希望有一个干净的解决方案来解决这个问题。 - Mikhail Prosalov
当您访问 getId(): int 时,您期望得到一个整数,但当它为 NULL 时,显然是不正确的。因此,您需要返回一个整数,并像这样检查:return (int)$this->id;。然后,NULL 将被转换为 0。或者将签名更改为 getId(): ?int - Markus Zeller
显示剩余2条评论
2个回答

7

我认为这里存在一个误解。未初始化的类型属性没有状态,这意味着它们没有初始值NULL。如果您想让属性为NULL,您必须明确指定。

private ?string $name = NULL;

所以,如果你不设置这些属性就试图避免这个异常是不正确的,也没有意义!类型化属性的目的是为了避免隐式初始化并始终提供明确且有意义的显式值。请不要仅将所有属性定义为可为空来消除这个异常!!因为这会破坏类型化属性和PHP 7.4的全部意义。

谢谢你的回答。将int属性定义为零,将字符串属性定义为空字符串是否更好?Null似乎有点奇怪,因为我的类不应该接受id或name的null值。 - Mikhail Prosalov
@MikhailProsalov 是的,使用相同声明类型的值初始化属性肯定更好。这也是 PHP 团队的设计目标,旨在推动开发人员始终提供明确的初始化。 - Rain
1
但是对于可空关联字段,我们该怎么办呢?如果我们将null设置为默认值,则懒加载不起作用,因为出于奇怪的原因,Doctrine不会使用代理实体对象填充它。 - ivnku

1

让我们简化事情,澄清基于反射的ORM(比如Doctrine)的工作原理。特别是,Doctrine只在从数据库加载对象时使用反射。所以id将总是被设置。考虑:

class Entity
{
    public int $id;

}
$entity = (new \ReflectionClass(Entity::class))->newInstanceWithoutConstructor();

$idProperty = new \ReflectionProperty($entity, 'id');
$idProperty->setValue($entity, 1001);

echo 'ID ' . $entity->id . "\n";

以上示例可以正常运行,不会出现错误信息。除非您计划设置所有属性,否则不要使用反射来创建没有构造函数的类。
在这一点上,唯一的问题是您希望“适当设计”的类用于什么:
$entity = new Entity();
echo 'ID ' . $entity->id . "\n";

如果在对象持久化之前允许 id 为空,则可以选择使用 ?int 路线。如果不允许,则可以考虑使用 guid 而不是序列。


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