Completing the Promised DTO Example
Completing the Promised DTO Example
~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Chapter 27 showed ProjectSummary only as a CONCEPT, WITHOUT a working provider – WITH the knowledge from chapter 57, this can NOW be completed FULLY.
Fully defining the operation
use App\Dto\ProjectSummary;
use App\State\ProjectSummaryProvider;
new Get(
uriTemplate: '/projects/{id}/summary',
output: ProjectSummary::class,
provider: ProjectSummaryProvider::class,
),Implementing the provider
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Dto\ProjectSummary;
use App\Repository\ProjectRepository;
final class ProjectSummaryProvider implements ProviderInterface
{
public function __construct(
private readonly ProjectRepository $projectRepository,
) {
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?ProjectSummary
{
$project = $this->projectRepository->find($uriVariables['id']);
if (null === $project) {
return null;
}
$summary = new ProjectSummary();
$summary->id = $project->getId();
$summary->name = $project->getName();
$summary->isRecent = $project->isRecent();
$summary->taskCount = $project->getTasks()->count();
$summary->openTaskCount = $project->getTasks()
->filter(fn ($task) => !$task->isDone())
->count();
return $summary;
}
}$uriVariables['id'] contains the {id} placeholder from the uriTemplate – EXACTLY the mechanism from chapter 39, now evaluated MANUALLY instead of via Link, since ProjectSummary is NOT a Doctrine entity of its own.
Extending the DTO with the new fields
<?php
declare(strict_types=1);
namespace App\Dto;
final class ProjectSummary
{
public int $id;
public string $name;
public bool $isRecent;
public int $taskCount;
public int $openTaskCount;
}Testing the endpoint
curl -k https://localhost/api/projects/1/summary -H "Authorization: Bearer $TOKEN"{
"id": 1,
"name": "Website Relaunch",
"isRecent": true,
"taskCount": 5,
"openTaskCount": 3
}A SINGLE endpoint that COMBINES data from MULTIPLE sources (Project fields PLUS computed task statistics) into ONE flat response – EXACTLY the use case chapter 27 NAMED as the reason for DTOs instead of groups.
Achtung: getTasks()->count() loads the ENTIRE tasks collection from the database, JUST to COUNT it – with MANY tasks, a dedicated COUNT query (via the repository) would be MORE EFFICIENT. For OUR learning example, the SIMPLE variant deliberately stays as is.
Tipp: THIS chapter closes the loop left open in chapter 27 – a GOOD example of how this course DELIBERATELY forward-references and COMPLETES LATER, instead of explaining every concept IMMEDIATELY down to the last detail.