Menu

Typed property must not be accessed before initialization

Published on December 06, 2019 and filed under Software engineering
Written
by Wouter Sioen and will take

4 minutes

of your time.
In short
PHP7.4 introduced this cool new feature called "typed properties" which adds the existing PHP type system to class properties.A lot of people see this "Typed property must not be accessed before initialization" error popping up though. Let's check some common pitfalls with this new feature and how to tackle them.

PHP7.4 introduced this cool new feature called “typed properties” which add the existing PHP type system to class properties. This means that you can now enforce which types a property has without having to encapsulate it in an object.

An example class containing typed properties looks like this:

class ChangeName
{
    public UserId $userId;
    public ?string $name = 'Wouter';
}

There are some limitations to Typed Properties, though.

Visibility declaration

The most simple one is that it can only be added to properties that have a visibility declaration. You thus require the public, protected, or private keyword before being able to add a type declaration. The var keyword is also supported. Let’s not use that, though, because this keyword has been deprecated for quite a while already.

class Invalid
{
    // this is invalid code giving Parse error: syntax error, unexpected 'string'
    string $invalidProperty;
}

Unsupported types

The callable and void types are not supported. Void is not allowed because there isn’t really a valid use case to have void properties. Callables are not supported because adding a callable to a property could make it dependent on a private method within that class. This would introduce invalid code because you should not be able to call the callable from outside of the class. The proposed workaround for this issue is using a Closure type instead.

This is an example of how the callable type could be misused (from the RFC to introduce typed properties)

class Invalid
{
    public callable $callable;
 
    public function __construct()
    {
        // $this->$callable is callable here
        $this->$callable = [$this, 'method'];
    }
 
    private function method() {}
}
 
$obj = new Test;
// $obj->$callable is NOT callable here
($obj->$callable)();

Uninitialized fields

The last potential problem is a “Typed property must not be accessed before initialization” error. This is happening because typed properties without a default value have a new state: uninitialized. This error is introduced because, if a constructor does not set a certain value in a typed property, it is in an invalid state. Consider this example:

class User
{
    private int $id;
    private string $username;

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

    public function getUsername()
    {
        $this->username;
    }
}

$user = new User(1);

Here, we have a user where the value of username does not respect the type, and it is thus in an invalid state. That’s why typed properties without a default value now have an uninitialized state. When calling a method that accesses such an uninitialized state, you’ll get the “Typed property User::$username must not be accessed before initialization” error.

Note that nullable typed properties also have this uninitialized state instead of having null as default value. You’ll thus also get this error in the following piece of code:

class Address
{
    private string $firstLine;
    private ?string $secondLine;

    public function __construct(string $firstLine)
    {
        $this->firstLine = $firstLine;
    }

    public function setSecondLine(?string $secondLine)
    {
        $this->secondLine = $secondLine;
    }

    public function getSecondLine()
    {
        $this->secondLine;
    }
}

// invalid usage
$address = new Address('221B Baker Street');
// throws Typed property Address::$secondLine must not be accessed before initialization
$address->getSecondLine();

// valid usage, the setSecondLine initializes the secondLine field
$address = new Address('221B Baker Street');
$address->setSecondLine(null);
$address->getSecondLine();

It is thus important to make sure that – before accessing a property – it is given a value. The best ways to enforce this are making sure the property is set in the constructor or giving them default values. It is also possible to use the isset keyword to check if a property is already initialized.

With that in mind, you should be able to safely convert your PHP7.4 code to use typed properties. Enjoy this added type safety!

Written by

Wouter Sioen

Wouter looks like the nice kid you knew at high school. But boy oh boy, when it comes to problem-solving, this developer is like a pitbull on steroids.

Learn more about Wouter Sioen

Related articles

Liskov Substitution Principle Explained

Liskov Substitution Principle Explained

Bert Ramakers

April 04, 2019

Using Prettier in PHP

Using Prettier in PHP

Maxime Fabre

November 27, 2018

Never talk to strangers

Never talk to strangers

Zvonimir Spajic

January 20, 2020