Quando a gente fala de arquitetura limpa no backend, tem um ingrediente que separa o improviso do profissionalismo: os DTOs. Eles tornam o fluxo de dados previsível, seguro e fácil de evoluir — especialmente em times que querem escalar com excelência e responsabilidade. Abaixo, um guia direto ao ponto, no meu estilo, para você adotar DTOs no seu PHP (com exemplo plug-and-play).
1) O que são DTOs (Data Transfer Objects)
DTO é um objeto simples, somente com dados, feito para transportar informações entre camadas (Controller → Use Case/Service → Repository, filas, etc.).
Sem regra de negócio, sem acesso a banco, sem side effects. Apenas um contrato explícito do que entra e do que sai.
2) Por que usar DTOs (e por que isso importa)
- Clareza e contrato: o DTO define o formato dos dados. Diminui “adivinhação” e quebra de compatibilidade entre camadas.
-
Imutabilidade: com
readonly
, você evita mutações silenciosas e bugs difíceis. O que o caso de uso recebeu é o que será processado. - Segurança e governança: ao fechar o formato dos dados, você reduz risco de fields “perdidos” ou inputs inesperados.
- Testabilidade: é simples criar DTOs em testes e simular cenários sem precisar de Request/Model.
- Evolução controlada: novas versões de endpoints/fluxos podem ter novos DTOs (v2, v3…) sem quebrar o legado.
3) Como usar DTO no PHP “apenas definindo uma classe”
Abaixo está o seu exemplo de DTO — já no padrão moderno do PHP 8.x, com constructor property promotion e propriedades readonly
:
<?php
namespace App\Domain\Entidade\DTO;
class NovaEntidadeDTO
{
public function __construct(
public readonly string $nome,
public readonly string $cnpj,
public readonly array $json = [],
public readonly string $cep,
public readonly string $rua,
public readonly string $numero,
public readonly string $complemento,
public readonly string $bairro,
public readonly string $cidade,
public readonly string $estado,
public readonly string $cnpj_correios
) {}
public static function fromRequest($request): self
{
return new self(
nome: $request->nome,
cnpj: $request->cnpj,
json: $request->json ?? [],
cep: $request->cep,
rua: $request->rua,
numero: $request->numero,
complemento: $request->complemento,
bairro: $request->bairro,
cidade: $request->cidade,
estado: $request->estado,
cnpj_correios: $request->cnpj_correios
);
}
public function toArray(): array
{
return [
'nome' => $this->nome,
'cnpj' => $this->cnpj,
'json' => $this->json,
'cep' => $this->cep,
'rua' => $this->rua,
'numero' => $this->numero,
'complemento' => $this->complemento,
'bairro' => $this->bairro,
'cidade' => $this->cidade,
'estado' => $this->estado,
'cnpj_correios' => $this->cnpj_correios
];
}
}
Como integrar no fluxo (exemplo Laravel — mas serve para qualquer stack)
Controller (valida, cria o DTO e chama o caso de uso/serviço):
public function store(Request $request, CriaEntidade $service)
{
// 1) Validação (exemplo)
$request->validate([
'nome' => ['required','string'],
'cnpj' => ['required','string'],
'json' => ['nullable','array'],
'cep' => ['required','string'],
'rua' => ['required','string'],
'numero' => ['required','string'],
'complemento' => ['nullable','string'],
'bairro' => ['required','string'],
'cidade' => ['required','string'],
'estado' => ['required','string'],
'cnpj_correios' => ['required','string'],
]);
// 2) Transformação segura
$dto = \App\Domain\Entidade\DTO\NovaEntidadeDTO::fromRequest($request);
// 3) Orquestração de negócio
$entidade = $service->handle($dto);
return response()->json($entidade, 201);
}
Service/Use Case (consome o DTO e persiste via Model/Repository):
final class CriaEntidade
{
public function handle(\App\Domain\Entidade\DTO\NovaEntidadeDTO $dto)
{
// Persistência simples usando o array do DTO
return \App\Models\Entidade::create($dto->toArray());
}
}
Observação prática (Laravel): se você tiver um campo do formulário chamado
json
, ele pode colidir conceitualmente com$request->json()
(que é um método). No seu código acima funciona porque você usa$request->json
como “propriedade”. Se quiser blindar isso, uma alternativa é criar também umfromArray(array $data): self
e passar$request->validated()
.
Dicas para levar ao próximo nível (quando fizer sentido)
-
Value Objects: transformar
CNPJ
,CEP
eUF
em tipos próprios (com validação) aumenta a qualidade e reduz bugs. -
Factories nomeadas:
NovaEntidadeDTO::fromWebhook(...)
,::fromCli(...)
— ótimas quando as fontes de dados variam. -
Versionamento:
NovaEntidadeV1DTO
,NovaEntidadeV2DTO
ajudam a evoluir contratos sem quebrar integrações. -
Anotações de static analysis: adicione docblocks para o shape de arrays (
@var array<string,mixed>
), melhorando insights de Psalm/PHPStan.
Conclusão: DTOs elevam a engenharia — clareza, segurança e escala. Em ambientes corporativos, onde governança e padronização importam, eles criam um vocabulário comum entre produto, dev e negócio. Seguimos construindo com ciência, colaboração e propósito.
Top comments (0)