<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Denis Augusto</title>
    <description>The latest articles on DEV Community by Denis Augusto (@denisgusto1).</description>
    <link>https://dev.to/denisgusto1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4003020%2F1774852b-de68-4d3c-bfca-b13d3d86c6ac.jpeg</url>
      <title>DEV Community: Denis Augusto</title>
      <link>https://dev.to/denisgusto1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denisgusto1"/>
    <language>en</language>
    <item>
      <title>Você criou uma tabela de tokens pra proteger PDF. O Laravel já fazia isso.</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 17 Aug 2026 18:19:41 +0000</pubDate>
      <link>https://dev.to/denisgusto1/voce-criou-uma-tabela-de-tokens-pra-proteger-pdf-o-laravel-ja-fazia-isso-3ggo</link>
      <guid>https://dev.to/denisgusto1/voce-criou-uma-tabela-de-tokens-pra-proteger-pdf-o-laravel-ja-fazia-isso-3ggo</guid>
      <description>&lt;h2&gt;
  
  
  O contrato do cliente tá numa URL que qualquer um adivinha
&lt;/h2&gt;

&lt;p&gt;A tarefa parecia simples: o cliente precisa baixar a nota fiscal dele.&lt;/p&gt;

&lt;p&gt;Você salvou em &lt;code&gt;storage/app/public/notas/&lt;/code&gt;, rodou &lt;code&gt;php artisan storage:link&lt;/code&gt;, mandou o link e foi feliz. &lt;code&gt;https://app.com/storage/notas/nota-1042.pdf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Semanas depois cai a ficha. Aquele arquivo está &lt;strong&gt;aberto na internet&lt;/strong&gt;. Sem login, sem nada. E o nome é sequencial: quem baixou a &lt;code&gt;nota-1042.pdf&lt;/code&gt; só precisa de curiosidade e cinco segundos pra tentar a &lt;code&gt;1041&lt;/code&gt;. E a &lt;code&gt;1040&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Então você faz a coisa certa: tira do disco público e cria um sistema pra controlar acesso. Tabela &lt;code&gt;download_tokens&lt;/code&gt;, model, geração de UUID, coluna &lt;code&gt;expires_at&lt;/code&gt;, controller que valida, e um comando no scheduler pra limpar os vencidos.&lt;/p&gt;

&lt;p&gt;Sessenta linhas depois, funciona. E aí alguém comenta no PR: "por que você não usou uma URL assinada?"&lt;/p&gt;

&lt;h2&gt;
  
  
  O sistema que você não precisava construir
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ migration + model + controller + command. tudo isso pra um PDF.&lt;/span&gt;
&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'download_tokens'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'expires_at'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;gerarLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NotaFiscal&lt;/span&gt; &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DownloadToken&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'token'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'path'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arquivo_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'user_id'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'expires_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não tem nada de errado tecnicamente. O problema é o custo: mais uma tabela crescendo pra sempre, mais um comando no scheduler, mais um caminho pra testar. E você vai manter isso enquanto o projeto existir.&lt;/p&gt;

&lt;p&gt;O Laravel resolve o mesmo problema com uma assinatura criptográfica na própria URL. &lt;strong&gt;Sem estado, sem tabela, sem limpeza.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Como uma URL assinada funciona
&lt;/h2&gt;

&lt;p&gt;A ideia é bonita de simples: o Laravel monta a URL com os parâmetros que você quer, calcula um hash disso tudo usando a &lt;code&gt;APP_KEY&lt;/code&gt; e cola o hash no final.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/notas/1042/download?expires=1755388800&amp;amp;signature=8f3a9c...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando o request chega, ele recalcula o hash e compara. Mexeu em qualquer coisa — o ID, a data de expiração, uma vírgula — a assinatura não fecha e o acesso morre com 403.&lt;/p&gt;

&lt;p&gt;Ou seja: &lt;strong&gt;o "token" é a própria URL.&lt;/strong&gt; Não tem o que guardar, porque a validade está embutida nela.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duas linhas, dois cenários
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cenário 1: o arquivo tá no S3 (ou em qualquer disco que gere URL temporária).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aqui é literalmente uma linha, e o download nem passa pelo seu servidor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s3'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;temporaryUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arquivo_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cinco minutos depois o link morre. O S3 se recusa a servir. Zero código seu envolvido.&lt;/p&gt;

&lt;p&gt;Se você usa disco &lt;code&gt;local&lt;/code&gt;, isso também funciona desde o Laravel 11 — só precisa avisar no &lt;code&gt;config/filesystems.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'local'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'root'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;storage_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app/private'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'serve'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// libera o temporaryUrl no disco local&lt;/span&gt;
    &lt;span class="s1"&gt;'throw'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cenário 2: você quer uma rota sua, pra registrar o acesso ou aplicar regra.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gerando o link&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;temporarySignedRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'notas.download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nota'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// a rota. o middleware 'signed' faz toda a validação.&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/notas/{nota}/download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NotaFiscal&lt;/span&gt; &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arquivo_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"nota-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;numero&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'notas.download'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'signed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;signed&lt;/code&gt; confere a assinatura e a expiração antes do seu código rodar. Se estiver inválido, 403 e pronto — você não escreve um &lt;code&gt;if&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como usar na prática
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nota fiscal enviada por e-mail:&lt;/strong&gt; gere com validade de 7 dias. O cliente clica direto do e-mail, sem login, e o link não serve pra mais ninguém depois.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cancelar inscrição:&lt;/strong&gt; o caso clássico. &lt;code&gt;URL::signedRoute('unsubscribe', ['user' =&amp;gt; $user-&amp;gt;id])&lt;/code&gt; sem expiração — o cara clica dois meses depois e funciona, mas ninguém consegue descadastrar outra pessoa trocando o ID na URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Convite de cadastro:&lt;/strong&gt; link com validade de 48 horas que já leva o e-mail assinado dentro. Se o convidado editar o e-mail pra outro, a assinatura quebra.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: assinatura não é autorização
&lt;/h2&gt;

&lt;p&gt;Essa é a confusão que vale gravar.&lt;/p&gt;

&lt;p&gt;Uma URL assinada garante que &lt;strong&gt;ninguém adulterou o link&lt;/strong&gt;. Ela não garante que quem está clicando é o dono do arquivo. Se o link vazar no WhatsApp, quem tiver o link entra.&lt;/p&gt;

&lt;p&gt;Pra dado sensível, as duas coisas convivem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/notas/{nota}/download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NotaFiscal&lt;/span&gt; &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Gate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// e ainda precisa ser o dono&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$nota&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arquivo_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'signed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assinatura para quem está fora do login (e-mail, webhook, convite). Policy para quem está dentro. Expiração curta em tudo que for confidencial.&lt;/p&gt;

&lt;p&gt;E o outro tropeço, o mais comum de todos: &lt;strong&gt;&lt;code&gt;storage:link&lt;/code&gt; com disco &lt;code&gt;public&lt;/code&gt; é público de verdade.&lt;/strong&gt; Não é "público pro usuário logado", é público pro Google. Se o arquivo tem dono, ele não mora ali — mora no disco privado, e sai por uma rota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: dois detalhes que economizam uma tarde
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Trocar a &lt;code&gt;APP_KEY&lt;/code&gt; invalida todas as assinaturas em circulação.&lt;/strong&gt; É até desejável (é o seu "logout de emergência" de links), mas se você rotacionar a chave num dia de disparo de e-mail, prepare o suporte.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atrás de proxy, cuide da URL.&lt;/strong&gt; Se &lt;code&gt;APP_URL&lt;/code&gt; ou os &lt;code&gt;TrustProxies&lt;/code&gt; estiverem tortos, o Laravel calcula a assinatura sobre um host diferente do que o usuário acessou e tudo dá 403. O sintoma é sempre o mesmo: funciona local, quebra em produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;A lição aqui é maior que URL assinada: &lt;strong&gt;antes de criar tabela e comando de limpeza pra resolver algo genérico, dá uma busca na doc.&lt;/strong&gt; "Expirar um link" é um problema que todo mundo tem — as chances de já vir resolvido são altas.&lt;/p&gt;

&lt;p&gt;E o código que você não escreve é o único que nunca tem bug.&lt;/p&gt;

&lt;p&gt;Agora fala a verdade: &lt;strong&gt;você tem arquivo de cliente numa pasta pública agora?&lt;/strong&gt; Se der aquele friozinho na barriga, abre o &lt;code&gt;storage/app/public&lt;/code&gt; antes de responder. Eu já achei um monte de contratos assinados lá dentro — em projeto que não era meu, felizmente. 😅&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Seu log tem 40 mil linhas e nenhuma resposta</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 17 Aug 2026 18:17:46 +0000</pubDate>
      <link>https://dev.to/denisgusto1/seu-log-tem-40-mil-linhas-e-nenhuma-resposta-2632</link>
      <guid>https://dev.to/denisgusto1/seu-log-tem-40-mil-linhas-e-nenhuma-resposta-2632</guid>
      <description>&lt;h2&gt;
  
  
  "Deu erro ao salvar, umas duas da tarde"
&lt;/h2&gt;

&lt;p&gt;É a única informação que você tem. O cliente não lembra o que clicou, não tirou print e já fechou a aba.&lt;/p&gt;

&lt;p&gt;Você abre o &lt;code&gt;laravel.log&lt;/code&gt;. Quarenta mil linhas no dia.&lt;/p&gt;

&lt;p&gt;Faz um &lt;code&gt;grep&lt;/code&gt; por "erro". Aparecem 1.200 ocorrências, e a maioria é isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-08-14 14:03:11] production.INFO: entrou
[2026-08-14 14:03:11] production.INFO: erro aqui
[2026-08-14 14:03:12] production.INFO: passou
[2026-08-14 14:03:12] production.ERROR: Erro ao salvar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Erro ao salvar &lt;strong&gt;o quê&lt;/strong&gt;? De qual usuário? Qual pedido? Qual valor? Aquele &lt;code&gt;entrou&lt;/code&gt; da linha de cima é do mesmo request ou de outro cliente que estava usando o sistema no mesmo segundo?&lt;/p&gt;

&lt;p&gt;Você tem log. Você não tem informação. São coisas diferentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema não é a falta de log. É o excesso de log inútil.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;emitir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Pedido&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'entrou no emitir'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$nota&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sefaz&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;emitir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'emitiu'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// parabéns, você registrou que algo deu errado em algum lugar 🎉&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Erro ao emitir nota'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;back&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Falha na emissão'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repara no que esse &lt;code&gt;catch&lt;/code&gt; jogou no lixo: a mensagem da exceção, o stack trace, o ID do pedido, o CNPJ, o retorno da SEFAZ. Tudo estava ali, na mão, e foi substituído por uma frase genérica.&lt;/p&gt;

&lt;p&gt;E os &lt;code&gt;Log::info('entrou')&lt;/code&gt; espalhados? Aquilo foi debug que virou permanente. Hoje eles só servem pra empurrar as linhas úteis pra fora da tela.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duas perguntas que todo log precisa responder
&lt;/h2&gt;

&lt;p&gt;Um log serve pra duas plateias: &lt;strong&gt;você, com sono, às 3h da manhã&lt;/strong&gt; — e uma &lt;strong&gt;máquina&lt;/strong&gt;, filtrando milhões de linhas.&lt;/p&gt;

&lt;p&gt;As duas querem a mesma coisa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;O que aconteceu&lt;/strong&gt;, numa mensagem que não muda nunca.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Com quem aconteceu&lt;/strong&gt;, em dados separados da mensagem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Essa separação é o pulo do gato. Repare na diferença:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ mensagem única pra cada pedido. impossível agrupar ou contar.&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Falha ao emitir nota do pedido &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; do cliente &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ mensagem estável + contexto estruturado&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Falha ao emitir nota fiscal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'pedido_id'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'cliente_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'valor'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'sefaz_code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s1"&gt;'exception'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A primeira versão gera 4.000 mensagens diferentes. Você nunca vai conseguir dizer "esse erro aconteceu 312 vezes hoje".&lt;/p&gt;

&lt;p&gt;A segunda gera &lt;strong&gt;uma&lt;/strong&gt; mensagem com 312 contextos diferentes. Dá pra contar, agrupar, filtrar por cliente e montar alerta. E passar a exceção inteira em &lt;code&gt;'exception' =&amp;gt; $e&lt;/code&gt; traz o stack trace de graça.&lt;/p&gt;

