PHP moderno
não é trocar mysql_*
por Laravel


Alessandro Feitoza  /  PHPeste 2026

Alessandro Feitoza

  • Fortaleza, Ceará
  • Professor de códigos e outras computarias
  • Programador / Dev / Severino
  • PHP com Rapadura  |  PHPeste
  • Bússola Social
Alessandro Feitoza

Se eu reescrever meu legado em Laravel,

o que realmente modernizei?











O que costumamos chamar de modernizar

mysql_*
PDO
procedural
orientado a objetos
código próprio
framework
PHP 5
PHP 8.4
app isolada
Composer, CI, ecossistema

Cada passo foi importante.
Nenhum garante, sozinho, uma arquitetura sustentável.

Atualizado não significa sustentável

Aplicação A
Aplicação B
Stack
PHP e Laravel na última versão
Versões suportadas, adequadas ao produto
Testes
Quebram a cada refatoração
Protegem as regras críticas
Acoplamento
Tudo conhece tudo
Dependências explícitas
Uma mudança
Ninguém sabe o que vai quebrar
Tem lugar certo e risco previsível

Qual das duas é a moderna?

E como a aplicação A
chegou nesse estado,
se ela já está no Laravel?

Você pode migrar o framework sem migrar os problemas.

Vamo ver com os olhos 👀

Um controller que faz tudo

Um controller que faz tudo

Validar, autorizar, decidir, persistir, integrar, responder. Um método.

Single Responsibility que se exploda!

Tamanho importa? 🍆

Tamanho do documento importa? 🫦

O problema não é o tamanho do arquivo

Requisição HTTPRequest, user()
Regras de negóciodono, saldo, limite
PersistênciaEloquent, transaction
Integração externaHttp::post
A regra lê o usuário do $request: só dá para testá-la simulando HTTP.
A regra fala Eloquent: só dá para testá-la com banco.
O saldo é lido fora da transação e sem trava: duas requisições simultâneas passam pela mesma checagem.
Parceiro lento ou fora do ar vira erro na tela, com o dinheiro já movido.
Requisição HTTPRequest, user()
Regras de negóciodono, saldo, limite
PersistênciaEloquent, transaction
Integração externaHttp::post
  • Cada seta é um acoplamento
  • Problema de testabilidade
  • Bug de concorrência
  • Efeito colateral / depende do terceiro

Laravel
Framework
não é bala de prata!

O Service virou controller











Criar várias classes
não garante separação de responsabilidades.

O dev chora e mãe não vê

Pedido do negócio O limite diário passa a depender do tipo de conta e não vale entre contas do mesmo titular.
TransferService::execute()

regra misturada com query, HTTP e integração

RunScheduledTransfer

o job de agendamento copiou a mesma checagem

LimitsController

a tela "quanto ainda posso enviar" repete a query

TransferTest

para testar o limite: banco, usuário, login e Http::fake()

Uma regra de negócio. Quatro lugares para lembrar.

Vamos modernizar uma transferência

Uma transferência só pode ser concluída quando respeitar as regras de saldo, limites e autorização.

Em apenas 13 passos   

Primeiro, identifique as responsabilidades

Entrada HTTP

  • validate()
  • $request->user()
  • back()->withErrors()
  • redirect()

Regras de negócio

  • a conta é do solicitante
  • valor ≤ saldo
  • enviado hoje + valor ≤ limite

Persistência

  • ler a conta
  • somar o dia
  • debitar e creditar
  • DB::transaction()

Serviços externos

  • avisar o parceiro

Ainda não tem pasta nova, nem interface, nem padrão. Só um mapa mental.

Antes de qualquer arquitetura





  • O que é protocolo?
  • O que é regra?
  • O que é guardar?
  • O que é falar com terceiros?

A regra não deveria depender do transporte

TransferRequest é um FormRequest.

O controller traduz HTTP para um comando e um erro de negócio para uma resposta HTTP. Só isso.

O mesmo caso de uso, sem HTTP

Lembra do job que copiava a regra?

Agora ele chama a mesma regra.

Se dá para chamar de um job, dá para chamar de um teste.





Melhoramos (e muito) a testabilidade

Dependências explícitas

ConnectionInterface
Contrato do próprio Laravel. A transação fica visível, não escondida.
Accounts interface criada pela gente, sangue do nosso sangue
Fronteira com o banco: o contrato exige trava e leitura consistente do dia.
Dispatcher
Contrato do Laravel para agendar efeitos colaterais.
SourceAccount sem interface
Regra pura, uma implementação, nada para substituir.

O domínio não precisa conhecer o Laravel

Controller / JobHTTP, fila, CLI
TransferMoneycaso de uso
SourceAccountregra de negócio
AccountslockForTransfer(), move()
EloquentAccountsinfraestrutura

Não vamos banir o framework. Vamo botar ele no lugar dele.


O framework deve servir ao propósito do negócio,
não o negócio ao propósito do framework.


A regra, em PHP puro

Nenhum use Illuminate\….

TransferRejected é uma DomainException com construtores nomeados.

A fronteira, de perto

Com a conta travada, toda transferência da mesma origem espera a anterior. A soma do dia fica consistente.










Bug de concorrência foi mitigado

