Common causes
- A typed property with no default (private Client $client;) that the constructor never sets
- A child class overrides __construct() without calling parent::__construct()
- Objects created without the constructor, e.g. by some ORMs, unserialize() or ReflectionClass::newInstanceWithoutConstructor()
- Setter or framework injection (setter, @required, init method) not called before use
- A conditional path in the constructor that skips the assignment
How to fix it
- Find where the property should be set. Open the class and check the constructor and setters for an assignment to the named property.
- Initialize it in the constructor. Assign every required typed property in __construct(), or use constructor property promotion: public function __construct(private Client $client) {}.
- Call the parent constructor. If a child class defines its own constructor, add parent::__construct(...) so properties declared in the parent are initialized.
- Give optional properties a default. If the value is genuinely optional, declare it nullable with a default: private ?Client $client = null; then check for null before use.
- Check initialization explicitly when lazy. For lazily created values, test with isset($this->client) (which returns false for uninitialized properties) before reading.
- Handle objects built without constructors. For unserialize() or ORM hydration, initialize in __wakeup()/__unserialize() or a lifecycle hook, or give the property a default.
PHP 8
class Mailer
{
public function __construct(
private HttpClient $client, // always set
private ?LoggerInterface $logger = null,
) {}
private function client(): HttpClient
{
return $this->client;
}
} How to stop it happening again
- Use constructor property promotion for required dependencies
- Give optional typed properties an explicit default
- Always call parent::__construct() in subclasses