I'm using PHP 7.4 and property type hints.
Let's say I have a class A, with a couple of private properties. When I use \SoapClient, Doctrine ORM, or any tool that instantiates a class bypassing the constructor and getting/setting properties directly using reflection, I face an error 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 ...
I can mitigate this problem by declaring properties as nullable and setting a null value by default.
<?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
However, I don't like this workaround. The point of my class is to be domain compliant and encapsulate domain constraints within the class design. In this case, the property name
shouldn't be nullable. Potentially I can declare the property name
as an empty string, but it doesn't seem like a clean solution as well.
<?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
My question is: is there a way to keep a proper class design and avoid facing the Typed property must not be accessed before initialization
error? If no, what is the preferred approach to tackle this problem? (e.g. define all properties as nullable nulls or string properties as an empty string, etc.)
I think there's a misunderstanding here. Uninitialized typed properties have no state, which means they have no initial NULL
. If you want a property to be NULL
you have to specify it explicitly.
private ?string $name = NULL;
So your attempt to avoid this exception without setting these properties is just not right and makes no sense!. The purpose of typed properties is to avoid implicit initialization and always provide an explicit value that is clear and makes sense. And please don't just define all properties as nullable to make this exception disappear!! as this will defeat the whole point of typed properties and PHP 7.4 .