DEV Community

Alexander Schnitzler
Alexander Schnitzler

Posted on

Extbase entities and property types

Hi folks, me again, with a post about extbase entities and property types.

We all were happy when PHP 7.4 introduced property types, right? We were able to finally switch from

class Foo {
    /**
     * @var string
     */
    private $bar;

    public function __construct() {
       $this->bar = 'string';
    }
}
Enter fullscreen mode Exit fullscreen mode

to

class Foo {
    private string $bar;

    public function __construct() {
       $this->bar = 'string';
    }
}
Enter fullscreen mode Exit fullscreen mode

except with extbase, we didn't, not to all extends. At least not until recently. Let's talk TYPO3(extbase) versions. There is this patch https://review.typo3.org/c/Packages/TYPO3.CMS/+/72005 which took care of uninitialized properties but it has only been merged to main and 11.5. So with 10.4 and prior we still have a little problem I'd like to shorty write about.

You may have noticed already that I mentioned uninitialized properties. Let me explain in detail with a couple of examples.

class Foo {
    private string $bar = '';
}
Enter fullscreen mode Exit fullscreen mode

This is an initialized property, i.e. a property which has a default value assigned. Let's say we instantiate that class and call get_object_vars() on it.


$object = new Foo();
var_dump(get_object_vars($object));
// array(1) {
//   ["bar"]=>
//   string(0) ""
// }
Enter fullscreen mode Exit fullscreen mode

You can see that get_object_vars() detects property bar, but only because it is initialized. And this is where our problem begins.

Back then, before PHP 7.4, all our properties were initialized by default with null. Because all properties were by default capable of being nulled.

class Foo {
    /**
     * @var string
     */
    private $bar;
}
Enter fullscreen mode Exit fullscreen mode

We find a lot of code snippets like these in legacy code bases. And while the phpdoc type expressed the desired/intended property type, it's actually wrong. The property can hold any value and is null by default. So what do we often do? We simply transform the code like this:

class Foo {
    private string $bar;
}
Enter fullscreen mode Exit fullscreen mode

But now, the property is unitialized, it may no longer be nulled and can only be assigned a string. PHP wise this is completely valid code. But transforming extbase entities like this in a code base prior to 11.5, you will run into trouble.

Up until 10.4, extbase used get_object_vars() to detect properties and up to and including PHP 7.3 this was no problem because all properties were always initialized and said method detected all properties. With 7.4 this changed and with extbase loosing information about the existence of properties, it may no longer set the property value when reconstituting entities from the database.

As mentioned, there is a fix in 11.5 but what if you are still on 10.4 or lower? Here's a best practice for you:

class Foo {
    private string $bar = '';
}
Enter fullscreen mode Exit fullscreen mode

If you have a property that can be initialized with a default value, like the value you also store as default in your database, do so. Even if you have a constructor that also sets a default value:

class Foo {
    private string $bar = '';

    public function __construct(string $bar) {
       $this->bar = $bar;
    }
}
Enter fullscreen mode Exit fullscreen mode

Your IDE will most likely try to tell you, you don't need the default property value, but stick with it. Also tools like PHPStan and Psalm will most likely raise an error but you need to ignore this because in this case you know better...

But what about properties that cannot be initialized, like those holding relations to other entities? Well, the only possible way is to allow and default to null:

class Foo {
    private ?Bar $bar = null;
}
Enter fullscreen mode Exit fullscreen mode

Even so if you know that relation cannot be null. Ugly, right? Let's say you are using your entities only to display data (database records) in your view and your TCA makes sure, the relation is set. Well, in that case my recommendation is to throw an Exception in the getter:

class Foo {
    private ?Bar $bar = null;

    public function getBar(): Bar
    {
        if ($this->bar instanceof Bar) {
            return $this->bar;
        }

        throw new \DomainException(
            'Foo::$bar is unexpectedly null'
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

I do recommend throwing an exception because that avoids a possible null value you have to deal with in underlying code. And since this case is never expected to happen, you are in the clear here.

And that's it for today. I hope this post helps you understand the framework problem and the workaround. And if you can update to 11.5, all the better!

Have a nice one.
Cheers.

Top comments (0)