&lt;h2&gt;
  
  
  O trace_id: o fio que costura o request
&lt;/h2&gt;

&lt;p&gt;Agora a parte que muda sua vida de verdade.&lt;/p&gt;

&lt;p&gt;Mesmo com bom contexto, seu log é um monte de linhas intercaladas de 200 usuários simultâneos. O que falta é saber &lt;strong&gt;quais linhas pertencem à mesma requisição&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um middleware resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdicionarContexto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'trace_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partir daí, &lt;strong&gt;todo&lt;/strong&gt; log escrito naquele request carrega esses dados automaticamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2026-08-14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;production.ERROR:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Falha&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ao&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;emitir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nota&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fiscal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1042&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sefaz_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;539&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"e04e1a11-e75c-4db3-b5b5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://app.com/pedidos/1042/emitir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um &lt;code&gt;grep e04e1a11&lt;/code&gt; e você tem a história completa daquele request, em ordem, sem nada de ninguém mais no meio.&lt;/p&gt;

&lt;p&gt;E tem um bônus enorme: &lt;strong&gt;o contexto atravessa a fila.&lt;/strong&gt; Job despachado dentro daquele request leva o &lt;code&gt;trace_id&lt;/code&gt; junto e continua logando com ele. Você consegue seguir o rastro do clique do usuário até o job que rodou três minutos depois em outro processo.&lt;/p&gt;

&lt;p&gt;Devolva o ID no header também (&lt;code&gt;$response-&amp;gt;headers-&amp;gt;set('X-Trace-Id', ...)&lt;/code&gt;) e mostre na tela de erro. O cliente passa a te mandar o código em vez de "deu erro às duas da tarde".&lt;/p&gt;

&lt;h2&gt;
  
  
  Níveis existem, use-os
&lt;/h2&gt;

&lt;p&gt;Isso é o que faz seu log ficar pequeno de novo. Meu critério, sem cerimônia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;debug&lt;/strong&gt; — investigação. Não vai pra produção (&lt;code&gt;LOG_LEVEL=info&lt;/code&gt; corta).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;info&lt;/strong&gt; — fato de negócio relevante: "pedido pago", "nota emitida".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;warning&lt;/strong&gt; — deu ruim mas contornamos: API lenta, retry funcionou.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;error&lt;/strong&gt; — o usuário foi prejudicado. Isso deveria virar alerta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;critical&lt;/strong&gt; — o sistema está fora.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se tudo no seu projeto é &lt;code&gt;info&lt;/code&gt;, você não tem níveis — tem um &lt;code&gt;echo&lt;/code&gt; com data.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha que dá multa
&lt;/h2&gt;

&lt;p&gt;Essa não é sobre organização, é sobre risco:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 💀 nunca&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Request recebido'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aí vai senha, token, número de cartão, CPF — em texto puro, num arquivo que a equipe toda lê, que vai pro backup e talvez pro Datadog. É incidente de segurança e é LGPD.&lt;/p&gt;

&lt;p&gt;Logue IDs, não conteúdo. Se precisar do payload, filtre antes com &lt;code&gt;$request-&amp;gt;except(['password', 'card_number', 'cpf'])&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: JSON e o próximo passo
&lt;/h2&gt;

&lt;p&gt;Se você já joga log em Loki, CloudWatch ou Datadog, troque o formatter do canal por &lt;code&gt;JsonFormatter&lt;/code&gt;. Cada linha vira um objeto pesquisável e aquele &lt;code&gt;grep&lt;/code&gt; heroico dá lugar a uma query de verdade.&lt;/p&gt;

&lt;p&gt;E se precisar carregar algo no contexto sem que apareça nos logs (um ID interno pra correlacionar com outro sistema), existe &lt;code&gt;Context::addHidden()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Log bom não é log detalhado. É log que responde uma pergunta.&lt;/p&gt;

&lt;p&gt;Da próxima vez que você escrever um &lt;code&gt;Log::&lt;/code&gt;, faça o teste rápido: &lt;strong&gt;daqui a seis meses, de madrugada, essa linha me diz o que fazer?&lt;/strong&gt; Se a resposta é "eu ia precisar abrir o código pra entender", falta contexto.&lt;/p&gt;

&lt;p&gt;Custa cinco segundos escrever um array em vez de uma string. Você recupera esse tempo no primeiro incidente.&lt;/p&gt;

&lt;p&gt;Me conta: &lt;strong&gt;qual a mensagem de log mais inútil que você já achou em produção?&lt;/strong&gt; Eu tenho &lt;code&gt;Log::info('aqui')&lt;/code&gt;, &lt;code&gt;Log::info('aqui2')&lt;/code&gt; e — meu favorito — um &lt;code&gt;Log::error('nao era pra chegar aqui')&lt;/code&gt; sem nenhum contexto, num sistema com 300 usuários. Chegou. 😅&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Middleware é porteiro, não gerente</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 17 Aug 2026 18:17:45 +0000</pubDate>
      <link>https://dev.to/denisgusto1/middleware-e-porteiro-nao-gerente-4aob</link>
      <guid>https://dev.to/denisgusto1/middleware-e-porteiro-nao-gerente-4aob</guid>
      <description>&lt;h2&gt;
  
  
  Ele começou com um &lt;code&gt;if&lt;/code&gt;. Hoje tem 80 linhas.
&lt;/h2&gt;

&lt;p&gt;Sabe como é: precisava barrar quem não tem assinatura ativa. Um middleware, três linhas, resolvido.&lt;/p&gt;

&lt;p&gt;Depois entrou o período de teste. Depois o plano legado que tem regra diferente. Depois "aproveita que já buscou a assinatura e desconta um crédito". Depois o e-mail de aviso quando faltam 3 dias pro vencimento.&lt;/p&gt;

&lt;p&gt;Hoje esse arquivo tem 80 linhas, faz quatro queries, altera dado no banco e dispara e-mail. Ele não é mais um middleware. É um Service que mora na pasta errada e roda em todo request.&lt;/p&gt;

&lt;p&gt;E o pior: &lt;strong&gt;essa regra não existe pro resto do seu sistema.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O middleware que virou gerente
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VerificarAssinatura&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$assinatura&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;assinatura&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$assinatura&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;venceu&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'planos'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// "aproveita que já tá aqui" 🙃&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;creditos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'planos'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Sem créditos'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'creditos'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'ultimo_acesso'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;vence_em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;diffInDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AssinaturaVencendo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Funciona. Passa nos testes de feature. E tem quatro problemas escondidos que só aparecem meses depois.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problema 1: middleware só existe no HTTP
&lt;/h2&gt;

&lt;p&gt;Esse é o grande. Middleware é uma camada de &lt;strong&gt;request HTTP&lt;/strong&gt;. Ela não roda em outro lugar nenhum.&lt;/p&gt;

&lt;p&gt;Então:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O comando &lt;code&gt;php artisan relatorio:gerar&lt;/code&gt; não desconta crédito.&lt;/li&gt;
&lt;li&gt;O job na fila não desconta crédito.&lt;/li&gt;
&lt;li&gt;Sua rota de API que você esqueceu de agrupar não desconta crédito.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;tinker&lt;/code&gt; passa por cima de tudo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Você não criou uma regra de negócio. Criou uma regra &lt;strong&gt;da porta da frente&lt;/strong&gt;. Qualquer outra entrada no sistema ignora ela.&lt;/p&gt;

&lt;p&gt;E, sério, isso não é hipótese: um dia alguém vai criar um endpoint novo, esquecer o middleware, e a assinatura vira um detalhe decorativo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problema 2: cobrou antes de saber se deu certo
&lt;/h2&gt;

&lt;p&gt;Repare na ordem: o &lt;code&gt;decrement('creditos')&lt;/code&gt; acontece &lt;strong&gt;antes&lt;/strong&gt; do &lt;code&gt;$next($request)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O controller ainda vai rodar. Pode estourar uma exceção, pode devolver erro de validação, o usuário pode ter mandado um PDF corrompido. Não importa — o crédito já foi.&lt;/p&gt;

&lt;p&gt;Cobrança é resultado da operação ter dado certo. Não da requisição ter chegado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problema 3: ele decide como responder
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;redirect()-&amp;gt;route('planos')&lt;/code&gt; é ótimo pra quem está no navegador. E é péssimo pra todo mundo mais.&lt;/p&gt;

&lt;p&gt;Seu app mobile chama a API e recebe... um redirect com HTML de página de planos. O front tenta dar &lt;code&gt;response.json()&lt;/code&gt; e explode com um erro que não tem nada a ver com o problema real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problema 4: e-mail dentro do middleware
&lt;/h2&gt;

&lt;p&gt;Isso significa que &lt;strong&gt;todo request&lt;/strong&gt; carrega a possibilidade de esperar um SMTP responder. E se o servidor de e-mail estiver lento, seu app inteiro fica lento — inclusive pra quem não tem nada a ver com assinatura vencendo.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que middleware faz bem
&lt;/h2&gt;

&lt;p&gt;Pensa nele como o porteiro do prédio. Ele tem um trabalho só, e faz muito bem: &lt;strong&gt;decide quem passa e quem não passa&lt;/strong&gt; — e ajusta detalhes na entrada e na saída.&lt;/p&gt;

