Extbase model - extending AbstractEntity

All classes of the domain model should inherit from the class \TYPO3\CMS\Extbase\DomainObject\AbstractEntity .

An entity is an object fundamentally defined not by its attributes, but by a thread of continuity and identity, for example, a person or a blog post.

Objects stored in the database are usually entities as they can be identified by the uid and are persisted, therefore have continuity.

In the TYPO3 backend models are displayed as Database records.

Example:

Class T3docs\BlogExample\Domain\Model\Comment
class Comment extends AbstractEntity implements \Stringable
{
    protected string $author = '';

    protected string $content = '';

    public function getAuthor(): string
    {
        return $this->author;
    }

    public function setAuthor(string $author): void
    {
        $this->author = $author;
    }

    public function getContent(): string
    {
        return $this->content;
    }

    public function setContent(string $content): void
    {
        $this->content = $content;
    }
}
Copied!

Persistence: Connecting the model to the database

It is possible to define models that are not persisted to the database. However in the most common use cases you want to save your model to the database and load it from there. See Persistence: Saving Extbase models to the database.

Properties of an Extbase model

The properties of a model can be defined either as public class properties:

Class T3docs\BlogExample\Domain\Model\Tag
final class Tag extends AbstractValueObject implements \Stringable
{

    public int $priority = 0;
}
Copied!

Or public getters:

Class T3docs\BlogExample\Domain\Model\Info
class Info extends AbstractEntity implements \Stringable
{
    protected string $name = '';

    protected string $bodytext = '';

    public function getName(): string
    {
        return $this->name;
    }

    public function getBodytext(): string
    {
        return $this->bodytext;
    }

    public function setBodytext(string $bodytext): void
    {
        $this->bodytext = $bodytext;
    }
}
Copied!

A public getter takes precedence over a public property. Getters have the advantage that you can make the properties themselves protected and decide which ones should be mutable.

It is also possible to have getters for properties that are not persisted and get created on the fly:

Class T3docs\BlogExample\Domain\Model\Info
class Info extends AbstractEntity implements \Stringable
{
    protected string $name = '';

    protected string $bodytext = '';

    public function getCombinedString(): string
    {
        return $this->name . ': ' . $this->bodytext;
    }
}
Copied!

One disadvantage of using additional getters is that properties that are only defined as getters do not get displayed in the debug output in Fluid, they do however get displayed when explicitly called:

Debugging different kind of properties
Does not display "combinedString":
<f:debug>{post.info}</f:debug>

But it is there:
<f:debug>{post.info.combinedString}</f:debug>
Copied!

Typed vs. untyped properties in Extbase models

In Extbase, you can define model properties using either PHP native type declarations or traditional @var annotations. Typed properties are preferred, untyped properties are still supported for backward compatibility.

The example below demonstrates a basic model with both a typed and an untyped property:

EXT:my_extension/Classes/Domain/Model/Blog.php
<?php

declare(strict_types=1);

namespace Vendor\Extension\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Blog extends AbstractEntity
{
    /**
     * Typed property (preferred)
     *
     * @var string
     */
    protected string $title = '';

    /**
     * Untyped property (legacy-compatible)
     *
     * @var bool
     */
    protected $published = false;

    // Getters and Setters
}
Copied!
  • $title is a typed property, using PHP’s type declaration. This is the recommended approach as it enforces type safety and improves code readability.
  • $published is an untyped property, defined only with a docblock. This remains valid and is often used in older codebases.

For persisted properties (those stored in the database), ensure that the property type matches the corresponding TCA Field type to avoid data mapping errors.

Nullable and Union types are also supported.

Default values for model properties

When Extbase loads an object from the database, it does not call the constructor.

This is explained in more detail in the section thawing objects of Extbase models.

This means:

  • Property promotion in the constructor (for example __construct(public string $title = '')) does not work
  • Properties must be initialized in a different way to avoid runtime errors

Good: Set default values directly

You can assign default values when defining the property. This works for simple types such as strings, integers, booleans or nullable properties:

EXT:my_extension/Classes/Domain/Model/Blog.php
class Blog extends AbstractEntity
{
    protected string $title = '';
    protected ?\DateTime $modified = null;
}
Copied!

Good: Use initializeObject() for setup

If a property needs special setup (for example, using new ObjectStorage()), you can put that logic into a method called initializeObject(). Extbase calls this method automatically after loading the object:

EXT:my_extension/Classes/Domain/Model/Blog.php
<?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();
    }
}
Copied!

Avoid: Constructor property promotion

This will not work when the object comes from the database:

public function __construct(protected string $title = '') {}
Copied!

Since the constructor is never called during hydration, such properties remain uninitialized and can cause errors like:

Error: Typed property MyVendorMyExtensionDomainModelBlog::$title must not be accessed before initialization

To prevent this, always initialize properties either where they are defined or inside the initializeObject() method.

TCA default values

If the TCA configuration of a field defines a default value, that value is applied after initializeObject() has been called, and before data from the database is mapped to the object.

Extending existing models

It is possible, with some limitations, to extend existing Extbase models in another extension. See also Tutorial: Extending an Extbase model.