Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TASK] Create stub for DTO explanation #4823

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,40 @@ extensions for TYPO3.

We also recommend to study common `software design
patterns <https://designpatternsphp.readthedocs.io/en/latest/>`__.

.. _concept-dto:

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 `AbstractEntity`.

This DTO serves as pure data storage. You can use it to receive and retrieve
data in a `<f:form>` fluid
`CRUD <https://en.wikipedia.org/wiki/Create,_read,_update_and_delete>`__
("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:

.. literalinclude:: _dto.php
:language: php
:caption: Example of DTO and AbstractEntity used in an Extbase controller

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:

* `<https://usetypo3.com/dtos-in-extbase/>`__
88 changes: 88 additions & 0 deletions Documentation/ExtensionArchitecture/BestPractises/_dto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?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.
}
}
Loading