Hi folks,
today I want to write about property types for lazy loaded entity properties in extbase:
use TYPO3\CMS\Extbase\Annotation as Extbase;
class Foo
{
/**
* @Extbase\ORM\Lazy
* @var Bar
*/
private $bar;
}
That's how things looked until PHP 7.3, but how do we deal with it with it with PHP 7.4+ (property types) and PHP 8.0+ (union types)?
Well, let's start with 7.4 and also take \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage
into account.
Something we cannot do with PHP 7.4 is to use union types. The following example look neat but is impossible to implement.
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
class Foo
{
/**
* @Extbase\ORM\Lazy
*/
private Bar|LazyLoadingProxy|null $bar = null;
}
But missing union types in PHP 7.4 aren't the only limitation we are facing here. There is a limitation/bug in extbase's reflection framework which lets extbase only detect a single type even in phpdoc blocks. So annotating @var Bar|LazyLoadingProxy
already leads to an error as extbase does skip the whole property processing.
So the last example isn't working due to a missing language feature (in PHP 7.4), but the following example isn't working because of a limitation/bug in extbase:
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
class Foo
{
/**
* @Extbase\ORM\Lazy
* @var Bar|LazyLoadingProxy
*/
private $bar;
}
Given the circumstances, what's the best approach here to be as strict and meaningful as possible? Here's my current best practice:
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
class Foo
{
/**
* @Extbase\ORM\Lazy
* @var Bar|null
* @phpstan-var Bar|LazyLoadingProxy|null
*/
private ?object $bar = null;
}
As phpdoc take precedence in extbase, the ?object
property type is not relevant for extbase, but it already narrows down the allowed types. Depending on your IDE and it's support for custom annotations for static code analysis tools like phpstan, it will or will not know that the property can be a LazyLoadingProxy
, but it will at least know the type is null
or Bar
. PHPStan, in this case, will know the whole truth and to me that's more important than full IDE support.
Speaking if LazyLoadingProxy
...
\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::_loadRealInstance()
is our public api to resolve the actual value of the proxy but unfortunately, the return type annotation of that method is just wrong. It says object
which is neither specific nor correct. The actual return type is hard to tell because it is actually dynamic, in theory at least. It's very unlikely that the internal logic may fail but from a static code analysis standpoint, it's possible. If the actual value of the proxy cannot be resolved, _loadRealInstance()
returns the actual value of the property, which in most cases is null
. This means, the following code can break:
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
class Foo
{
/**
* @Extbase\ORM\Lazy
* @var Bar|null
* @phpstan-var Bar|LazyLoadingProxy|null
*/
private ?object $bar = null;
public function getBar(): ?Bar
{
return $this->bar instanceof LazyLoadingProxy
? $this->bar->_loadRealInstance()
: $this->bar;
}
}
It's really just a theoretical case but my best practice for those getters looks like this:
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
class Foo
{
/**
* @Extbase\ORM\Lazy
* @var Bar|null
* @phpstan-var Bar|LazyLoadingProxy|null
*/
private ?object $bar = null;
public function getBar(): ?Bar
{
if ($this->bar instanceof LazyLoadingProxy) {
/** @var Bar|null $resolvedValue */
$resolvedValue = $this->bar->_loadRealInstance();
return $this->bar = $resolvedValue instanceof Bar
? $resolvedValue
: null;
}
return $this->bar;
}
}
What about LazyObjectStorage?
Right, there is LazyObjectStorage
as well. How to deal with that? Well, this is a lot easier because LazyObjectStorage
is a sub class of ObjectStorage
which enables us to write this code:
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Foo
{
/**
* @Extbase\ORM\Lazy
*/
private ObjectStorage $bar;
public function __construct() {
$this->initializeObject();
}
public function initializeObject() {
$this->bar = new ObjectStorage();
}
public function getBar(): ObjectStorage
{
return $this->bar;
}
}
We completely omit the LazyObjectStorage
type here because we really don't need it. There is no benefit for PHPStan and the like to know, neither for your IDE or you personally because the api of LazyObjectStorage
is the same as for ObjectStorage
and the underlying code can work the same both. Easy, right?
That's it for today.
Have a nice one!
Top comments (0)