&lt;p&gt;Cabe no middleware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autenticação e autorização de acesso (&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;signed&lt;/code&gt;, &lt;code&gt;verified&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Rate limit (&lt;code&gt;throttle&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Definir idioma, timezone, tenant do request&lt;/li&gt;
&lt;li&gt;Mexer em request/response: limpar strings, adicionar header, forçar HTTPS&lt;/li&gt;
&lt;li&gt;Correlação e observabilidade: &lt;code&gt;trace_id&lt;/code&gt; no contexto do log&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Não cabe: alterar dado de negócio, cobrar, enviar e-mail, disparar evento de domínio, decidir preço.&lt;/p&gt;

&lt;p&gt;O porteiro confere se você mora no prédio. Ele não reajusta seu condomínio.&lt;/p&gt;

&lt;h2&gt;
  
  
  O mesmo código, cada coisa no lugar
&lt;/h2&gt;

&lt;p&gt;O middleware volta a ser o que era: um porteiro.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VerificarAssinatura&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;abort_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;temAssinaturaAtiva&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Assinatura inativa'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma linha, uma decisão. E &lt;code&gt;abort_unless&lt;/code&gt; deixa o Laravel resolver o formato da resposta: HTML pra navegador, JSON pra API. Você não escolhe por ele.&lt;/p&gt;

&lt;p&gt;O crédito vira responsabilidade de quem executa a ação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GerarRelatorio&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$filtros&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Relatorio&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$assinatura&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;assinatura&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// a regra mora aqui: vale pro controller, pro job e pro comando&lt;/span&gt;
        &lt;span class="nf"&gt;throw_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;temCredito&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SemCreditosException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$filtros&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$relatorio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Relatorio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;gerar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filtros&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nv"&gt;$assinatura&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;consumirCredito&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// cobra depois de dar certo&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$relatorio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora a regra é do &lt;strong&gt;negócio&lt;/strong&gt;, não do HTTP. Rodou pelo painel, pela API, pelo comando agendado ou pela fila? Mesma regra, mesma cobrança, mesma transação.&lt;/p&gt;

&lt;p&gt;E o e-mail de "vence em 3 dias" nem devia estar no caminho de request nenhum: é um comando no scheduler rodando uma vez por dia.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: &lt;code&gt;$request-&amp;gt;user()&lt;/code&gt; vindo &lt;code&gt;null&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Enquanto estamos aqui, a confusão mais comum de middleware: ordem.&lt;/p&gt;

&lt;p&gt;Middleware global roda &lt;strong&gt;antes&lt;/strong&gt; do &lt;code&gt;auth&lt;/code&gt;. Se você registrar algo global que usa &lt;code&gt;$request-&amp;gt;user()&lt;/code&gt;, vai receber &lt;code&gt;null&lt;/code&gt; e um "call to a member function on null" que não faz sentido nenhum.&lt;/p&gt;

&lt;p&gt;Se seu middleware depende de usuário logado, ele é de rota (ou de grupo), depois do &lt;code&gt;auth&lt;/code&gt;. Nunca global.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: e se for coisa pra depois da resposta?
&lt;/h2&gt;

&lt;p&gt;Se a ideia era "registrar o acesso sem atrasar o usuário", existe o lugar certo: middleware &lt;em&gt;terminable&lt;/em&gt;, com um método &lt;code&gt;terminate()&lt;/code&gt; que roda depois da resposta ir pro navegador.&lt;/p&gt;

&lt;p&gt;Mas se o dado importa de verdade, prefira um evento e um listener na fila. Aí funciona igual em qualquer entrada do sistema — e é a mesma lógica de sempre: regra de negócio não deveria depender de como o request chegou.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Teste rápido pro seu middleware: &lt;strong&gt;se essa mesma ação rodasse por um comando Artisan, a regra ainda precisaria valer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se sim, ela não é do middleware. Ela é do domínio, e o middleware está só escondendo isso de você.&lt;/p&gt;

&lt;p&gt;Porteiro barra ou libera. Gerente decide o negócio. Quando o porteiro começa a decidir o negócio, alguém entra pela garagem e ninguém percebe.&lt;/p&gt;

&lt;p&gt;Me conta: &lt;strong&gt;qual o middleware mais gordo que você já viu?&lt;/strong&gt; Eu já achei um com 200 linhas que fazia login, criava tenant, populava dado inicial e mandava e-mail de boas-vindas — tudo em &lt;code&gt;handle()&lt;/code&gt;. 😬&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>cleancode</category>
      <category>backend</category>
    </item>
    <item>
      <title>Seu log tem 40 mil linhas e nenhuma resposta</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 17 Aug 2026 18:04:08 +0000</pubDate>
      <link>https://dev.to/denisgusto1/seu-log-tem-40-mil-linhas-e-nenhuma-resposta-5796</link>
      <guid>https://dev.to/denisgusto1/seu-log-tem-40-mil-linhas-e-nenhuma-resposta-5796</guid>
      <description>&lt;h2&gt;
  
  
  "Deu erro ao salvar, umas duas da tarde"
&lt;/h2&gt;

&lt;p&gt;É a única informação que você tem. O cliente não lembra o que clicou, não tirou print e já fechou a aba.&lt;/p&gt;

&lt;p&gt;Você abre o &lt;code&gt;laravel.log&lt;/code&gt;. Quarenta mil linhas no dia.&lt;/p&gt;

&lt;p&gt;Faz um &lt;code&gt;grep&lt;/code&gt; por "erro". Aparecem 1.200 ocorrências, e a maioria é isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-08-14 14:03:11] production.INFO: entrou
[2026-08-14 14:03:11] production.INFO: erro aqui
[2026-08-14 14:03:12] production.INFO: passou
[2026-08-14 14:03:12] production.ERROR: Erro ao salvar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Erro ao salvar &lt;strong&gt;o quê&lt;/strong&gt;? De qual usuário? Qual pedido? Qual valor? Aquele &lt;code&gt;entrou&lt;/code&gt; da linha de cima é do mesmo request ou de outro cliente que estava usando o sistema no mesmo segundo?&lt;/p&gt;

&lt;p&gt;Você tem log. Você não tem informação. São coisas diferentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema não é a falta de log. É o excesso de log inútil.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;emitir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Pedido&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'entrou no emitir'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$nota&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sefaz&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;emitir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'emitiu'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// parabéns, você registrou que algo deu errado em algum lugar 🎉&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Erro ao emitir nota'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;back&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Falha na emissão'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repara no que esse &lt;code&gt;catch&lt;/code&gt; jogou no lixo: a mensagem da exceção, o stack trace, o ID do pedido, o CNPJ, o retorno da SEFAZ. Tudo estava ali, na mão, e foi substituído por uma frase genérica.&lt;/p&gt;

&lt;p&gt;E os &lt;code&gt;Log::info('entrou')&lt;/code&gt; espalhados? Aquilo foi debug que virou permanente. Hoje eles só servem pra empurrar as linhas úteis pra fora da tela.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duas perguntas que todo log precisa responder
&lt;/h2&gt;

&lt;p&gt;Um log serve pra duas plateias: &lt;strong&gt;você, com sono, às 3h da manhã&lt;/strong&gt; — e uma &lt;strong&gt;máquina&lt;/strong&gt;, filtrando milhões de linhas.&lt;/p&gt;

&lt;p&gt;As duas querem a mesma coisa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;O que aconteceu&lt;/strong&gt;, numa mensagem que não muda nunca.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Com quem aconteceu&lt;/strong&gt;, em dados separados da mensagem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Essa separação é o pulo do gato. Repare na diferença:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ mensagem única pra cada pedido. impossível agrupar ou contar.&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Falha ao emitir nota do pedido &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; do cliente &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ mensagem estável + contexto estruturado&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Falha ao emitir nota fiscal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'pedido_id'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'cliente_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'valor'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'sefaz_code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s1"&gt;'exception'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A primeira versão gera 4.000 mensagens diferentes. Você nunca vai conseguir dizer "esse erro aconteceu 312 vezes hoje".&lt;/p&gt;

&lt;p&gt;A segunda gera &lt;strong&gt;uma&lt;/strong&gt; mensagem com 312 contextos diferentes. Dá pra contar, agrupar, filtrar por cliente e montar alerta. E passar a exceção inteira em &lt;code&gt;'exception' =&amp;gt; $e&lt;/code&gt; traz o stack trace de graça.&lt;/p&gt;

&lt;h2&gt;
  
  
  O trace_id: o fio que costura o request
&lt;/h2&gt;

&lt;p&gt;Agora a parte que muda sua vida de verdade.&lt;/p&gt;

&lt;p&gt;Mesmo com bom contexto, seu log é um monte de linhas intercaladas de 200 usuários simultâneos. O que falta é saber &lt;strong&gt;quais linhas pertencem à mesma requisição&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um middleware resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdicionarContexto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'trace_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partir daí, &lt;strong&gt;todo&lt;/strong&gt; log escrito naquele request carrega esses dados automaticamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2026-08-14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;production.ERROR:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Falha&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ao&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;emitir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nota&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fiscal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1042&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sefaz_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;539&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"e04e1a11-e75c-4db3-b5b5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://app.com/pedidos/1042/emitir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um &lt;code&gt;grep e04e1a11&lt;/code&gt; e você tem a história completa daquele request, em ordem, sem nada de ninguém mais no meio.&lt;/p&gt;

&lt;p&gt;E tem um bônus enorme: &lt;strong&gt;o contexto atravessa a fila.&lt;/strong&gt; Job despachado dentro daquele request leva o &lt;code&gt;trace_id&lt;/code&gt; junto e continua logando com ele. Você consegue seguir o rastro do clique do usuário até o job que rodou três minutos depois em outro processo.&lt;/p&gt;

&lt;p&gt;Devolva o ID no header também (&lt;code&gt;$response-&amp;gt;headers-&amp;gt;set('X-Trace-Id', ...)&lt;/code&gt;) e mostre na tela de erro. O cliente passa a te mandar o código em vez de "deu erro às duas da tarde".&lt;/p&gt;

&lt;h2&gt;
  
  
  Níveis existem, use-os
&lt;/h2&gt;

&lt;p&gt;Isso é o que faz seu log ficar pequeno de novo. Meu critério, sem cerimônia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;debug&lt;/strong&gt; — investigação. Não vai pra produção (&lt;code&gt;LOG_LEVEL=info&lt;/code&gt; corta).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;info&lt;/strong&gt; — fato de negócio relevante: "pedido pago", "nota emitida".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;warning&lt;/strong&gt; — deu ruim mas contornamos: API lenta, retry funcionou.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;error&lt;/strong&gt; — o usuário foi prejudicado. Isso deveria virar alerta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;critical&lt;/strong&gt; — o sistema está fora.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se tudo no seu projeto é &lt;code&gt;info&lt;/code&gt;, você não tem níveis — tem um &lt;code&gt;echo&lt;/code&gt; com data.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha que dá multa
&lt;/h2&gt;

&lt;p&gt;Essa não é sobre organização, é sobre risco:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 💀 nunca&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Request recebido'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aí vai senha, token, número de cartão, CPF — em texto puro, num arquivo que a equipe toda lê, que vai pro backup e talvez pro Datadog. É incidente de segurança e é LGPD.&lt;/p&gt;

&lt;p&gt;Logue IDs, não conteúdo. Se precisar do payload, filtre antes com &lt;code&gt;$request-&amp;gt;except(['password', 'card_number', 'cpf'])&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: JSON e o próximo passo
&lt;/h2&gt;

&lt;p&gt;Se você já joga log em Loki, CloudWatch ou Datadog, troque o formatter do canal por &lt;code&gt;JsonFormatter&lt;/code&gt;. Cada linha vira um objeto pesquisável e aquele &lt;code&gt;grep&lt;/code&gt; heroico dá lugar a uma query de verdade.&lt;/p&gt;

&lt;p&gt;E se precisar carregar algo no contexto sem que apareça nos logs (um ID interno pra correlacionar com outro sistema), existe &lt;code&gt;Context::addHidden()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Log bom não é log detalhado. É log que responde uma pergunta.&lt;/p&gt;

&lt;p&gt;Da próxima vez que você escrever um &lt;code&gt;Log::&lt;/code&gt;, faça o teste rápido: &lt;strong&gt;daqui a seis meses, de madrugada, essa linha me diz o que fazer?&lt;/strong&gt; Se a resposta é "eu ia precisar abrir o código pra entender", falta contexto.&lt;/p&gt;

&lt;p&gt;Custa cinco segundos escrever um array em vez de uma string. Você recupera esse tempo no primeiro incidente.&lt;/p&gt;

&lt;p&gt;Me conta: &lt;strong&gt;qual a mensagem de log mais inútil que você já achou em produção?&lt;/strong&gt; Eu tenho &lt;code&gt;Log::info('aqui')&lt;/code&gt;, &lt;code&gt;Log::info('aqui2')&lt;/code&gt; e — meu favorito — um &lt;code&gt;Log::error('nao era pra chegar aqui')&lt;/code&gt; sem nenhum contexto, num sistema com 300 usuários. Chegou. 😅&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Idempotência: a palavra difícil que salva seu webhook</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 17 Aug 2026 18:02:56 +0000</pubDate>
      <link>https://dev.to/denisgusto1/idempotencia-a-palavra-dificil-que-salva-seu-webhook-7pj</link>
      <guid>https://dev.to/denisgusto1/idempotencia-a-palavra-dificil-que-salva-seu-webhook-7pj</guid>
      <description>&lt;h2&gt;
  
  
  O cliente pagou uma vez. Seu sistema registrou duas.
&lt;/h2&gt;

&lt;p&gt;Sexta-feira, seis da tarde. Chega a mensagem no suporte: "fui cobrado duas vezes".&lt;/p&gt;

&lt;p&gt;Você abre o banco e é pior do que parecia. Não foi o gateway que cobrou duas vezes — foi o &lt;strong&gt;seu sistema&lt;/strong&gt; que criou dois pagamentos, liberou dois créditos e mandou dois e-mails de confirmação. O cliente pagou uma única vez.&lt;/p&gt;

&lt;p&gt;Você vai no log do webhook. Dois &lt;code&gt;POST /webhooks/pagamento&lt;/code&gt;. Mesmo &lt;code&gt;event_id&lt;/code&gt;. Intervalo de 40 segundos.&lt;/p&gt;

&lt;p&gt;E aí cai a ficha: o gateway não errou. Ele &lt;strong&gt;reenviou&lt;/strong&gt;. É o comportamento documentado dele. Quem errou foi seu código, que tratou o segundo envio como se fosse um pagamento novo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que todo webhook é reenviado
&lt;/h2&gt;

&lt;p&gt;Isso não é bug do provedor, é o desenho do sistema. Stripe, Mercado Pago, Asaas, PagSeguro — todos funcionam igual: eles te enviam o evento e esperam um &lt;code&gt;2xx&lt;/code&gt;. Se não vier &lt;code&gt;2xx&lt;/code&gt; em alguns segundos, eles tentam de novo. E de novo.&lt;/p&gt;

&lt;p&gt;O detalhe cruel: &lt;strong&gt;seu processamento pode ter dado certo e a resposta não chegar.&lt;/strong&gt; Timeout de 30 segundos porque você mandou e-mail dentro do request. Deploy no meio do caminho. Nginx devolvendo 502 depois do commit no banco.&lt;/p&gt;

&lt;p&gt;Do lado do gateway, isso é indistinguível de falha. Então ele reenvia. E seu código roda tudo de novo.&lt;/p&gt;

&lt;h2&gt;
  
  
  O webhook que aceita tudo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// "chegou evento, então é pagamento novo" 🙃&lt;/span&gt;
    &lt;span class="nv"&gt;$pagamento&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pagamento&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'pedido_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data.pedido_id'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'valor'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data.valor'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pago'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$pagamento&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;liberarAcesso&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pagamento&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cliente&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PagamentoConfirmado&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pagamento&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'ok'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Funciona perfeitamente no teste. Funciona no primeiro envio real.&lt;/p&gt;

&lt;p&gt;No reenvio, cria o segundo pagamento, libera acesso de novo e dispara o segundo e-mail. E como esse método é lento (e-mail dentro do request!), ele &lt;strong&gt;aumenta&lt;/strong&gt; a chance de timeout — ou seja, aumenta a chance de reenvio. O código causa o próprio problema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotência em uma frase
&lt;/h2&gt;

&lt;p&gt;A palavra assusta, o conceito é bobo: &lt;strong&gt;uma operação idempotente pode rodar dez vezes e o efeito é o mesmo de ter rodado uma.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;É o botão de elevador. Você aperta o 5 uma vez ou quinze vezes com raiva — o elevador vai uma vez pro quinto andar.&lt;/p&gt;

&lt;p&gt;Um &lt;code&gt;DELETE /pedidos/10&lt;/code&gt; é naturalmente idempotente: apagou uma vez, apagar de novo não muda nada. Um &lt;code&gt;INSERT&lt;/code&gt; não é. E webhook quase sempre virou &lt;code&gt;INSERT&lt;/code&gt; na sua mão.&lt;/p&gt;

&lt;p&gt;O que você precisa é de uma forma de perguntar "eu já vi esse evento antes?" — e a resposta tem que ser confiável mesmo com dois requests chegando ao mesmo tempo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: deixe o banco decidir
&lt;/h2&gt;

&lt;p&gt;Todo provedor manda um ID único no evento. É esse ID que você guarda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'webhook_events'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'provider'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'event_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// o guarda de verdade mora aqui&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'provider'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'event_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o controller fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\UniqueConstraintViolationException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;WebhookEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'provider'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'gateway'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'event_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UniqueConstraintViolationException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// já processamos esse evento. responde 200 pra ele parar de reenviar.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;noContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;ProcessarPagamento&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;noContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Três coisas mudaram, e cada uma resolve um problema diferente:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A checagem é um &lt;code&gt;INSERT&lt;/code&gt;, não um &lt;code&gt;SELECT&lt;/code&gt;.&lt;/strong&gt; Essa é a parte que quase todo mundo erra. &lt;code&gt;if (WebhookEvent::where('event_id', $id)-&amp;gt;exists())&lt;/code&gt; parece resolver, mas não resolve: dois requests simultâneos passam os dois pelo &lt;code&gt;if&lt;/code&gt; antes de qualquer um inserir. O &lt;code&gt;unique&lt;/code&gt; no banco é o único lugar onde essa disputa tem um vencedor só.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reenvio devolve &lt;code&gt;200&lt;/code&gt;, não erro.&lt;/strong&gt; Evento repetido não é falha — é o gateway fazendo o trabalho dele. Se você responder 500, ele reenvia mais ainda.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. O trabalho pesado foi pra fila.&lt;/strong&gt; O controller agora responde em milissegundos. Menos timeout, menos reenvio, e o e-mail que travava tudo saiu do caminho do request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como usar na prática
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;API pública sua:&lt;/strong&gt; aceite um header &lt;code&gt;Idempotency-Key&lt;/code&gt; do cliente e guarde na mesma tabela. Assim o app mobile pode repetir o &lt;code&gt;POST /pedidos&lt;/code&gt; sem medo quando o 4G cair no meio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jobs na fila:&lt;/strong&gt; job também repete. Se o worker morre depois do &lt;code&gt;charge()&lt;/code&gt; e antes do &lt;code&gt;save()&lt;/code&gt;, o retry cobra de novo. A mesma guarda vale ali dentro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Importação de planilha:&lt;/strong&gt; &lt;code&gt;updateOrCreate&lt;/code&gt; com uma chave natural (CPF, código externo) em vez de &lt;code&gt;create&lt;/code&gt;. Rodar a importação duas vezes deixa de duplicar a base.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: &lt;code&gt;ShouldBeUnique&lt;/code&gt; não é idempotência
&lt;/h2&gt;

&lt;p&gt;Vejo essa confusão direto. Marcar o job com &lt;code&gt;ShouldBeUnique&lt;/code&gt; evita &lt;strong&gt;dois jobs iguais na fila ao mesmo tempo&lt;/strong&gt;. Ele solta o lock quando o job termina.&lt;/p&gt;

&lt;p&gt;Se o webhook chegar de novo cinco minutos depois — e vai — o job entra tranquilo e processa tudo outra vez. &lt;code&gt;ShouldBeUnique&lt;/code&gt; é controle de concorrência, não memória de longo prazo. A tabela com &lt;code&gt;unique&lt;/code&gt; é a memória.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: não confie no corpo do evento
&lt;/h2&gt;

&lt;p&gt;Enquanto você está aí, uma segunda blindagem que custa pouco: valide a assinatura do webhook (todo provedor manda uma) e, para operações sensíveis, &lt;strong&gt;use o evento só como gatilho&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Em vez de confiar no &lt;code&gt;valor&lt;/code&gt; que veio no payload, pegue o ID e consulte a API do gateway pra saber o estado real. Payload é aviso; a fonte da verdade é o provedor.&lt;/p&gt;

&lt;p&gt;E confira se sua rota de webhook está fora do &lt;code&gt;VerifyCsrfToken&lt;/code&gt; e do &lt;code&gt;throttle&lt;/code&gt; agressivo — dá pra passar um bom tempo caçando um "webhook que não chega" que na verdade está tomando 419 ou 429.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Idempotência é uma daquelas coisas que ninguém implementa antes do primeiro incidente. E o primeiro incidente costuma envolver dinheiro do cliente.&lt;/p&gt;

&lt;p&gt;O bom é que o custo de acertar é ridículo: uma tabela com dois campos, uma constraint &lt;code&gt;unique&lt;/code&gt; e um &lt;code&gt;try/catch&lt;/code&gt;. Vinte minutos hoje contra uma sexta-feira inteira depois.&lt;/p&gt;

&lt;p&gt;Agora me conta: &lt;strong&gt;qual foi o seu evento duplicado?&lt;/strong&gt; Cobrança em dobro, e-mail repetido, estoque baixado duas vezes? Eu já vi um webhook de nota fiscal emitir três notas pro mesmo pedido — e o contador descobriu antes do dev. 😅&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>O bug não existe: é o cache te enganando</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 17 Aug 2026 18:02:53 +0000</pubDate>
      <link>https://dev.to/denisgusto1/o-bug-nao-existe-e-o-cache-te-enganando-4d8c</link>
      <guid>https://dev.to/denisgusto1/o-bug-nao-existe-e-o-cache-te-enganando-4d8c</guid>
      <description>&lt;h2&gt;
  
  
  "Mas eu já corrigi isso"
&lt;/h2&gt;

&lt;p&gt;O cliente avisa que o preço do produto está errado no site. Você abre o banco: 89,90. Correto.&lt;/p&gt;

&lt;p&gt;Abre a página: 129,90.&lt;/p&gt;

&lt;p&gt;Você acha que enlouqueceu. Dá um &lt;code&gt;dd()&lt;/code&gt; no controller — vem 89,90. Tira o &lt;code&gt;dd()&lt;/code&gt;, recarrega: 129,90 de novo. Você começa a duvidar do navegador, do Nginx, da existência.&lt;/p&gt;

&lt;p&gt;Aí você lembra daquele &lt;code&gt;Cache::remember()&lt;/code&gt; que você mesmo escreveu três meses atrás. Com TTL de 24 horas.&lt;/p&gt;

&lt;p&gt;O bug não existe. O cache está te mostrando um retrato de ontem — e defendendo esse retrato com unhas e dentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  O &lt;code&gt;remember()&lt;/code&gt; que todo mundo escreve
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;destaques&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// resolvido! a home ficou 400ms mais rápida 🎉&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'produtos.destaques'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Produto&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;destaque&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'categoria'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa linha é linda. Ela resolve o problema de performance de verdade, na hora, e o gráfico do APM agradece.&lt;/p&gt;

&lt;p&gt;Só que ela responde metade da pergunta. Você definiu &lt;strong&gt;como guardar&lt;/strong&gt;. Não definiu &lt;strong&gt;quando esquecer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;E o &lt;code&gt;Produto::update()&lt;/code&gt; que roda no painel do admin não tem a menor ideia de que essa chave existe. Ninguém apresentou os dois.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache é cópia. E cópia envelhece.
&lt;/h2&gt;

&lt;p&gt;Antes da solução, o jeito certo de pensar: cache não é uma otimização mágica, é uma &lt;strong&gt;segunda fonte de verdade&lt;/strong&gt; que você acabou de criar no seu sistema.&lt;/p&gt;

&lt;p&gt;Toda vez que você guarda algo em cache, você assume um compromisso: manter duas coisas sincronizadas. O banco e a cópia.&lt;/p&gt;

&lt;p&gt;Só existem três jeitos de honrar esse compromisso. Todo cache decente usa um deles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 1: quem escreve, esquece
&lt;/h2&gt;

&lt;p&gt;A mais direta. Quando o dado muda, mata a chave. Um Observer resolve isso sem espalhar &lt;code&gt;Cache::forget()&lt;/code&gt; pelo projeto inteiro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProdutoObserver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Produto&lt;/span&gt; &lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'produtos.destaques'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"produto:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Produto&lt;/span&gt; &lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Funciona bem e é fácil de ler. O ponto fraco aparece com o tempo: cada chave nova que você criar precisa entrar nessa lista. Esquecer uma é o bug do começo do post voltando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 2: chave que se aposenta sozinha
&lt;/h2&gt;

&lt;p&gt;Aqui vem o truque que eu mais gosto, porque não precisa invalidar nada: coloque a versão do dado &lt;strong&gt;dentro da chave&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fichaTecnica&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Produto&lt;/span&gt; &lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// updated_at muda no save → a chave muda → o valor velho vira órfão&lt;/span&gt;
    &lt;span class="nv"&gt;$chave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"produto:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:ficha:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;montarFichaTecnica&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// aquele cálculo caro&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Salvou o produto? O &lt;code&gt;updated_at&lt;/code&gt; mudou, a chave mudou, o valor antigo simplesmente nunca mais é pedido e expira sozinho.&lt;/p&gt;

&lt;p&gt;Não tem &lt;code&gt;forget()&lt;/code&gt;, não tem Observer, não tem lista pra manter. E funciona no driver &lt;code&gt;file&lt;/code&gt;, no &lt;code&gt;database&lt;/code&gt;, em qualquer um.&lt;/p&gt;

&lt;p&gt;O custo é lixo acumulado no cache até o TTL vencer. Em troca você ganha algo raro: é impossível esquecer de invalidar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 3: tags, quando é um grupo inteiro
&lt;/h2&gt;

&lt;p&gt;Quando um único evento invalida vários caches de uma vez, tag é o caminho:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'produtos'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"categoria:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;categoria_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;relacionados&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// depois, quando a categoria muda de nome:&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"categoria:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detalhe importante: &lt;strong&gt;tags só funcionam em Redis e Memcached.&lt;/strong&gt; Se seu &lt;code&gt;CACHE_STORE&lt;/code&gt; é &lt;code&gt;file&lt;/code&gt; ou &lt;code&gt;database&lt;/code&gt;, isso estoura na sua cara. É provavelmente o motivo número um de "funciona local, quebra em produção" (ou o contrário) quando o assunto é cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: o cache que mente no teste
&lt;/h2&gt;

&lt;p&gt;Essa merece atenção porque é traiçoeira.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;phpunit.xml&lt;/code&gt;, o &lt;code&gt;CACHE_STORE&lt;/code&gt; normalmente é &lt;code&gt;array&lt;/code&gt; — um cache que morre no fim de cada teste. Ótimo pra isolamento, péssimo pra confiança: o teste passa mesmo quando sua invalidação está errada, porque o cache nunca sobrevive o suficiente pra servir dado velho.&lt;/p&gt;

&lt;p&gt;Se o cache faz parte da regra de negócio, teste a invalidação de propósito:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'esquece o cache dos destaques quando um produto muda'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'produtos.destaques'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'velho'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Produto&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'nome'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'novo'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'produtos.destaques'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o parente confuso: &lt;code&gt;php artisan config:cache&lt;/code&gt; &lt;strong&gt;não&lt;/strong&gt; tem relação nenhuma com &lt;code&gt;Cache::&lt;/code&gt; — é outro assunto, outro arquivo, outra dor (essa eu já contei em outro post 😄).&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: o dado velho que serve pra caramba
&lt;/h2&gt;

&lt;p&gt;Às vezes o problema não é dado desatualizado, é o oposto: a chave expira, chegam 300 requests ao mesmo tempo e todos vão bater no banco pra recalcular a mesma coisa. É o famoso &lt;em&gt;cache stampede&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;O Laravel tem uma resposta pronta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// fresquinho por 5 min; até 30 min serve o valor velho e recalcula em background&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;flexible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'relatorio.vendas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calcular&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um request só paga o preço do recálculo. Os outros recebem o valor um pouco velho na hora, em vez de ficarem na fila do banco. Pra dashboard e relatório, isso é troca boa demais pra ignorar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Antes de escrever &lt;code&gt;Cache::remember()&lt;/code&gt;, responda uma pergunta: &lt;strong&gt;quem apaga isso, e quando?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se você não sabe, não é cache — é um bug com data marcada. Pode ser Observer, pode ser chave versionada, pode ser TTL curto porque o dado suporta ficar dois minutos velho. Qualquer resposta serve. A ausência de resposta é que custa caro.&lt;/p&gt;

&lt;p&gt;E me conta uma coisa: &lt;strong&gt;quanto tempo você já perdeu caçando um bug que era só cache?&lt;/strong&gt; Eu já passei uma tarde inteira num "erro de cálculo" que era um &lt;code&gt;Cache::remember&lt;/code&gt; de 24h que eu mesmo tinha escrito na semana anterior. 🙃&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Pare de chamar variável de $data, $info e $temp</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:05:23 +0000</pubDate>
      <link>https://dev.to/denisgusto1/pare-de-chamar-variavel-de-data-info-e-temp-4e15</link>
      <guid>https://dev.to/denisgusto1/pare-de-chamar-variavel-de-data-info-e-temp-4e15</guid>
      <description>&lt;h2&gt;
  
  
  Você abre o arquivo e encontra $data. De novo.
&lt;/h2&gt;

&lt;p&gt;Sabe aquele método que você precisa ler três vezes? Não porque a lógica é difícil — a lógica é boba. É porque toda variável se chama a mesma coisa com sufixo diferente.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$data&lt;/code&gt;. &lt;code&gt;$data2&lt;/code&gt;. &lt;code&gt;$dataFinal&lt;/code&gt;. &lt;code&gt;$result&lt;/code&gt;. &lt;code&gt;$temp&lt;/code&gt;. &lt;code&gt;$aux&lt;/code&gt;. &lt;code&gt;$arr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Você lê a linha 40 e precisa subir até a linha 12 pra lembrar o que tem dentro de &lt;code&gt;$result&lt;/code&gt;. Aí desce, lê mais três linhas, e esquece de novo.&lt;/p&gt;

&lt;p&gt;O código não tá complexo. Ele tá &lt;strong&gt;anônimo&lt;/strong&gt;. E anonimato custa memória de trabalho, que é justamente o recurso mais escasso de quem lê código.&lt;/p&gt;

&lt;h2&gt;
  
  
  O crime na cena
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;processar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'discount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'qty'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;$t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$d&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sum'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cinco letras soltas (&lt;code&gt;$d&lt;/code&gt;, &lt;code&gt;$v&lt;/code&gt;, &lt;code&gt;$t&lt;/code&gt;) e três nomes genéricos (&lt;code&gt;$data&lt;/code&gt;, &lt;code&gt;$result&lt;/code&gt;, &lt;code&gt;$temp&lt;/code&gt;, &lt;code&gt;$info&lt;/code&gt;) num método de quinze linhas. Pra entender que &lt;code&gt;$t&lt;/code&gt; é o total do item com desconto aplicado, você precisa executar o código na cabeça.&lt;/p&gt;

&lt;p&gt;E o pior detalhe: &lt;code&gt;$data&lt;/code&gt; aqui não é data (calendário) nem dado específico. É "coisas". É o equivalente a nomear uma pasta de "arquivos".&lt;/p&gt;

&lt;h2&gt;
  
  
  O mesmo método, com nome nas coisas
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calcularCarrinho&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$itensDoCarrinho&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$itensCalculados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nv"&gt;$totalDoCarrinho&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$itensDoCarrinho&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$percentualDesconto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'discount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'qty'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;$totalDoItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$percentualDesconto&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$totalDoCarrinho&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$totalDoItem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$itensCalculados&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$totalDoItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'items'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$itensCalculados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'sum'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$totalDoCarrinho&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesma lógica. Zero comentário adicionado. E agora dá pra ler de cima pra baixo sem parar pra decifrar nada.&lt;/p&gt;

&lt;p&gt;Repara que eu também matei o &lt;code&gt;$info&lt;/code&gt; do final. Ele existia só pra guardar o retorno por uma linha — variável que só serve de escada pro &lt;code&gt;return&lt;/code&gt; geralmente pode sumir.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sete regras que resolvem 90% dos casos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. O nome diz o conteúdo, não o tipo.&lt;/strong&gt; &lt;code&gt;$usuariosArray&lt;/code&gt; não ajuda — o PHP já sabe que é array. &lt;code&gt;$usuariosInadimplentes&lt;/code&gt; ajuda.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Booleano começa com verbo de estado.&lt;/strong&gt; &lt;code&gt;$ativo&lt;/code&gt; é ambíguo (é o objeto ativo? é a flag?). &lt;code&gt;$estaAtivo&lt;/code&gt;, &lt;code&gt;$temEstoque&lt;/code&gt;, &lt;code&gt;$podeEditar&lt;/code&gt; se leem como pergunta com resposta sim/não.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Coleção no plural, item no singular.&lt;/strong&gt; &lt;code&gt;foreach ($pedidos as $pedido)&lt;/code&gt;. Parece óbvio, mas &lt;code&gt;foreach ($pedido as $p)&lt;/code&gt; aparece muito.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Abreviação só se for universal.&lt;/strong&gt; &lt;code&gt;$id&lt;/code&gt;, &lt;code&gt;$url&lt;/code&gt;, &lt;code&gt;$html&lt;/code&gt; tudo bem. &lt;code&gt;$qtdItPed&lt;/code&gt; não é economia, é criptografia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Nome curto pra vida curta.&lt;/strong&gt; Numa closure de uma linha, &lt;code&gt;fn ($u) =&amp;gt; $u-&amp;gt;email&lt;/code&gt; é perfeitamente legível. Numa variável que vive quarenta linhas, o nome precisa se sustentar sozinho.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Use a palavra que o negócio usa.&lt;/strong&gt; Se o pessoal do financeiro fala "inadimplente", a variável se chama &lt;code&gt;$clientesInadimplentes&lt;/code&gt; — não &lt;code&gt;$clientesComProblema&lt;/code&gt;. Isso encurta reunião.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Não nomeie pelo "como", nomeie pelo "o quê".&lt;/strong&gt; &lt;code&gt;$listaOrdenadaPorDataDesc&lt;/code&gt; envelhece na primeira mudança de ordenação. &lt;code&gt;$ultimosPedidos&lt;/code&gt; sobrevive.&lt;/p&gt;

&lt;h2&gt;
  
  
  O nome que mente: pior que o nome ruim
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$temp&lt;/code&gt; é preguiça, mas pelo menos é honesto: avisa que não vai te ajudar.&lt;/p&gt;

&lt;p&gt;O problema sério é o nome que &lt;strong&gt;promete uma coisa e faz outra&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// diz que só busca. na verdade cria se não existir e ainda dispara e-mail. 😬&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUsuario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$usuario&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;firstOrCreate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$usuario&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BoasVindas&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$usuario&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quem lê &lt;code&gt;getUsuario()&lt;/code&gt; numa outra classe acha que é operação inofensiva de leitura. Chama dentro de um loop pra montar um relatório. E manda quatrocentos e-mails de boas-vindas pra base inteira.&lt;/p&gt;

&lt;p&gt;Com &lt;code&gt;$total&lt;/code&gt; acontece a mesma coisa: quem lê assume "valor final". Se aquele &lt;code&gt;$total&lt;/code&gt; é antes do frete e do imposto, o nome certo é &lt;code&gt;$subtotal&lt;/code&gt;. Um dia alguém vai somar duas vezes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nome ruim atrasa a leitura. Nome mentiroso causa bug.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: português, inglês ou os dois?
&lt;/h2&gt;

&lt;p&gt;Aqui vem a parte que sempre gera discussão — e é discussão legítima.&lt;/p&gt;

&lt;p&gt;Meu jeito: &lt;strong&gt;estrutura do framework em inglês, domínio na língua do negócio.&lt;/strong&gt; Então &lt;code&gt;public function store()&lt;/code&gt;, &lt;code&gt;$request&lt;/code&gt;, &lt;code&gt;User::create()&lt;/code&gt; ficam em inglês. Mas &lt;code&gt;$clientesInadimplentes&lt;/code&gt;, &lt;code&gt;NotaFiscal&lt;/code&gt;, &lt;code&gt;calcularComissao()&lt;/code&gt; ficam em português, porque é assim que o time e o cliente falam.&lt;/p&gt;

&lt;p&gt;O que eu evito com força é a mistura dentro do mesmo conceito: &lt;code&gt;$userInadimplente&lt;/code&gt;, &lt;code&gt;getClienteData()&lt;/code&gt;, &lt;code&gt;NotaFiscalRepository::findByCliente()&lt;/code&gt;. Isso não é bilíngue, é confuso.&lt;/p&gt;

&lt;p&gt;Se o time todo escreve em inglês e é consistente, ótimo, funciona também. O pecado mesmo é não ter combinado nada e cada arquivo seguir uma escola.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: quando o nome não sai
&lt;/h2&gt;

&lt;p&gt;Aquele momento em que você fica cinco minutos travado tentando nomear um método é informação valiosa, não falta de vocabulário.&lt;/p&gt;

&lt;p&gt;Se o nome honesto seria &lt;code&gt;processarEValidarEEnviar()&lt;/code&gt;, o problema não é o nome. É que o método faz três coisas. Dificuldade de nomear quase sempre é sintoma de responsabilidade demais num lugar só.&lt;/p&gt;

&lt;p&gt;Quebra em três, e os nomes aparecem sozinhos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Nome de variável é o único tipo de documentação que &lt;strong&gt;nunca fica desatualizado&lt;/strong&gt;, porque ele vive dentro do código. Comentário mente com o tempo. README envelhece. O nome, você é obrigado a manter.&lt;/p&gt;

&lt;p&gt;E não custa nada. Não é refatoração de arquitetura, não precisa de aprovação, não entra em sprint. É &lt;code&gt;F2&lt;/code&gt; no editor e cinco segundos de atenção na hora de escrever.&lt;/p&gt;

&lt;p&gt;Agora eu quero saber: &lt;strong&gt;qual o pior nome de variável que você já encontrou em produção?&lt;/strong&gt; Aceito &lt;code&gt;$xpto&lt;/code&gt;, &lt;code&gt;$aux2&lt;/code&gt;, &lt;code&gt;$naoMexeAqui&lt;/code&gt; e similares. Conta nos comentários que eu começo — já vi um &lt;code&gt;$listaFinal2Corrigida&lt;/code&gt; em código rodando com cliente pagando. 😅&lt;/p&gt;




</description>
      <category>cleancode</category>
      <category>php</category>
      <category>laravel</category>
      <category>beginners</category>
    </item>
    <item>
      <title>DDD no seu CRUD é canhão pra matar mosquito</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:01:57 +0000</pubDate>
      <link>https://dev.to/denisgusto1/ddd-no-seu-crud-e-canhao-pra-matar-mosquito-2jb6</link>
      <guid>https://dev.to/denisgusto1/ddd-no-seu-crud-e-canhao-pra-matar-mosquito-2jb6</guid>
      <description>&lt;h2&gt;
  
  
  Sete arquivos pra salvar um cadastro
&lt;/h2&gt;

&lt;p&gt;Peguei um projeto uma vez pra fazer uma alteração pequena: adicionar o campo "telefone secundário" no cadastro de cliente. Coisa de dez minutos, né?&lt;/p&gt;

&lt;p&gt;Uma hora e meia depois eu tinha mexido em: a entidade &lt;code&gt;Cliente&lt;/code&gt;, o value object &lt;code&gt;Telefone&lt;/code&gt;, o DTO de entrada, o mapper que traduz DTO pra entidade, a interface do repositório, a implementação Eloquent do repositório, o use case &lt;code&gt;AtualizarClienteUseCase&lt;/code&gt;, e o request do controller.&lt;/p&gt;

&lt;p&gt;Oito arquivos. Pra um campo de texto que vai direto pra uma coluna &lt;code&gt;varchar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O sistema não fazia nada de mirabolante com esse cliente. Cadastrava, listava, editava, apagava. Um CRUD. Mas estava vestido com o terno de um sistema bancário.&lt;/p&gt;

&lt;p&gt;Já pegou um projeto assim? Ou — pergunta mais desconfortável — já &lt;strong&gt;escreveu&lt;/strong&gt; um?&lt;/p&gt;

&lt;h2&gt;
  
  
  O que parece sofisticação e é só cerimônia
&lt;/h2&gt;

&lt;p&gt;Olha o caminho que um &lt;code&gt;POST /clientes&lt;/code&gt; percorria naquele projeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Controller recebe e converte pra DTO&lt;/span&gt;
&lt;span class="nv"&gt;$dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CriarClienteDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Use case recebe o DTO&lt;/span&gt;
&lt;span class="nv"&gt;$useCase&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;executar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Dentro do use case: DTO vira entidade de domínio&lt;/span&gt;
&lt;span class="nv"&gt;$cliente&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClienteEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Nome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Telefone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Repositório recebe a entidade&lt;/span&gt;
&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repositorio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;salvar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 5. Dentro do repositório: entidade vira Model&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClienteModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 6. E aí volta tudo, agora no sentido contrário 🙃&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cinco traduções do mesmo dado. Nome vira &lt;code&gt;Nome&lt;/code&gt;, &lt;code&gt;Nome&lt;/code&gt; vira string de novo, string vira coluna.&lt;/p&gt;

&lt;p&gt;Agora a pergunta honesta: &lt;strong&gt;qual bug isso evitou?&lt;/strong&gt; Qual regra de negócio ficou protegida? Nesse projeto, nenhuma. O &lt;code&gt;ClienteEntity&lt;/code&gt; era um saco de propriedades sem nenhum comportamento — exatamente o que a gente acusa de "modelo anêmico", só que espalhado em quatro camadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  DDD nunca foi sobre pastas
&lt;/h2&gt;

&lt;p&gt;Aqui mora a confusão. A maior parte do que a galera chama de DDD é a &lt;strong&gt;parte tática&lt;/strong&gt;: entidade, value object, agregado, repositório, use case. É a parte fácil de copiar, porque é estrutura de pasta.&lt;/p&gt;

&lt;p&gt;Só que o DDD é sobre a parte &lt;strong&gt;estratégica&lt;/strong&gt;: entender o negócio a fundo, conversar com quem trabalha nele, descobrir que "pedido" pro pessoal do estoque não é a mesma coisa que "pedido" pro financeiro, e desenhar fronteiras a partir disso. Linguagem ubíqua, contextos delimitados.&lt;/p&gt;

&lt;p&gt;A estrutura de pastas é consequência disso. Não substituto.&lt;/p&gt;

&lt;p&gt;Quando você copia só as pastas, o que sobra é o custo — mais arquivos, mais indireção, mais tempo pra qualquer mudança — sem o benefício, que seria um código que fala a língua do negócio.&lt;/p&gt;

&lt;h2&gt;
  
  
  A régua: complexidade de dado ou complexidade de regra?
&lt;/h2&gt;

&lt;p&gt;A pergunta que eu faço hoje antes de montar qualquer camada:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Esse dado tem regra ou só tem formulário?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se o fluxo é "recebe do form, valida formato, salva no banco, mostra numa listagem", isso é &lt;strong&gt;complexidade de dado&lt;/strong&gt;. Laravel já resolve isso lindamente com Form Request, Eloquent e Resource. Adicionar camada aí só aumenta a distância entre você e o &lt;code&gt;varchar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Agora, se o fluxo é "recebe a solicitação, verifica se o cliente tem limite, calcula juros conforme a modalidade, aplica desconto de fidelidade, agenda a cobrança e emite o título" — aí é &lt;strong&gt;complexidade de regra&lt;/strong&gt;. É aqui que separar as coisas te salva, porque a regra vive mais tempo que o framework e muda por motivos diferentes.&lt;/p&gt;

&lt;p&gt;E o ponto que quase ninguém fala: &lt;strong&gt;os dois convivem no mesmo projeto&lt;/strong&gt;. Não precisa escolher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como fica na prática
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;O CRUD continua CRUD.&lt;/strong&gt; Sem culpa nenhuma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClienteController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StoreClienteRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$cliente&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cliente&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClienteResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Três linhas. Todo mundo entende, inclusive o dev que entrar no time semana que vem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que tem regra ganha uma Action:&lt;/strong&gt; o caminho do meio que resolve 90% dos casos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AprovarEmprestimo&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Emprestimo&lt;/span&gt; &lt;span class="nv"&gt;$emprestimo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$analista&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// aqui mora a regra, isolada e testável, sem quatro camadas&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;E as peças táticas você usa avulso, onde valem a pena.&lt;/strong&gt; Value Object pra &lt;code&gt;CPF&lt;/code&gt; e &lt;code&gt;Dinheiro&lt;/code&gt;, Enum pra status, DTO na fronteira de integração. Não é pacote fechado — dá pra pegar só as ferramentas que resolvem o seu problema.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: "mas e quando o projeto crescer?"
&lt;/h2&gt;

&lt;p&gt;Esse é o argumento que eu mais ouço. E ele tem um erro escondido: assume que refatorar depois é mais caro do que carregar a estrutura desde o começo.&lt;/p&gt;

&lt;p&gt;Quase nunca é. Transformar um controller gordo em Action é trabalho de uma tarde, e você faz isso &lt;strong&gt;sabendo&lt;/strong&gt; qual é a regra de verdade, porque o negócio já se revelou.&lt;/p&gt;

&lt;p&gt;O contrário é que dói: desmontar uma arquitetura de quatro camadas que ninguém entende e que já espalhou tradução de dado por trinta arquivos. Aí é semana, não tarde.&lt;/p&gt;

&lt;p&gt;E tem o custo que não aparece no gráfico: cada dev novo no time leva três semanas pra fazer o primeiro CRUD, porque precisa aprender o dialeto local antes de escrever a primeira linha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: o teste do "conta pro estagiário"
&lt;/h2&gt;

&lt;p&gt;Antes de adicionar uma camada, tenta explicar em voz alta pra alguém que acabou de chegar &lt;strong&gt;qual problema aquilo resolve&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se a resposta sair como "é pra desacoplar do framework" ou "é boa prática", segura a onda. Não é justificativa, é slogan.&lt;/p&gt;

&lt;p&gt;Se a resposta for "porque a regra de cálculo de comissão muda por estado e a gente precisa testar as 27 combinações sem tocar no banco" — aí sim. Isso é um problema concreto, e a camada tem endereço.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Não estou dizendo que DDD é ruim. Em domínio complexo de verdade, com regra de negócio que muda toda semana e gente do negócio discutindo termo por termo, ele é o que segura o sistema em pé.&lt;/p&gt;

&lt;p&gt;O que eu tô dizendo é que &lt;strong&gt;arquitetura é resposta a um problema&lt;/strong&gt;. Sem o problema, ela é só custo com aparência de sofisticação.&lt;/p&gt;

&lt;p&gt;Cadastro de cliente não precisa de camada anticorrupção. Precisa de &lt;code&gt;Cliente::create()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;E aí, você já pegou (ou construiu) um projeto vestido pra uma complexidade que ele não tinha? Ou é do time que acha que camada nunca é demais? Vem discutir nos comentários — esse tema divide a comunidade ao meio e eu quero ouvir os dois lados. 🍿&lt;/p&gt;




</description>
      <category>laravel</category>
      <category>php</category>
      <category>cleancode</category>
      <category>backend</category>
    </item>
    <item>
      <title>Code review: como apontar o problema sem parecer babaca</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:01:56 +0000</pubDate>
      <link>https://dev.to/denisgusto1/code-review-como-apontar-o-problema-sem-parecer-babaca-19c5</link>
      <guid>https://dev.to/denisgusto1/code-review-como-apontar-o-problema-sem-parecer-babaca-19c5</guid>
      <description>&lt;h2&gt;
  
  
  O comentário tecnicamente correto que estraga o dia
&lt;/h2&gt;

&lt;p&gt;Você abre o pull request e lá está:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isso tá errado."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Três palavras. Tecnicamente, pode até estar certíssimo. Mas o efeito na pessoa do outro lado é o mesmo de levar um empurrão no corredor.&lt;/p&gt;

&lt;p&gt;E aí o PR trava. A pessoa responde defendendo a escolha. Você responde explicando melhor. Ela responde meio seca. Dois dias depois o código sai igual ao que ia sair, só que agora tem um clima estranho na daily.&lt;/p&gt;

&lt;p&gt;Já viveu isso? De um lado ou do outro? Eu já vivi dos dois — e o lado de quem escreve o comentário ruim é mais fácil de estar do que a gente imagina.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema é o meio, não a maldade
&lt;/h2&gt;

&lt;p&gt;Quase ninguém escreve comentário de review querendo ferir. O problema é estrutural.&lt;/p&gt;

&lt;p&gt;Code review é &lt;strong&gt;texto&lt;/strong&gt;, &lt;strong&gt;assíncrono&lt;/strong&gt; e &lt;strong&gt;público&lt;/strong&gt;. Não tem tom de voz, não tem cara, não tem o "risinho" que na mesa amaciaria a frase. E o time inteiro lê.&lt;/p&gt;

&lt;p&gt;Quando falta tom, quem lê &lt;strong&gt;preenche sozinho&lt;/strong&gt;. E a pessoa que acabou de passar seis horas naquele código, insegura sobre o resultado, vai preencher pro lado ruim. Sempre.&lt;/p&gt;

&lt;p&gt;O mesmo comentário dito de pé, ao lado da mesa, com uma pausa e um "cara, olha só", passa liso. Escrito, vira julgamento.&lt;/p&gt;

&lt;p&gt;Então não é sobre ser bonzinho. É sobre compensar o que o meio tira.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sete coisas que mudaram meus reviews
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Comente o código, não a pessoa
&lt;/h3&gt;

&lt;p&gt;Trocar "você" por "esse método" muda tudo, e não custa nada:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ "Você esqueceu de tratar o retorno nulo aqui."&lt;br&gt;
✅ "Esse retorno pode vir nulo quando o cliente não tem endereço — dá exception na linha de baixo."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mesma informação. Só que a segunda fala do código, e a primeira aponta o dedo.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Diga o porquê e o impacto
&lt;/h3&gt;

&lt;p&gt;Comentário sem motivo vira gosto pessoal — e ninguém aceita gosto pessoal de bom grado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ "Não usa &lt;code&gt;all()&lt;/code&gt; aqui."&lt;br&gt;
✅ "Esse &lt;code&gt;all()&lt;/code&gt; carrega a tabela inteira na memória. Em produção são uns 800 mil registros, o job vai estourar o limite. &lt;code&gt;chunkById()&lt;/code&gt; resolve."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A segunda versão não precisa de autoridade nenhuma pra convencer. O argumento se defende sozinho, e a pessoa aprende algo em vez de só obedecer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separe o que bloqueia do que é preferência
&lt;/h3&gt;

&lt;p&gt;Essa é a que mais melhora review em time, e é a mais fácil de adotar. Prefixo no comentário:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bloqueante:&lt;/code&gt;&lt;/strong&gt; — não pode subir assim (bug, falha de segurança, quebra de contrato)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sugestão:&lt;/code&gt;&lt;/strong&gt; — melhoraria, mas é decisão sua&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nit:&lt;/code&gt;&lt;/strong&gt; — implicância mínima, pode ignorar sem culpa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dúvida:&lt;/code&gt;&lt;/strong&gt; — eu não entendi, me explica&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sem isso, todo comentário pesa igual. Quem recebe dez apontamentos não sabe se tem um problema sério ou dez preferências, e trata tudo como reprovação.&lt;/p&gt;

&lt;p&gt;Com prefixo, "nit: acho que esse nome ficaria melhor no plural" é lido em dois segundos e resolvido em cinco.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pergunta de verdade, não pergunta retórica
&lt;/h3&gt;

&lt;p&gt;Existe uma diferença enorme entre curiosidade e ironia disfarçada de pergunta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ "Tem certeza que isso funciona?"&lt;br&gt;
❌ "Por que você fez desse jeito?"&lt;br&gt;
✅ "Fiquei em dúvida no caso do cliente sem contrato ativo — esse &lt;code&gt;where&lt;/code&gt; cobre isso? Posso ter perdido alguma coisa."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As duas primeiras são acusação com ponto de interrogação no fim. Todo mundo sente.&lt;/p&gt;

&lt;p&gt;E tem um bônus real na terceira: &lt;strong&gt;às vezes quem não entendeu foi você&lt;/strong&gt;. Manter a abertura de "posso ter perdido alguma coisa" já me salvou de várias sugestões erradas ditas com confiança demais.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Sugira o código, não só o problema
&lt;/h3&gt;

&lt;p&gt;O GitHub e o GitLab têm bloco de sugestão. Usa. Escrever a alternativa custa trinta segundos e muda a natureza do comentário: em vez de tarefa, vira ajuda.&lt;/p&gt;

&lt;p&gt;E força você a conferir se a sua ideia realmente funciona ali. Já desisti de uns bons comentários no meio da escrita da sugestão. 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Comente o que tá bom também
&lt;/h3&gt;

&lt;p&gt;Não é bajulação nem "elogio sanduíche". É &lt;strong&gt;calibragem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se você só aparece pra apontar problema, seus comentários viram sinal de perigo — a pessoa vê seu nome na notificação e já se contrai. Um "esse teste do cenário de borda ficou ótimo, eu não tinha pensado nesse caso" custa dez segundos e muda como todo o resto é lido.&lt;/p&gt;

&lt;p&gt;E tem um efeito colateral bom: elogiar o que você quer ver mais espalha padrão muito mais rápido do que criticar o que você não quer.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Passou de três idas e voltas, sai do texto
&lt;/h3&gt;

&lt;p&gt;Se o mesmo ponto já teve três rodadas de comentário, o problema não é mais técnico. Ou vocês estão discutindo coisas diferentes, ou tem contexto que não cabe em caixa de texto.&lt;/p&gt;

&lt;p&gt;Cinco minutos de call resolvem o que dez comentários não resolvem — e depois alguém registra no PR o que ficou combinado.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: review de gosto pessoal
&lt;/h2&gt;

&lt;p&gt;Metade dos comentários azedos do mundo são sobre coisa que máquina deveria resolver.&lt;/p&gt;

&lt;p&gt;Aspas simples ou duplas, quebra de linha, ordem dos imports, espaço antes da chave. Se isso aparece em review, o problema não é a pessoa — é que falta &lt;strong&gt;Pint&lt;/strong&gt; (ou Pint + PHPStan) no pipeline.&lt;/p&gt;

&lt;p&gt;Configura uma vez, o robô aponta, ninguém se ofende com robô. E aí o review humano fica livre pro que importa: regra de negócio, caso de borda, nome que não descreve o que faz, teste que não testa nada.&lt;/p&gt;

&lt;p&gt;Regra que eu tento seguir: &lt;strong&gt;se um linter poderia ter dito, um humano não deveria dizer.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Do outro lado: recebendo review
&lt;/h2&gt;

&lt;p&gt;Vale dizer, porque é mão dupla.&lt;/p&gt;

&lt;p&gt;Todo comentário é sobre o código. Mesmo o mal escrito — quem escreveu provavelmente estava com pressa entre duas reuniões, não com raiva de você. Se um comentário te irritou, espera vinte minutos antes de responder. E se foi grosseiro mesmo, dá pra falar direto: "esse comentário soou meio seco, o que você quis dizer?". Quase sempre a resposta é "ih, desculpa, escrevi correndo".&lt;/p&gt;

&lt;p&gt;E quando discordar, discorda com argumento. Review não é ordem, é conversa — quem revisa também erra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: combinem uma página de regras
&lt;/h2&gt;

&lt;p&gt;Meia hora de conversa e um documento de uma página resolvem 80% do atrito: o que é bloqueante de verdade, em quanto tempo se espera resposta num PR, tamanho máximo de PR (o maior favor que você faz pro revisor é o PR pequeno), quem pode aprovar e os prefixos de comentário.&lt;/p&gt;

&lt;p&gt;Não é burocracia. É parar de negociar as mesmas coisas em todo PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Code review é o único momento em que o time inteiro conversa sobre código de verdade. É onde padrão se espalha, onde gente nova aprende rápido e onde bug morre barato.&lt;/p&gt;

&lt;p&gt;E é também onde relação se desgasta silenciosamente, um comentário seco de cada vez, até alguém começar a aprovar tudo só pra evitar a conversa. Aí acabou o review — sobrou o carimbo.&lt;/p&gt;

&lt;p&gt;Trinta segundos a mais escrevendo o comentário é o melhor investimento de tempo que existe no processo.&lt;/p&gt;

&lt;p&gt;Agora conta: &lt;strong&gt;qual o comentário mais babaca que você já recebeu num PR?&lt;/strong&gt; Ou — se tiver coragem, e eu tenho — qual foi o mais babaca que você já &lt;em&gt;escreveu&lt;/em&gt;? Eu abro os trabalhos: já escrevi um "isso não faz sentido" às 19h de sexta e passei o fim de semana inteiro me arrependendo. 🙈&lt;/p&gt;




</description>
      <category>career</category>
      <category>cleancode</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Você testou a API funcionando. E quando ela cair?</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 04 Aug 2026 22:58:21 +0000</pubDate>
      <link>https://dev.to/denisgusto1/voce-testou-a-api-funcionando-e-quando-ela-cair-4n3i</link>
      <guid>https://dev.to/denisgusto1/voce-testou-a-api-funcionando-e-quando-ela-cair-4n3i</guid>
      <description>&lt;h2&gt;
  
  
  O caminho feliz sempre passa. O problema é o outro.
&lt;/h2&gt;

&lt;p&gt;Sua integração com o gateway funciona. Você testou. O teste tá verde. Deploy feito, tá em produção há meses.&lt;/p&gt;

&lt;p&gt;Aí, numa terça qualquer, o gateway devolve um erro 500. Instabilidade do lado deles.&lt;/p&gt;

&lt;p&gt;E o seu app? Cospe uma tela branca com &lt;code&gt;Undefined array key "transaction_id"&lt;/code&gt; na cara do cliente. Porque seu código pegou a resposta de erro e tentou ler o &lt;code&gt;transaction_id&lt;/code&gt; que não existe ali.&lt;/p&gt;

&lt;p&gt;Você testou a API &lt;strong&gt;funcionando&lt;/strong&gt;. Nunca testou ela &lt;strong&gt;falhando&lt;/strong&gt;. E é exatamente aí que mora o bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  O teste que só conhece o dia bonito
&lt;/h2&gt;

&lt;p&gt;Olha o teste típico de integração. Aposto que o seu se parece com esse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_processa_o_pagamento&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'gateway.com/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'aprovado'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'transaction_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tx_123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cobrar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resultado&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;aprovado&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bonito. Verde. E completamente ingênuo.&lt;/p&gt;

&lt;p&gt;Ele responde uma única pergunta: "quando dá tudo certo, meu código funciona?". Ótimo. Mas a produção não é só o dia bonito. A API vai devolver 500, vai dar timeout, vai responder um JSON estranho, vai ficar fora do ar. &lt;strong&gt;E você nunca escreveu uma linha testando o que acontece nesses casos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O caminho feliz é o que menos quebra. O caminho de falha é onde o cliente vê tela branca.&lt;/p&gt;

&lt;h2&gt;
  
  
  O resgate: &lt;code&gt;Http::fake()&lt;/code&gt; também simula desastre
&lt;/h2&gt;

&lt;p&gt;Aqui tá a beleza de mockar a API — você não fica refém de esperar ela cair pra ver o que acontece. &lt;strong&gt;Você manda ela cair.&lt;/strong&gt; Na hora que quiser, no teste que quiser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_gateway_fora_do_ar_nao_quebra_o_app&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// simula o gateway devolvendo erro 500&lt;/span&gt;
    &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'gateway.com/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Service Unavailable'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cobrar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// seu código degrada com elegância, em vez de explodir?&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resultado&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;aprovado&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'gateway_indisponivel'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$resultado&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;motivo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pendente'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse teste vale ouro. Ele te obriga a responder: "quando o gateway cai, meu pedido fica num estado consistente ou vira lixo?". Se o teste passa, você &lt;strong&gt;sabe&lt;/strong&gt; que o cliente vê uma mensagem decente e o pedido fica pendente pra retentar — não uma tela branca.&lt;/p&gt;

&lt;h2&gt;
  
  
  Os três desastres que você precisa testar
&lt;/h2&gt;

&lt;p&gt;Não é só o erro 500. O mundo real tem sabores variados de falha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Erro do servidor (a API respondeu, mas com pau)&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'gateway.com/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Resposta inesperada (mudaram o contrato, ou veio corrompido)&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'gateway.com/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'algo'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'diferente'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. A API nem respondeu (timeout / fora do ar)&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConnectionException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'timeout'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada um exercita um pedaço diferente do seu tratamento de erro. O 500 testa seu &lt;code&gt;throw()&lt;/code&gt;. A resposta estranha testa se você valida o que recebeu antes de usar. A &lt;code&gt;ConnectionException&lt;/code&gt; testa o que acontece quando a requisição nem chega — o cenário do post sobre &lt;code&gt;timeout&lt;/code&gt; e &lt;code&gt;retry&lt;/code&gt;, agora coberto por teste.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: caminho de falha sem tratamento não "passa", ele engana
&lt;/h2&gt;

&lt;p&gt;Cuidado com uma armadilha sutil. Se seu código &lt;strong&gt;não trata&lt;/strong&gt; o erro, esse teste de falha pode passar por engano — porque o Laravel lança a exceção e o teste morre com ela, ou o &lt;code&gt;assert&lt;/code&gt; bate em cima de um estado que você nem pensou.&lt;/p&gt;

&lt;p&gt;O teste de falha só tem valor se você escreveu o teste &lt;strong&gt;esperando um comportamento específico e digno&lt;/strong&gt;: uma flag &lt;code&gt;aprovado = false&lt;/code&gt;, um status &lt;code&gt;pendente&lt;/code&gt;, uma exceção sua tratada. Se você não sabe o que seu código deveria fazer quando a API cai... esse é o verdadeiro bug, e o teste acabou de te mostrar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;O caminho feliz é o mínimo. Ele prova que seu código funciona no melhor dos mundos — que é justamente o mundo que menos acontece.&lt;/p&gt;

&lt;p&gt;O teste que te salva de plantão às 2 da manhã é o do caminho de falha. Pega a sua integração externa mais crítica e se pergunta: "eu tenho um teste que simula essa API caindo?". Se a resposta é não, você já sabe qual teste escrever amanhã.&lt;/p&gt;

&lt;p&gt;Me conta: você testa o caminho de falha das suas integrações, ou só o dia bonito? Sem julgamento — a maioria só testa o feliz. 👀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>testing</category>
      <category>api</category>
    </item>
    <item>
      <title>Seu teste bate na API de verdade? Um dia ela vai te trair</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 04 Aug 2026 22:57:40 +0000</pubDate>
      <link>https://dev.to/denisgusto1/seu-teste-bate-na-api-de-verdade-um-dia-ela-vai-te-trair-4l97</link>
      <guid>https://dev.to/denisgusto1/seu-teste-bate-na-api-de-verdade-um-dia-ela-vai-te-trair-4l97</guid>
      <description>&lt;h2&gt;
  
  
  O teste quebrou. E não foi você.
&lt;/h2&gt;

&lt;p&gt;Segunda de manhã, você abre o projeto e o CI tá vermelho.&lt;/p&gt;

&lt;p&gt;Você não mexeu em nada desde sexta. Abre o log: o teste de integração com a API de pagamento falhou. Timeout.&lt;/p&gt;

&lt;p&gt;O gateway deles teve uma instabilidade às 3 da manhã. Seu código tá perfeito. Mesmo assim, o build tá vermelho, o deploy travado, e agora você perde meia hora provando que o problema não é seu.&lt;/p&gt;

&lt;p&gt;Já passou por isso? Teste que depende de API externa é assim: &lt;strong&gt;lento, instável e quebra por motivos que não são culpa sua&lt;/strong&gt;. E tem o cenário pior — o teste que roda de verdade contra a API de pagamento, e a cada &lt;code&gt;php artisan test&lt;/code&gt; você dispara 30 cobranças reais no sandbox até o gateway achar suspeito e bloquear.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema: seu teste não deveria conhecer a internet
&lt;/h2&gt;

&lt;p&gt;Olha esse teste. Parece razoável, né?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_busca_o_cep&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// isso bate no ViaCEP de VERDADE, toda vez que o teste roda&lt;/span&gt;
    &lt;span class="nv"&gt;$endereco&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;buscarCep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'01001000'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'São Paulo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$endereco&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cidade'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que tem de errado? Tudo que importa num teste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lento&lt;/strong&gt; — cada teste espera a ida e volta pela rede.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instável&lt;/strong&gt; — a API cair, ficar lenta ou mudar o retorno quebra seu teste sem seu código ter mudado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perigoso&lt;/strong&gt; — se for uma API que cobra, envia e-mail ou cria pedido, você tá fazendo isso pra valer a cada execução.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sem controle&lt;/strong&gt; — como você testa "o que acontece quando a API devolve erro 500"? Não dá pra pedir pra ela cair na hora certa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O teste devia verificar o &lt;strong&gt;seu&lt;/strong&gt; código. Não a saúde do servidor dos outros.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: &lt;code&gt;Http::fake()&lt;/code&gt; intercepta tudo
&lt;/h2&gt;

&lt;p&gt;Aqui vem o &lt;code&gt;$pedido-&amp;gt;restore()&lt;/code&gt; dessa história. Se você usa o HTTP Client do Laravel (&lt;code&gt;Http::get()&lt;/code&gt;, &lt;code&gt;Http::post()&lt;/code&gt;...), uma linha resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_busca_o_cep&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A partir daqui, NENHUMA requisição sai de verdade.&lt;/span&gt;
    &lt;span class="c1"&gt;// Você diz o que cada URL deve responder.&lt;/span&gt;
    &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'viacep.com.br/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'localidade'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'São Paulo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'uf'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'SP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$endereco&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;buscarCep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'01001000'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'São Paulo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$endereco&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cidade'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto. O &lt;code&gt;Http::fake()&lt;/code&gt; &lt;strong&gt;intercepta&lt;/strong&gt; a requisição e devolve a resposta que você mandou, sem tocar na internet. O teste roda offline, em milissegundos, e dá o mesmo resultado toda vez. Você virou dono do comportamento da API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como usar na prática
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;fake()&lt;/code&gt; é flexível. Alguns cenários que você vai usar toda hora:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fingir QUALQUER requisição como 200 vazio (o mais simples)&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Respostas diferentes por URL&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'github.com/*'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'plano'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pro'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'viacep.com.br/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// simula CEP não achado&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Simular a API fora do ar (erro 500) — o caminho que ninguém testa&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'gateway.com/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Erro interno'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repara no último: agora você consegue testar &lt;strong&gt;o caminho de falha&lt;/strong&gt;. "Se o gateway devolver 500, meu app mostra uma mensagem amigável ou explode na cara do usuário?" Sem &lt;code&gt;fake()&lt;/code&gt;, você nunca teve como responder isso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Não basta simular: confirme que você mandou certo
&lt;/h2&gt;

&lt;p&gt;Aqui mora a pegadinha que separa o teste que testa do teste que só finge.&lt;/p&gt;

&lt;p&gt;Simular a resposta não garante que &lt;strong&gt;você montou a requisição direito&lt;/strong&gt;. Passou o header de autenticação? Mandou o valor no campo certo? O &lt;code&gt;Http::assertSent()&lt;/code&gt; confere isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cobrar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cartao&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'tok_abc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;assertSent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'https://gateway.com/charges'&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora o teste garante os dois lados: você mandou o que devia &lt;strong&gt;e&lt;/strong&gt; reagiu certo à resposta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: proíba requisições reais escaparem
&lt;/h2&gt;

&lt;p&gt;Tem um detalhe traiçoeiro. Se você esquecer de fakear uma URL, a requisição sai de verdade — e você nem percebe que aquele teste ainda depende da internet.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;preventStrayRequests()&lt;/code&gt; fecha essa porta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventStrayRequests&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'gateway.com/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ok'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://gateway.com/charges'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ok, tá fakeado&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://outra-api.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// 💥 lança exceção na hora&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Qualquer requisição sem fake correspondente estoura na hora, em vez de vazar silenciosamente pra rede. Coloca isso no &lt;code&gt;setUp()&lt;/code&gt; da sua suíte de testes e durma tranquilo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;A regra de ouro: &lt;strong&gt;teste não conversa com o mundo lá fora&lt;/strong&gt;. Ele verifica o seu código, de forma rápida e previsível. A conversa real com a API você deixa pra um teste de integração separado, que roda de vez em quando — não a cada commit.&lt;/p&gt;

&lt;p&gt;Um detalhe importante: isso só funciona se você usa o HTTP Client do Laravel (&lt;code&gt;Http::&lt;/code&gt;). Se ainda chama Guzzle ou cURL na mão, o &lt;code&gt;fake()&lt;/code&gt; não te alcança — e migrar pro &lt;code&gt;Http::&lt;/code&gt; é justamente o próximo passo (spoiler: ele também te dá &lt;code&gt;timeout&lt;/code&gt; e &lt;code&gt;retry&lt;/code&gt; de graça, mas isso é papo pra outro post).&lt;/p&gt;

&lt;p&gt;Me conta: você mocka suas APIs nos testes ou ainda reza pro sandbox ficar de pé? 👀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>testing</category>
      <category>api</category>
    </item>
    <item>
      <title>A API de terceiro caiu e derrubou seu app junto</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 04 Aug 2026 22:56:07 +0000</pubDate>
      <link>https://dev.to/denisgusto1/a-api-de-terceiro-caiu-e-derrubou-seu-app-junto-1366</link>
      <guid>https://dev.to/denisgusto1/a-api-de-terceiro-caiu-e-derrubou-seu-app-junto-1366</guid>
      <description>&lt;h2&gt;
  
  
  A API deles caiu. Por que o SEU app caiu junto?
&lt;/h2&gt;

&lt;p&gt;O fornecedor de frete teve um problema. A API deles ficou lenta — respondendo em 40 segundos em vez de 200 milissegundos.&lt;/p&gt;

&lt;p&gt;Do lado deles, um incidente. Do seu lado? Caos.&lt;/p&gt;

&lt;p&gt;Cada requisição que seu app fez pra calcular o frete ficou pendurada esperando. Os workers da fila entupiram. As conexões do PHP-FPM esgotaram. Em poucos minutos, seu app inteiro parou de responder — não só o checkout, &lt;strong&gt;tudo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um problema que era deles virou seu problema. E o pior: era evitável com três linhas.&lt;/p&gt;

&lt;h2&gt;
  
  
  O código que confia demais no mundo lá fora
&lt;/h2&gt;

&lt;p&gt;Chamada de API é um ato de fé. Você pede algo pra um servidor que não é seu e espera que ele responda rápido, sempre, sem erro. Spoiler: ele não vai.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Confiança cega: espera pra sempre, e reza pra dar certo&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api-frete.com/cotacao'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'cep'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$cep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que tá faltando aqui?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sem limite de espera.&lt;/strong&gt; Se a API travar, sua requisição trava junto. Por padrão o HTTP Client espera até 30 segundos — uma eternidade quando isso acontece em cascata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sem tentar de novo.&lt;/strong&gt; Uma falha de rede momentânea (acontece o tempo todo) já derruba a operação inteira.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sem tratar erro.&lt;/strong&gt; Se a API devolver 500, o &lt;code&gt;$response-&amp;gt;json()&lt;/code&gt; segue como se nada fosse, e você processa lixo achando que é cotação.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A blindagem: &lt;code&gt;timeout&lt;/code&gt;, &lt;code&gt;retry&lt;/code&gt; e &lt;code&gt;throw&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O HTTP Client do Laravel já traz o resgate embutido. Você encadeia e pronto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// desiste depois de 5s, não fica pendurado&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// tenta 3x, esperando 200ms entre as tentativas&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api-frete.com/cotacao'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cep'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$cep&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;            &lt;span class="c1"&gt;// erro 4xx/5xx? lança exceção, não segue com lixo&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Três métodos, três problemas resolvidos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timeout(5)&lt;/code&gt;&lt;/strong&gt; — se em 5 segundos não respondeu, o Laravel desiste e lança uma &lt;code&gt;ConnectionException&lt;/code&gt;. Sua requisição não fica travada arrastando o app junto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;retry(3, 200)&lt;/code&gt;&lt;/strong&gt; — falha de rede? Ele tenta de novo, até 3 vezes, com 200ms de intervalo. A instabilidade passageira some sozinha.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;throw()&lt;/code&gt;&lt;/strong&gt; — se depois de tudo a resposta for erro, ele lança exceção. Você trata de propósito, em vez de processar dado inválido silenciosamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Como usar na prática
&lt;/h2&gt;

&lt;p&gt;Alguns ajustes que valem a pena conhecer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Separar timeout de conexão do timeout de resposta&lt;/span&gt;
&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connectTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Só relançar em erro de servidor, não em 404 esperado&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;throwIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;serverError&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Ou garantir que veio exatamente 200&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;throwUnlessStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;connectTimeout&lt;/code&gt; é quanto você espera pra &lt;strong&gt;conectar&lt;/strong&gt; (3s costuma bastar). &lt;code&gt;timeout&lt;/code&gt; é o total até a resposta chegar. Separar os dois te dá controle fino: "conecta rápido, mas se conectou, dou mais um tempinho pra processar".&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: retry cego pode cobrar o cartão duas vezes
&lt;/h2&gt;

&lt;p&gt;Aqui mora o perigo que quase ninguém pensa. O &lt;code&gt;retry&lt;/code&gt; é maravilhoso pra operações &lt;strong&gt;idempotentes&lt;/strong&gt; — buscar uma cotação, consultar um CEP. Repetir não faz mal.&lt;/p&gt;

&lt;p&gt;Mas e uma cobrança? Se a API processou o pagamento e a resposta se perdeu no caminho, o &lt;code&gt;retry&lt;/code&gt; vai tentar de novo — e cobrar o cliente &lt;strong&gt;duas vezes&lt;/strong&gt;. 😬&lt;/p&gt;

&lt;p&gt;Pra esses casos, o &lt;code&gt;retry&lt;/code&gt; aceita uma condição: só repete se for erro de conexão (a requisição nem chegou), nunca se o servidor já respondeu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Client\ConnectionException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Client\PendingRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;PendingRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// só tenta de novo se NEM CHEGOU no servidor&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ConnectionException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://gateway.com/charges'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$dados&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A régua: &lt;strong&gt;repetir consulta, sempre. Repetir cobrança, só se tiver certeza de que nem chegou.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: e agora?
&lt;/h2&gt;

&lt;p&gt;Trocar Guzzle na mão pelo &lt;code&gt;Http::&lt;/code&gt; do Laravel não é só sintaxe mais bonita. É &lt;code&gt;timeout&lt;/code&gt;, &lt;code&gt;retry&lt;/code&gt;, &lt;code&gt;throw&lt;/code&gt; e — de brinde — a possibilidade de mockar tudo nos testes com &lt;code&gt;Http::fake()&lt;/code&gt;, sem tocar na API real (isso rende um post inteiro à parte).&lt;/p&gt;

&lt;p&gt;Se você tem uma integração externa crítica sem &lt;code&gt;timeout&lt;/code&gt;, essa é sua lição de casa pra hoje. Uma API lenta lá fora não pode ser capaz de derrubar seu app aqui dentro.&lt;/p&gt;

&lt;p&gt;Me conta: seu app já foi derrubado por uma API de terceiro? O que travou primeiro — a fila ou o FPM? 👀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>api</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