Testar comportamento e não a implementação

PHPUnit puro: sem banco, sem HTTP, sem mock.

Testes e Negócios, mesma lingua 👅

GERENCIAR AS CONSEQUÊNCIAS

Só vai avisar o parceiro se a transferencia der bom

🙋‍♀️ Paciente Tem sintomas
→
🥵 Sintomas São consequências, não o problema em si
→
💊 Tratamento dos sintomas
→
❤️‍🩹 Cura Resolve o problema de verdade

O requisito define a estratégia

Se o requisito é…
…então
O aviso pode atrasar, mas nunca sair para uma transferência que não aconteceu
Job na fila com afterCommit()
O evento não pode se perder, mesmo se a fila cair
Gravar o evento na mesma transação (outbox) e publicar depois
O parceiro precisa aprovar antes de concluir
Status como "Pendente" e estratégia de timeout

Filas e eventos não são soluções universais.
São respostas a garantias específicas.

A arquitetura digivolveu

TransferController::store()
HTTPvalidate, user()
Regrasdono, saldo, limite
Persistênciatransaction, increment
IntegraçãoHttp::post

A arquitetura evoluiu

TransferControllerHTTP vira comando
TransferMoneycaso de uso e transação
SourceAccountregras, PHP puro
RunScheduledTransfermesma regra, via fila
Accountscontrato, com trava
NotifyTransferPartnerdepois do commit
Regras testáveis sem banco nem HTTP
Menos dependências inesperadas
Um lugar claro para cada mudança
Infraestrutura substituível onde importa

Não confunda organização com qualidade

Um CRUD de categorias
app/Domain/Category/ Contracts/CategoryRepositoryInterface.php Contracts/CategoryServiceInterface.php DTOs/CreateCategoryDTO.php DTOs/UpdateCategoryDTO.php Factories/CategoryFactoryInterface.php Mappers/CategoryMapper.php Services/CategoryService.php … tudo isso para salvar o diabo de um nome
A transferência
app/Transfers/ TransferMoney.php TransferCommand.php SourceAccount.php TransferRejected.php Accounts.php EloquentAccounts.php uma interface, onde há fronteira

Arquitetura boa não é a que tem mais camadas.
É a que resolve o problema sem criar complexidade desnecessária.










Arquitetura proporcional ao problema

O sistema não pode parar para você modernizar.

Usuárioscontinuam transferindo
Dadoscontinuam crescendo
Integraçõescontinuam chamando
Entregascontinuam sendo cobradas

Reescrever também tem custo

regras implícitas esquecidas
comportamentos incompatíveis
migração de dados
meses sem entregar melhorias
defeitos novos em funcionalidades antigas

Reescrever pode ser a decisão certa.
Só não pode ser a decisão padrão.

Conheça o comportamento antes de mudar

Tipo desconhecido paga como conta corrente. Acima de R$ 10 mil, não paga. Alguém depende disso?

Teste de caracterização

O teste não diz que o comportamento está certo. Diz que ele não muda sem alguém perceber.

Modernização incremental

identificarum problema proteger ocomportamento refatoraruma parte validar entregar repetir, com o próximo problema Em produção, a cada balão. Não só quando acabar (porque nunca acaba)

Escolha o próximo problema com critério

frequência de mudança impacto de defeitos comece aqui deixe quieto, mesmo que feio TransferService tarifa_transferencia() relatório de 2014
custo de manutenção
risco operacional
complexidade desnecessária
"esse código é feio"
Migrar tecnologia é trocar ferramentas.
Modernizar software é diminuir o custo de digivolvê-lo.
Arquiteturaa mudança tem um lugar certo
Qualidadea mudança tem rede de proteção
Operaçãoa mudança chega e é observada

Um checklist que não pergunta o framework

A versão do PHP recebe suporte?
As dependências são gerenciadas e mantidas?
As regras críticas têm testes?
Dá para alterar um comportamento com risco controlado?
Existem logs, métricas e diagnóstico adequados?
A entrega detecta problemas antes da produção?
A arquitetura é proporcional à complexidade do produto?

O que medir

Tempo para entregar uma mudançado pedido até produção
Frequência e impacto de defeitosquanto escapa e quanto dói
Tempo de recuperaçãodo incidente até a normalidade
Facilidade de testar regras críticasquanto custa escrever o próximo teste

Nenhum indicador isolado prova que a arquitetura é boa. A tendência ao longo do tempo diz mais que o número.

O framework não é o vilão

O Laravel resolve

  • Roteamento e validação
  • Filas e jobs
  • ORM e transações
  • Autenticação
  • Container de dependências

Você decide

  • Onde mora a regra de negócio
  • Quem depende de quem
  • O que precisa de teste
  • Qual garantia cada efeito exige
  • Quanta estrutura o problema pede

Seu sistema está moderno porque usa Laravel

ou porque consegue continuar evoluindo?

Modernizar não é trocar tudo.
É tornar a próxima mudança menos arriscada do que a anterior.



Valeu o boi. Valeu o vaqueiro

slides.feitoza.tec.br



PHP moderno não é trocar mysql_* por Laravel PHPeste 2026
Alessandro Feitoza @alessandro_feitoza