Attention
TYPO3 v12 has reached end-of-life as of April 30th 2026 and is no longer being maintained. Use the version switcher on the top left of this page to select documentation for a supported version of TYPO3.
Need more time before upgrading? You can purchase Extended Long Term Support (ELTS) for TYPO3 v12 here: TYPO3 ELTS.
Software design principles
The following principles are considered best practices and are good to know when you develop extensions for TYPO3.
- Object-Oriented Programming (OOP) organizes software design around data, or objects, rather than the functions and logic of procedural programming.
- Domain-Driven Design (DDD) is a collection of principles and patterns that help developers design elegant code around clearly defined conceptual models.
- Model-View-Controller (MVC) is a programming paradigm that leads to clear isolation of data, presentation layer, and business logic.
- Test-Driven Development (TDD) is a basic technique for generating code that is stable, resilient to errors, and legible — and therefore maintainable.
- Dependency Injection is a software design pattern that adds flexibility by removing the need for hard-coded dependencies.
- Separation of Concerns and Single-responsibility principle (SRP) are computer programming principles that help developers create modular, coherent, and maintainable software.
We also recommend to study common software design patterns.
DTO / Data Transfer Objects
A very common pattern in Extbase extensions is a "DTO" ("Data Transfer Object").
A DTO is an instance of a basic class that usually only has a constructor,
getters and setters. It is not meant to be an extension of an Extbase Abstract.
This DTO serves as pure data storage. You can use it to receive and retrieve
data in a <f: fluid
CRUD
("Create Read Update Delete") setup.
Later on, the DTO can be accessed and converted into a "proper" Extbase domain model entity: The DTO getters retrieve the data, and the Extbase domain model entity's setters receive that data:
<?php
declare(strict_types=1);
// The actual "plain" DTO, just setters and getters
class PersonDTO
{
protected string $first_name;
protected string $last_name;
public function getFirstName(): string
{
return $this->first_name;
}
public function setFirstName(string $first_name): void
{
$this->first_name = $first_name;
}
public function getLastName(): string
{
return $this->last_name;
}
public function setLastName(string $last_name): void
{
$this->last_name = $last_name;
}
}
// The Extbase domain model entity.
// Note that the getters and setters can easily be mapped
// to the DTO due to their same names!
class Person extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
protected string $first_name;
protected string $last_name;
public function getFirstName(): string
{
return $this->first_name;
}
public function setFirstName(string $first_name): void
{
$this->first_name = $first_name;
}
public function getLastName(): string
{
return $this->last_name;
}
public function setLastName(string $last_name): void
{
$this->last_name = $last_name;
}
}
// An Extbase controller utilizing DTO-to-entity transfer
class DtoController extends TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function __construct(protected MyVendor\MyExtension\Domain\Repository\PersonRepository $personRepository) {}
public function createAction(): Psr\Http\Message\ResponseInterface
{
// Set up a DTO to be filled with input data.
// The Fluid template would use <f:form> and its helpers.
$this->view->assign('personDTO', new PersonDTO());
}
public function saveAction(PersonDTO $personDTO): Psr\Http\Message\ResponseInterface
{
// Transfer all data to a proper Extbase entity.
// Create an empty entity first:
$person = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(Person::class);
// Use setters/getters for propagation
$person->setFirstName($personDTO->getFirstName());
$person->setLastName($personDTO->getLastName());
// Persist the extbase entity
$this->personRepository->add($person);
// The "old" DTO needs to further processing.
}
}
DTOs are helpful because:
- They allow to decouple pure data from processed/validated data in entities.
- They allow to structure data into distinct data models.
- They allow to use good type hinting and using setters/getters instead of using untyped and unstructured PHP arrays for data storage.
- They can be used to hide implementation details of your actual entities by transferring data like filter settings that internally gets applied to actual data models.
Some more reading: