A maioria dos desenvolvedores PHP sabe fazer CRUD.
Isso não te torna pleno. Muito menos sênior.
O que diferencia um dev mais experiente não é o que ele sabe fazer, mas o que ele evita fazer.
Vou te ensinar aqui um padrão prático, usado em projetos reais, que resolve três problemas clássicos:
- Controllers inchados
- Regras de negócio espalhadas
- Código impossível de testar ou evoluir
public function store(Request $request)
{
if (!$request->email) {
return response()->json([‘error’ => ‘Email obrigatório’], 422);
}
$user = User::create([
'name' => $request->name,
'email' => $request->email,
]);
Mail::to($user->email)->send(new WelcomeMail($user));
Log::info('Usuário criado', ['id' => $user->id]);
return response()->json($user);
}
Funciona? Funciona.
É bom? Não.
Por quê?
- Controller decide regra de negócio
- Controller cria usuário
- Controller dispara e-mail
- Controller registra log
Isso acopla tudo.
Pensamento pleno/sênior: separar responsabilidade
Controller não decide regra.
Controller orquestra.
Vamos refatorar com um Service + DTO, padrão simples e poderoso.
Criando um DTO (Data Transfer Object)
Isso evita Request sendo usado como regra de negócio.
final class CreateUserDTO
{
public function __construct(
public readonly string $name,
public readonly string $email,
) {}
}
Simples, explícito e tipado.
Criando o Service (onde a regra mora)
final class CreateUserService
{
public function execute(CreateUserDTO $dto): User
{
if (!filter_var($dto->email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Email inválido');
}
$user = User::create([
'name' => $dto->name,
'email' => $dto->email,
]);
Mail::to($user->email)->send(new WelcomeMail($user));
Log::info('Usuário criado', ['id' => $user->id]);
return $user;
}
}
Agora sim:
- Regra centralizada
- Código reutilizável
- Fácil de testar
- Fácil de evoluir
Controller limpo (como deveria ser)
public function store(Request $request, CreateUserService $service)
{
$dto = new CreateUserDTO(
name: $request->name,
email: $request->email
);
$user = $service->execute($dto);
return response()->json($user);
}
O controller:
- Recebe request
- Constrói DTO
- Chama serviço
- Retorna resposta
Nada além disso.
O ganho real (que júnior não enxerga)
Testabilidade
Agora você testa a regra sem framework:
public function test_user_creation()
{
$service = new CreateUserService();
$dto = new CreateUserDTO(
name: 'Leo',
email: '[email protected]'
);
$user = $service->execute($dto);
$this->assertEquals('Leo', $user->name);
}
Sem Request.
Sem Controller.
Sem gambiarra.
Evolução sem dor
Amanhã você precisa:
- Enviar evento para fila
- Criar usuário em sistema externo
- Validar regra nova
Você altera um lugar só.
Mentalidade sênior (isso vale ouro)
” Código que funciona não é código bom.mCódigo bom é o que aguenta mudança.”
Pleno/sênior pensa assim:
- Onde essa regra deve morar?
- O que vai mudar daqui 6 meses?
- Quem vai dar manutenção nisso?
Erros clássicos que isso evita
- Fat models
- Controllers gigantes
- Services que viram controllers
- Regra duplicada
- Código impossível de refatorar
Esse tipo de abordagem não aparece em tutorial de YouTube, mas é exatamente o que mantém sistemas vivos em produção.








