Hydrating / Thawing objects of Extbase models
Hydrating (the term originates from doctrine/orm), or in Extbase terms thawing,
is the act of creating an object from a given database row. The responsible
class involved is the
\TYPO3\
.
During the process of hydrating, the
Data
creates objects to map
the raw database data onto.
Before diving into the framework internals, let us take a look at models from the user's perspective.
Creating model objects with constructor arguments
Imagine you have a table
tx_
and a
corresponding model or entity (entity is used as a synonym here)
\My
.
Now, also imagine there is a domain rule which states, that all blogs must have
a title. This rule can easily be followed by letting the blog class have a
constructor with a required argument
string $title
.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Blog extends AbstractEntity
{
protected ObjectStorage $posts;
public function __construct(protected string $title)
{
// Property "posts" is not initialized on thawing / fetching from database!!
// Must be initialized in initializeObject()!!
$this->posts = new ObjectStorage();
}
}
This example also shows how the posts
property is initialized. It is done in
the constructor because PHP does not allow setting a default value that is of
type object.
Hydrating objects with constructor arguments
Whenever the user creates new blog objects in extension code, the aforementioned
domain rule is followed. It is also possible to work on the
posts
Object
without further initialization.
new Blog
is
all one need to create a blog object with a valid state.
What happens in the
Data
however, is a totally different thing.
When hydrating an object, the
Data
cannot follow any domain rules.
Its only job is to map the raw database values onto a
Blog
instance. The
Data
could of course detect constructor arguments and try to guess
which argument corresponds to what property, but only if there is an easy
mapping, that means, if the constructor takes the argument
string $title
and updates the property title
with it.
To avoid possible errors due to guessing, the
Data
simply ignores
the constructor at all. It does so with the help of the library
doctrine/instantiator.
But there is more to all this.
Initializing objects
Have a look at the
$posts
property in the example above. If the
Data
ignores the constructor, that property is in an invalid state,
that means, uninitialized.
To address this problem and possible others, the
Data
will call the
method
initialize
on models, if it exists.
Here is an updated version of the model:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Blog extends AbstractEntity
{
protected ObjectStorage $posts;
public function __construct(protected string $title)
{
$this->initializeObject();
}
public function initializeObject(): void
{
$this->posts = new ObjectStorage();
}
}
This example demonstrates how Extbase expects the user to set up their models.
If the method
initialize
is used for initialization logic that
needs to be triggered on initial creation and on hydration. Please mind
that
__
should call
initialize
.
If there are no domain rules to follow, the recommended way to set up a model
would then still be to define a
__
and
initialize
method like this:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Blog extends AbstractEntity
{
protected ObjectStorage $posts;
public function __construct()
{
$this->initializeObject();
}
public function initializeObject(): void
{
$this->posts = new ObjectStorage();
}
}
Mutating objects
Some few more words on mutators (setter, adder, etc.). One might think that
Data
uses mutators during object hydration but it does not.
Mutators are the only way for the user (developer) to implement business rules
besides using the constructor.
The
Data
uses the internal method
Abstract
to update object properties. This
looks a bit dirty and is a way around all business rules but that is what the
Data
needs in order to leave the mutators to the users.
Warning
While the
Data
does not use any mutators, other parts of
Extbase do. Both, validation and property
mapping, either use existing mutators or gather type information from them.
Property visibility
One important thing to know is that Extbase needs entity properties to be
protected or public. As written in the former paragraph,
Abstract
is used to bypass setters.
However,
Abstract
is not able to access private properties of
child classes, hence the need to have protected or public properties.
Dependency injection
Without digging too deep into dependency injection the following statements have to be made:
- Extbase expects entities to be so-called prototypes, that means classes that do have a different state per instance.
Data
does not use dependency injection for the creation of entities, that means it does not query the object container. This also means, that dependency injection is not possible in entities.Mapper
If you think that your entities need to use/access services, you need to find other ways to implement it.
Using an event when a object is thawed
The PSR-14 event AfterObjectThawedEvent is available to modify values when creating domain objects.