【问题标题】:"Typed property must not be accessed before initialization" error in PHP 7.4+ [duplicate]PHP 7.4+中的“初始化之前不能访问类型化的属性”错误[重复]
【发布时间】:2020-12-28 16:36:21
【问题描述】:

我正在使用 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。
  • 但是从域的角度来看,它会使类状态无效,因为 0 不是正确的 ID。看起来很hacky。
  • 我认为有一个 int 能够为 NULL 更加hacky并且违反类型安全性,即使在我看来这是可能的。
  • 是的,离理想还很远。我不喜欢默认设置为可空或 0。我希望有一个干净的解决方案。
  • 当您访问getId(): int 时,您期望一个int,但是当它为NULL 时,这当然是不对的。因此,您需要返回一个 int,并像 return (int)$this-&gt;id; 一样自行检查。然后 NULL 将被强制转换为 0。或者将签名更改为 getId(): ?int

标签: php doctrine-orm soap-client php-7.4


【解决方案1】:

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

private ?string $name = NULL;

因此,您试图在不设置这些属性的情况下避免此异常是不正确的,而且毫无意义!。类型化属性的目的是避免隐式初始化,并始终提供明确且有意义的显式值。并且请不要仅仅将所有属性定义为可为空以使此异常消失!因为这将破坏类型属性和 PHP 7.4 的全部意义。

【讨论】:

  • 感谢您的回答。将 int 属性定义为零并将 string 属性定义为空字符串会更好吗? Null 确实看起来有点奇怪,因为我的班级不应该接受 id 或 name 的 null。
  • @MikhailProsalov 是的,使用相同声明类型的值初始化属性肯定更好。 PHP 团队的设计目标是推动开发人员始终提供显式初始化。
  • 但是我们应该如何处理可以为空的关系字段呢?如果我们将 null 设置为默认值,那么延迟加载将不起作用,因为出于奇怪原因的学说不会用代理实体对象填充它
【解决方案2】:

让我们简化一些事情并阐明基于反射的 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 为 null 是可以的,那么你就走 ?int 路线。如果没有,那么可以考虑使用 guid 而不是序列。

【讨论】:

    猜你喜欢
    • 2021-04-10
    • 2021-02-27
    • 2020-04-03
    • 2020-07-09
    • 2022-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多