<?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%2F17b8b710-b578-41fa-9ade-844a8e0fe03b.jpg</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>Deixei um bot derrubar minha API antes de descobrir o throttle</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:59:34 +0000</pubDate>
      <link>https://dev.to/denisgusto1/deixei-um-bot-derrubar-minha-api-antes-de-descobrir-o-throttle-2n4j</link>
      <guid>https://dev.to/denisgusto1/deixei-um-bot-derrubar-minha-api-antes-de-descobrir-o-throttle-2n4j</guid>
      <description>&lt;h2&gt;
  
  
  Segunda de manhã, servidor no chão, e eu sem entender nada
&lt;/h2&gt;

&lt;p&gt;A cena: segunda-feira, 8h. Abro o monitoramento e o servidor está gritando. CPU no talo, banco engasgado, aplicação lenta pra todo mundo.&lt;/p&gt;

&lt;p&gt;Meu primeiro palpite? "Bombou o projeto, viralizou!" 🎉&lt;/p&gt;

&lt;p&gt;Aí olhei o log. Não era gente. Era &lt;strong&gt;um único IP&lt;/strong&gt; batendo no meu endpoint de login. Milhares de requisições por minuto, tentando senha atrás de senha. Um bot fazendo força bruta enquanto eu dormia.&lt;/p&gt;

&lt;p&gt;A pior parte: eu não tinha &lt;strong&gt;nada&lt;/strong&gt; pra segurar isso. Meu endpoint aceitava requisição de quem quisesse, quantas vezes quisesse. A porta estava escancarada.&lt;/p&gt;

&lt;p&gt;Já parou pra pensar quantas requisições um usuário consegue disparar na sua API agora, sem nenhum limite? Pois é.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema: sua API confia em todo mundo
&lt;/h2&gt;

&lt;p&gt;Por padrão, uma rota Laravel não tem limite nenhum. Ela responde a qualquer um, o quanto vier:&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AuthController&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="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// Pode receber 5 requisições por dia ou 50 mil por minuto.&lt;/span&gt;
&lt;span class="c1"&gt;// Pra ela tanto faz. 🤷&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é convite pra três dores de cabeça:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Força bruta&lt;/strong&gt; no login, tentando senha até acertar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scraping&lt;/strong&gt; dos seus dados, alguém raspando tudo de uma vez.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custo&lt;/strong&gt;: cada requisição gasta CPU, banco e, se você estiver na nuvem, dinheiro.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A solução tem nome e é embutida no Laravel: &lt;strong&gt;rate limiting&lt;/strong&gt;. Ou seja, "você só pode fazer X requisições por minuto. Passou disso, toma um 429 e espera".&lt;/p&gt;

&lt;h2&gt;
  
  
  A versão de 3 linhas: middleware &lt;code&gt;throttle&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O jeito mais rápido de proteger uma rota é o middleware &lt;code&gt;throttle&lt;/code&gt;. Você diz quantas requisições por quantos minutos:&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;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'throttle:60,1'&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;group&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;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;apiResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PostController&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="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 60 requisições por 1 minuto, por usuário/IP. Passou disso, 429.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto. Se um IP passar de 60 requisições no minuto, o Laravel responde com &lt;code&gt;429 Too Many Requests&lt;/code&gt; sozinho, sem você escrever uma linha de lógica. O bot que estava me atormentando teria batido na parede.&lt;/p&gt;

&lt;p&gt;E se você tem uma API, provavelmente já tem um limiter chamado &lt;code&gt;api&lt;/code&gt; funcionando sem você saber, aplicado no grupo de rotas de &lt;code&gt;routes/api.php&lt;/code&gt;. Vale conferir.&lt;/p&gt;

&lt;h2&gt;
  
  
  A versão profissional: named limiters
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;throttle:60,1&lt;/code&gt; resolve o básico. Mas e quando você quer &lt;strong&gt;regras diferentes&lt;/strong&gt; por tipo de rota, ou por tipo de usuário? Aí entram os &lt;strong&gt;named limiters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Você define a regra num lugar central (no &lt;code&gt;boot()&lt;/code&gt; do seu &lt;code&gt;AppServiceProvider&lt;/code&gt;) e dá um nome pra ela:&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\Cache\RateLimiting\Limit&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\Request&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\Support\Facades\RateLimiter&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;boot&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;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api'&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="c1"&gt;// 60 por minuto, contados por usuário logado — ou por IP se for visitante&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&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;by&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="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;ip&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 &lt;code&gt;-&amp;gt;by()&lt;/code&gt;: é ele que decide &lt;strong&gt;quem&lt;/strong&gt; conta pra cota. Usando o ID do usuário, cada pessoa tem sua própria cota; caindo pro IP quando é visitante. Sem isso, todo mundo dividiria o mesmo balde — um abusador estouraria o limite de todos os outros.&lt;/p&gt;

&lt;p&gt;Depois é só apontar a rota pro limiter pelo nome:&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;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'throttle:api'&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;group&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="c1"&gt;// suas rotas protegidas&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Protegendo o login (o meu caso).&lt;/strong&gt; Aqui o pulo do gato é contar por e-mail, não só por IP, pra travar força bruta mesmo que o bot troque de IP:&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;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'login'&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="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&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;// teto geral da rota&lt;/span&gt;
        &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;by&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;'email'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;// 5 tentativas por e-mail&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 tentativas erradas naquele e-mail e a conta fica trancada por um minuto. Bot de força bruta chora.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limite diferente por plano.&lt;/strong&gt; Cliente VIP não deveria ter a mesma cota do plano grátis. Dá pra decidir na 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="nc"&gt;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'uploads'&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;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;vip&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;// VIP sem limite&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perHour&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="c1"&gt;// grátis, 10 por hora&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;Mensagem de erro amigável.&lt;/strong&gt; Em vez do 429 seco, você pode devolver uma resposta customizada:&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;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api'&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="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&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;response&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="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$headers&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;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;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Calma lá! Tenta de novo em instantes.'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$headers&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;h2&gt;
  
  
  Pegadinha: os headers que ninguém lê (mas deveria)
&lt;/h2&gt;

&lt;p&gt;Toda resposta com rate limit vem com headers que contam a história: &lt;code&gt;X-RateLimit-Limit&lt;/code&gt; (seu teto) e &lt;code&gt;X-RateLimit-Remaining&lt;/code&gt; (quanto ainda sobra). Quando estoura, vem também o &lt;code&gt;Retry-After&lt;/code&gt;, dizendo em quantos segundos pode tentar de novo.&lt;/p&gt;

&lt;p&gt;Se você consome uma API de terceiros e vive tomando 429, é aí que você olha. E se você &lt;strong&gt;expõe&lt;/strong&gt; uma API, esses headers são o que permite o cliente se comportar bem sozinho, sem ficar batendo na parede.&lt;/p&gt;

&lt;p&gt;Outra pegadinha: rate limiting usa o &lt;strong&gt;cache&lt;/strong&gt; pra contar. Em produção, garante que seu cache está num driver de verdade (Redis, de preferência), não no &lt;code&gt;array&lt;/code&gt;, senão a contagem se perde a cada requisição.&lt;/p&gt;

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

&lt;p&gt;Rate limiting é a porta de entrada de uma API saudável, mas não para por aí:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combine com &lt;strong&gt;throttle no login&lt;/strong&gt; e você já matou 90% dos ataques de força bruta.&lt;/li&gt;
&lt;li&gt;Use limites diferentes pra rotas &lt;strong&gt;públicas&lt;/strong&gt; (mais apertado) e &lt;strong&gt;autenticadas&lt;/strong&gt; (mais folgado).&lt;/li&gt;
&lt;li&gt;Se sua fila (queue) chama APIs externas, aplique rate limiting nos &lt;strong&gt;jobs&lt;/strong&gt; também — o Laravel tem &lt;code&gt;Redis::throttle()&lt;/code&gt; pra isso.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Não precisa fazer tudo de uma vez. Começa protegendo o login e as rotas mais sensíveis. O resto vem depois.&lt;/p&gt;

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

&lt;p&gt;Eu aprendi na marra: API sem rate limiting é porta destrancada. Funciona lindamente até o dia que um bot resolve entrar.&lt;/p&gt;

&lt;p&gt;O melhor de tudo? A proteção básica é literalmente &lt;strong&gt;uma linha de middleware&lt;/strong&gt;. Não tem desculpa pra deixar a porta aberta.&lt;/p&gt;

&lt;p&gt;E aí, suas rotas de login e API já têm rate limiting? Ou você também só vai descobrir isso quando o servidor cair numa segunda de manhã? Conta nos comentários. 🔒&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curtiu? Tem mais conteúdo sobre Laravel limpo e seguro por aqui. Me segue pra não perder os próximos.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>api</category>
      <category>security</category>
    </item>
    <item>
      <title>Funciona na minha máquina: o mistério do env() que some em produção</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 13 Jul 2026 17:00:41 +0000</pubDate>
      <link>https://dev.to/denisgusto1/funciona-na-minha-maquina-o-misterio-do-env-que-some-em-producao-36n7</link>
      <guid>https://dev.to/denisgusto1/funciona-na-minha-maquina-o-misterio-do-env-que-some-em-producao-36n7</guid>
      <description>&lt;h2&gt;
  
  
  "Mas aqui na minha máquina roda perfeitamente"
&lt;/h2&gt;

&lt;p&gt;Você terminou a feature. Testou local, passou. Subiu pra produção.&lt;/p&gt;

&lt;p&gt;E aí, do nada, algo que dependia de uma variável de ambiente simplesmente… parou de funcionar. Sua integração com o gateway de pagamento agora usa uma chave vazia. O client de uma API externa aponta pra lugar nenhum.&lt;/p&gt;

&lt;p&gt;Você abre o log, confere o &lt;code&gt;.env&lt;/code&gt;, a variável tá lá, certinha. Roda local de novo: funciona. Em produção: &lt;code&gt;null&lt;/code&gt;. 🙃&lt;/p&gt;

&lt;p&gt;Se você já viveu essa cena de terror, provavelmente o culpado é um só: você usou &lt;code&gt;env()&lt;/code&gt; num lugar que não devia.&lt;/p&gt;

&lt;h2&gt;
  
  
  O código que parece inofensivo
&lt;/h2&gt;

&lt;p&gt;Digamos que você criou um service pra falar com uma API de frete. Precisa da chave, então você faz o óbvio:&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;FreteService&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;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// parece certo, né? peguei do .env e pronto&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;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRETE_API_KEY'&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;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRETE_API_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://api.frete.com'&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;Local, isso funciona lindamente. O Laravel lê o &lt;code&gt;.env&lt;/code&gt;, encontra &lt;code&gt;FRETE_API_KEY&lt;/code&gt;, e a vida segue.&lt;/p&gt;

&lt;p&gt;O problema é que esse código carrega uma bomba-relógio. E o pino é puxado por um comando que &lt;strong&gt;todo deploy sério roda&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que o config:cache faz (e por que ele te trai)
&lt;/h2&gt;

&lt;p&gt;Ler o arquivo &lt;code&gt;.env&lt;/code&gt; do disco e parsear tudo a cada request é lento. Por isso, em produção, a recomendação oficial é rodar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando junta todos os seus arquivos de &lt;code&gt;config/&lt;/code&gt; num único arquivo PHP otimizado e cacheado. A partir daí, o Laravel nunca mais lê o &lt;code&gt;.env&lt;/code&gt; — ele lê o cache.&lt;/p&gt;

&lt;p&gt;E aqui tá a pegadinha: &lt;strong&gt;quando o config está cacheado, a função &lt;code&gt;env()&lt;/code&gt; passa a retornar &lt;code&gt;null&lt;/code&gt; pra tudo que não estava dentro dos arquivos de config.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ou seja, aquele &lt;code&gt;env('FRETE_API_KEY')&lt;/code&gt; lá no seu service vira &lt;code&gt;null&lt;/code&gt; em produção. Local você provavelmente nunca rodou &lt;code&gt;config:cache&lt;/code&gt;, por isso "funciona na sua máquina". O bug estava dormindo o tempo todo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A regra de ouro
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;env()&lt;/code&gt; só deve ser chamado &lt;strong&gt;dentro dos arquivos da pasta &lt;code&gt;config/&lt;/code&gt;&lt;/strong&gt;. Nunca em services, controllers, models, jobs ou qualquer outro lugar.&lt;/p&gt;

&lt;p&gt;O jeito certo é: você lê o &lt;code&gt;env()&lt;/code&gt; no config, e no resto da aplicação você lê o config com &lt;code&gt;config()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Primeiro, cria (ou usa) um arquivo de config:&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;// config/frete.php&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;'api_key'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRETE_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'base_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRETE_API_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://api.frete.com'&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;Depois, no service, você troca &lt;code&gt;env()&lt;/code&gt; por &lt;code&gt;config()&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FreteService&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;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// agora lê do config — que sobrevive ao cache&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;apiKey&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'frete.api_key'&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;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'frete.base_url'&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;Pronto. Agora o &lt;code&gt;env()&lt;/code&gt; mora onde deve, o &lt;code&gt;config:cache&lt;/code&gt; empacota o valor junto, e produção passa a se comportar igual ao seu local.&lt;/p&gt;

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

&lt;p&gt;O padrão é sempre o mesmo, em qualquer contexto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chave de API num service&lt;/strong&gt; → cria uma entrada em &lt;code&gt;config/services.php&lt;/code&gt; e lê com &lt;code&gt;config('services.frete.key')&lt;/code&gt;. Aliás, o &lt;code&gt;config/services.php&lt;/code&gt; já existe justamente pra isso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature flag num controller&lt;/strong&gt; → &lt;code&gt;config('app.features.novo_checkout')&lt;/code&gt; em vez de &lt;code&gt;env('NOVO_CHECKOUT')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout de um job&lt;/strong&gt; → coloca em &lt;code&gt;config/queue.php&lt;/code&gt; ou num config seu e chama &lt;code&gt;config('...')&lt;/code&gt; lá dentro.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regra mental simples: se você tá prestes a escrever &lt;code&gt;env()&lt;/code&gt; e o arquivo &lt;strong&gt;não&lt;/strong&gt; está dentro de &lt;code&gt;config/&lt;/code&gt;, para. Tá quase virando bug de produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha da pegadinha
&lt;/h2&gt;

&lt;p&gt;"Mas eu uso &lt;code&gt;env()&lt;/code&gt; fora do config e funciona!" — sim, funciona &lt;strong&gt;enquanto você não rodar &lt;code&gt;config:cache&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O perigo é justamente esse: em desenvolvimento tudo passa, os testes passam, o PR é aprovado. O bug só nasce no primeiro deploy que roda o cache. E aí você vai debugar em produção, no pior momento possível.&lt;/p&gt;

&lt;p&gt;Quer testar se seu código tá limpo agora? Roda isso local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se algo quebrar depois disso, achou seus &lt;code&gt;env()&lt;/code&gt; mal colocados. Pra voltar ao normal no dev:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Passa um &lt;code&gt;grep&lt;/code&gt; no seu projeto atrás de &lt;code&gt;env(&lt;/code&gt; fora da pasta &lt;code&gt;config/&lt;/code&gt;. Aposto que tem pelo menos um escondido por aí — geralmente num service antigo que "sempre funcionou".&lt;/p&gt;

&lt;p&gt;Enquanto tá nisso, dá uma olhada no &lt;code&gt;config/services.php&lt;/code&gt;. Ele foi feito pra guardar credenciais de serviços de terceiros, e muita gente ignora que ele existe.&lt;/p&gt;

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

&lt;p&gt;O &lt;code&gt;env()&lt;/code&gt; fora do config é aquele bug educado: ele espera você virar as costas pra produção pra dar as caras.&lt;/p&gt;

&lt;p&gt;Já perdeu uma tarde caçando um valor que sumia em produção? Conta aí nos comentários qual foi o seu — quero saber se fui só eu. 👀&lt;/p&gt;

&lt;p&gt;Se esse tipo de armadilha de Laravel te ajuda, salva o post e me segue: tem mais dessas por aqui.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>API Resources no Laravel: pare de dar return no seu Model cru</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:10:59 +0000</pubDate>
      <link>https://dev.to/denisgusto1/api-resources-no-laravel-pare-de-dar-return-no-seu-model-cru-318i</link>
      <guid>https://dev.to/denisgusto1/api-resources-no-laravel-pare-de-dar-return-no-seu-model-cru-318i</guid>
      <description>&lt;h2&gt;
  
  
  Sua API tá contando segredo pro mundo inteiro
&lt;/h2&gt;

&lt;p&gt;Você monta o endpoint, testa no Insomnia, o JSON volta bonitinho e você segue a vida. Deploy feito, tá funcionando.&lt;/p&gt;

&lt;p&gt;Só que naquele JSON, junto com o &lt;code&gt;name&lt;/code&gt; e o &lt;code&gt;email&lt;/code&gt; que você queria mostrar, foi também o &lt;code&gt;password&lt;/code&gt; (mesmo que hasheado), o &lt;code&gt;remember_token&lt;/code&gt;, o &lt;code&gt;stripe_id&lt;/code&gt; do cliente e aquele campo &lt;code&gt;internal_notes&lt;/code&gt; que só o time devia ver.&lt;/p&gt;

&lt;p&gt;Ninguém reparou. Até alguém reparar. 😬&lt;/p&gt;

&lt;p&gt;Esse é o preço de retornar o Model direto. E a solução no Laravel tem nome: &lt;strong&gt;API Resources&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O código que todo mundo já escreveu
&lt;/h2&gt;

&lt;p&gt;Olha esse controller. Aposto que você já fez algo parecido:&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;show&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="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="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ou a versão "prática":&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;index&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;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&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. Retorna o usuário. Mas quando você faz &lt;code&gt;return $user&lt;/code&gt;, o Laravel chama &lt;code&gt;toArray()&lt;/code&gt; no Model e &lt;strong&gt;despeja todas as colunas&lt;/strong&gt; que vieram do banco no JSON.&lt;/p&gt;

&lt;p&gt;Toda coluna. A que você quer e a que você esqueceu que existia. Se amanhã alguém adicionar um campo &lt;code&gt;documento&lt;/code&gt; ou &lt;code&gt;salario&lt;/code&gt; na tabela, ele vai vazar de graça na sua API sem ninguém pedir.&lt;/p&gt;

&lt;p&gt;Você não tem controle nenhum sobre o formato da resposta. E API é contrato — sair jogando o banco cru é o oposto de contrato.&lt;/p&gt;

&lt;h2&gt;
  
  
  O &lt;code&gt;$hidden&lt;/code&gt; ajuda, mas não resolve
&lt;/h2&gt;

&lt;p&gt;"Ah, mas eu uso &lt;code&gt;$hidden&lt;/code&gt; no Model." Ótimo, é melhor que nada:&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;protected&lt;/span&gt; &lt;span class="nv"&gt;$hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'remember_token'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema é que &lt;code&gt;$hidden&lt;/code&gt; é uma blacklist. Você precisa &lt;strong&gt;lembrar&lt;/strong&gt; de esconder cada campo sensível novo. Esqueceu um? Vazou.&lt;/p&gt;

&lt;p&gt;E tem outro detalhe: o mesmo Model serve pra vários lugares. Às vezes você quer o &lt;code&gt;email&lt;/code&gt; na resposta, às vezes não. O &lt;code&gt;$hidden&lt;/code&gt; é global demais pra essa decisão, que na real é &lt;strong&gt;por endpoint&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: uma camada só pra montar a resposta
&lt;/h2&gt;

&lt;p&gt;API Resource é uma classe que fica entre o seu Model e o JSON. Ela decide, campo a campo, o que vira resposta. É uma &lt;strong&gt;whitelist&lt;/strong&gt;: só sai o que você escreveu ali.&lt;/p&gt;

&lt;p&gt;Cria com um comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:resource UserResource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E define o formato no &lt;code&gt;toArray()&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Resources&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\Request&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\Resources\Json\JsonResource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JsonResource&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;toArray&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;array&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;'id'&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="n"&gt;id&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&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;$this&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="s1"&gt;'criado_em'&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="n"&gt;created_at&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d/m/Y'&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;Repara no que ganhou de brinde: se não tá na lista, não sai. &lt;code&gt;password&lt;/code&gt; nunca vai vazar porque você simplesmente não escreveu ele ali. É seguro &lt;strong&gt;por padrão&lt;/strong&gt;, não por você lembrar de esconder.&lt;/p&gt;

&lt;p&gt;E de bônus você controla o formato: renomeou &lt;code&gt;name&lt;/code&gt; pra &lt;code&gt;nome&lt;/code&gt;, formatou a data. A resposta é sua, não um reflexo direto da tabela.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como usar no controller
&lt;/h2&gt;

&lt;p&gt;Aí no controller você embrulha o Model no Resource:&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;show&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="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;UserResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&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;Pra uma lista, usa o &lt;code&gt;collection()&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&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;UserResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&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;all&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;Mesmo Resource, seja um registro ou mil. O Laravel cuida de aplicar a transformação em cada item da coleção.&lt;/p&gt;

&lt;p&gt;Por padrão a resposta vem embrulhada numa chave &lt;code&gt;data&lt;/code&gt; — o que é ótimo, porque te dá espaço pra meta dados (paginação, links) sem quebrar o contrato lá na frente.&lt;/p&gt;

&lt;h2&gt;
  
  
  O pulo do gato: campos condicionais
&lt;/h2&gt;

&lt;p&gt;Aqui é onde Resource passa longe do &lt;code&gt;$hidden&lt;/code&gt;. Às vezes um campo deve aparecer &lt;strong&gt;só pra certas pessoas&lt;/strong&gt;. Tipo um dado que o admin vê e o usuário comum nã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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&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;array&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;'id'&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="n"&gt;id&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&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;$this&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="c1"&gt;// só entra no JSON se quem pediu for admin&lt;/span&gt;
        &lt;span class="s1"&gt;'notas_internas'&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;when&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;isAdmin&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;internal_notes&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;O &lt;code&gt;when()&lt;/code&gt; só inclui a chave se a condição for verdadeira. Pro usuário comum, o campo &lt;strong&gt;nem existe&lt;/strong&gt; na resposta — não vem como &lt;code&gt;null&lt;/code&gt;, não vem vazio, simplesmente não vem. Impossível vazar.&lt;/p&gt;

&lt;p&gt;E pra relacionamentos, tem o &lt;code&gt;whenLoaded()&lt;/code&gt;, que ainda te salva de N+1:&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;'posts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PostResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;collection&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;whenLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O relacionamento só entra no JSON se você já tiver carregado ele com &lt;code&gt;with('posts')&lt;/code&gt;. Não carregou? Não aparece — e o Laravel não dispara query extra tentando buscar. Segurança e performance no mesmo método.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha que confunde muita gente
&lt;/h2&gt;

&lt;p&gt;Dentro do Resource, você acessa os campos com &lt;code&gt;$this-&amp;gt;id&lt;/code&gt;, &lt;code&gt;$this-&amp;gt;name&lt;/code&gt;... e parece mágica, porque &lt;code&gt;$this&lt;/code&gt; não é o Model, é o Resource.&lt;/p&gt;

&lt;p&gt;O que rola é que o Resource &lt;strong&gt;encaminha&lt;/strong&gt; o acesso pro Model que ele está embrulhando. Então &lt;code&gt;$this-&amp;gt;name&lt;/code&gt; na prática é &lt;code&gt;$user-&amp;gt;name&lt;/code&gt;. Funciona lindo.&lt;/p&gt;

&lt;p&gt;O tropeço é achar que dá pra chamar qualquer método do Model direto no &lt;code&gt;$this&lt;/code&gt; sem ele estar carregado, ou esquecer que numa coleção o &lt;code&gt;$this&lt;/code&gt; representa &lt;strong&gt;cada item&lt;/strong&gt;, um de cada vez — não a coleção inteira. Se você tentar tratar a coleção como um objeto só ali dentro, vai se enrolar.&lt;/p&gt;

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

&lt;p&gt;Se você tem Service Layer, o controller fica magrinho: o service resolve a regra, o Resource formata a saída. Cada um no seu quadrado.&lt;/p&gt;

&lt;p&gt;Vale também dar uma olhada em &lt;code&gt;make:resource --collection&lt;/code&gt; (pra quando a coleção precisa de metadados próprios) e no método &lt;code&gt;additional()&lt;/code&gt;, pra pendurar dados extras na resposta sem sujar o &lt;code&gt;toArray()&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Faz um teste rápido agora: pega o endpoint mais antigo do seu projeto, aquele que retorna o Model direto, e olha o JSON completo que ele devolve. Tem algum campo ali que não devia estar aparecendo?&lt;/p&gt;

&lt;p&gt;Se tem, você acabou de achar seu próximo refactor. E se não tem, provavelmente é porque você teve sorte — não porque teve controle. 🙂&lt;/p&gt;

&lt;p&gt;Você usa Resource em tudo ou só quando a resposta é complexa? Conta aí nos comentários como você organiza isso.&lt;/p&gt;

&lt;p&gt;E se esse post te fez lembrar de um endpoint suspeito, compartilha com aquele colega que ainda dá &lt;code&gt;return $model&lt;/code&gt; sem pensar duas vezes.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>DB::transaction(): o try/catch que salva seu banco de dados</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 07 Jul 2026 13:07:26 +0000</pubDate>
      <link>https://dev.to/denisgusto1/dbtransaction-o-trycatch-que-salva-seu-banco-de-dados-57pl</link>
      <guid>https://dev.to/denisgusto1/dbtransaction-o-trycatch-que-salva-seu-banco-de-dados-57pl</guid>
      <description>&lt;h2&gt;
  
  
  Salvou o pedido, mas não salvou os itens. E agora?
&lt;/h2&gt;

&lt;p&gt;Imagina a cena: seu checkout cria o pedido, dá baixa no estoque e registra o pagamento. Três operações no banco, uma atrás da outra.&lt;/p&gt;

&lt;p&gt;Aí no meio do caminho o banco cai. Ou uma validação estoura. Ou a API de pagamento dá timeout.&lt;/p&gt;

&lt;p&gt;Resultado? O pedido foi criado, o estoque foi debitado, mas o pagamento nunca registrou. Você acabou de vender um produto de graça. 🎉&lt;/p&gt;

&lt;p&gt;Esse é o pesadelo do dado inconsistente. E a solução tem nome: &lt;strong&gt;transaction&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O código que parece inofensivo (mas não é)
&lt;/h2&gt;

&lt;p&gt;Olha esse controller bem comum:&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;store&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="nv"&gt;$pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pedido&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;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cliente_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'total'&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;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;itens&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;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;itens&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="nv"&gt;$item&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;find&lt;/span&gt;&lt;span class="p"&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;'produto_id'&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;decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'estoque'&lt;/span&gt;&lt;span class="p"&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;'qtd'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&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;$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;'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="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="nv"&gt;$pedido&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;Parece limpo, né? O problema é que cada linha grava no banco na hora.&lt;/p&gt;

&lt;p&gt;Se der ruim na criação do pagamento, o pedido e os itens &lt;strong&gt;já estão salvos&lt;/strong&gt;. Não tem volta. Seu banco fica com um Frankenstein: metade de uma operação que era pra ser uma coisa só.&lt;/p&gt;

&lt;h2&gt;
  
  
  O conceito por trás: atomicidade
&lt;/h2&gt;

&lt;p&gt;Uma transaction resolve isso com uma ideia simples: &lt;strong&gt;tudo ou nada&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ou todas as operações dão certo e são gravadas de vez (o &lt;em&gt;commit&lt;/em&gt;), ou qualquer uma falha e o banco desfaz tudo como se nada tivesse acontecido (o &lt;em&gt;rollback&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;É o mesmo princípio de uma transferência bancária: não existe "saiu da minha conta mas não entrou na sua". Ou acontece por inteiro, ou não acontece.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: DB::transaction()
&lt;/h2&gt;

&lt;p&gt;O Laravel deixa isso ridiculamente fácil. Você embrulha o código num &lt;code&gt;DB::transaction()&lt;/code&gt; 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="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&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;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="nv"&gt;$pedido&lt;/span&gt; &lt;span class="o"&gt;=&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;$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;$pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pedido&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;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cliente_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'total'&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;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;itens&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;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;itens&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="nv"&gt;$item&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;find&lt;/span&gt;&lt;span class="p"&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;'produto_id'&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;decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'estoque'&lt;/span&gt;&lt;span class="p"&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;'qtd'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&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;$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;'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="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&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="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="nv"&gt;$pedido&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;Só isso. Se qualquer linha dentro da closure lançar uma exception, o Laravel faz o rollback automático de tudo. Nada é gravado.&lt;/p&gt;

&lt;p&gt;Se rodar até o fim sem erro, ele dá o commit. E de bônus: o que a closure retornar (&lt;code&gt;return $pedido&lt;/code&gt;) vira o retorno do &lt;code&gt;DB::transaction()&lt;/code&gt;. Prático demais.&lt;/p&gt;

&lt;p&gt;Repara que não precisa de &lt;code&gt;try/catch&lt;/code&gt;, nem de &lt;code&gt;commit&lt;/code&gt;, nem de &lt;code&gt;rollBack&lt;/code&gt; na mão. A closure faz o trabalho sujo pra você.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;No checkout que a gente viu:&lt;/strong&gt; garante que pedido, itens, estoque e pagamento vivem e morrem juntos. Nunca mais um pedido órfão.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Num cadastro com múltiplas tabelas:&lt;/strong&gt; criar o usuário, o perfil e as configurações padrão. Se o perfil falhar, o usuário não fica solto no banco sem perfil.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numa importação em lote:&lt;/strong&gt; processar 500 linhas de uma planilha. Se a linha 480 tiver um erro, você não quer 479 registros pela metade. Ou entra tudo, ou você corrige a planilha e roda de novo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A versão manual (pra quando você precisa de controle)
&lt;/h2&gt;

&lt;p&gt;Às vezes a closure não dá conta — você precisa decidir &lt;em&gt;quando&lt;/em&gt; fazer commit ou rollback com base em alguma lógica. Aí usa a forma manual:&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;beginTransaction&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;$pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pedido&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;$dados&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ... mais operações ...&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;commit&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="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;rollBack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// loga o erro pra você investigar depois&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// relança pra não engolir o problema em silêncio&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Funciona igual, só que você está no controle. Mas repara: se você usar essa versão, &lt;strong&gt;não engula a exception&lt;/strong&gt;. Fazer o rollback e seguir a vida como se nada tivesse acontecido esconde o bug. Loga e relança.&lt;/p&gt;

&lt;p&gt;Na dúvida, prefira o &lt;code&gt;DB::transaction()&lt;/code&gt;. Menos código, menos chance de esquecer um &lt;code&gt;rollBack&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha que pega todo mundo
&lt;/h2&gt;

&lt;p&gt;Transaction serve pra &lt;strong&gt;banco de dados&lt;/strong&gt;. Só isso.&lt;/p&gt;

&lt;p&gt;O erro clássico é enfiar coisas que &lt;em&gt;não são&lt;/em&gt; banco dentro dela, achando que vão ser "desfeitas" também:&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="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;$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;Pedido&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;$pedido&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;$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;PedidoConfirmado&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ perigo&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;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nota.pdf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$conteudo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// ⚠️ perigo&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se a transaction der rollback &lt;em&gt;depois&lt;/em&gt; disso, o pedido some do banco — mas o e-mail &lt;strong&gt;já foi enviado&lt;/strong&gt; e o arquivo &lt;strong&gt;já foi salvo&lt;/strong&gt;. Rollback não volta no tempo pro mundo real.&lt;/p&gt;

&lt;p&gt;A regra de ouro: dentro da transaction, só banco. E-mail, envio de arquivo, chamada de API, disparo de job — deixa pra depois do commit.&lt;/p&gt;

&lt;p&gt;O Laravel até te ajuda nisso com &lt;code&gt;DB::afterCommit()&lt;/code&gt; (e jobs que implementam &lt;code&gt;ShouldQueue&lt;/code&gt; já respeitam isso automaticamente se você configurar &lt;code&gt;after_commit&lt;/code&gt;). Assim o e-mail só dispara quando o commit deu certo de verdade.&lt;/p&gt;

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

&lt;p&gt;Se você trabalha com Service Layer, a transaction geralmente mora lá dentro do método do service — não no controller. O controller só orquestra, o service garante a consistência.&lt;/p&gt;

&lt;p&gt;E vale abrir a doc pra dar uma olhada em &lt;code&gt;retry&lt;/code&gt; (o segundo parâmetro do &lt;code&gt;DB::transaction()&lt;/code&gt;, útil pra deadlocks) e em transactions aninhadas com savepoints. Assunto pra um próximo post. 😉&lt;/p&gt;

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

&lt;p&gt;Se você tem algum fluxo que grava em várias tabelas seguidas e ainda não usa transaction, esse é o sinal pra revisar. É o tipo de bug que não aparece nos testes felizes e só dá as caras em produção, numa sexta à noite.&lt;/p&gt;

&lt;p&gt;Você já tomou um susto com dado pela metade no banco? Conta aí nos comentários — aposto que tem história boa.&lt;/p&gt;

&lt;p&gt;E se esse post te salvou de um checkout quebrado, compartilha com aquele colega que ainda faz três &lt;code&gt;create()&lt;/code&gt; seguidos sem rede de proteção. 🙂&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>database</category>
    </item>
    <item>
      <title>Soft Deletes no Laravel: apague sem apagar de verdade</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Mon, 06 Jul 2026 21:50:00 +0000</pubDate>
      <link>https://dev.to/denisgusto1/soft-deletes-no-laravel-apague-sem-apagar-de-verdade-3po</link>
      <guid>https://dev.to/denisgusto1/soft-deletes-no-laravel-apague-sem-apagar-de-verdade-3po</guid>
      <description>&lt;h2&gt;
  
  
  "Some do banco" nem sempre é o que você quer
&lt;/h2&gt;

&lt;p&gt;Imagina a cena: o cliente liga desesperado porque um funcionário apagou um pedido importante. Você abre o banco, e... não tem. Sumiu. &lt;code&gt;DELETE&lt;/code&gt; é &lt;code&gt;DELETE&lt;/code&gt;. Agora é backup ou choro.&lt;/p&gt;

&lt;p&gt;Agora imagina a mesma cena, só que você digita &lt;code&gt;$pedido-&amp;gt;restore()&lt;/code&gt; e o registro volta como se nada tivesse acontecido. Herói do dia.&lt;/p&gt;

&lt;p&gt;Essa é a proposta do &lt;strong&gt;Soft Delete&lt;/strong&gt;: em vez de apagar a linha de verdade, o Laravel marca ela como apagada e finge que ela não existe mais nas consultas. Ela continua lá, só invisível. E dá pra trazer de volta quando quiser.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema: &lt;code&gt;delete()&lt;/code&gt; é definitivo
&lt;/h2&gt;

&lt;p&gt;Sem soft delete, isso aqui é irreversível:&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;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// tchau. pra sempre. 💀&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some do banco, some dos relatórios, some da vida. Se alguém apagou por engano, ou se você precisa de histórico pra auditoria, azar. E é impressionante como "apagou por engano" acontece com frequência em produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: três passos e pronto
&lt;/h2&gt;

&lt;p&gt;Primeiro, adiciona a coluna &lt;code&gt;deleted_at&lt;/code&gt; na migration. O Laravel tem um helper só pra 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;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pedidos'&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;softDeletes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// cria a coluna deleted_at (nullable)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois, usa a trait &lt;code&gt;SoftDeletes&lt;/code&gt; na model:&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\Eloquent\SoftDeletes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pedido&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&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;SoftDeletes&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. Agora o &lt;code&gt;delete()&lt;/code&gt; mudou de comportamento sem você tocar em mais nada:&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;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// não apaga a linha — só preenche deleted_at com a data/hora de agora&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o melhor: as consultas normais &lt;strong&gt;ignoram automaticamente&lt;/strong&gt; os registros apagados. &lt;code&gt;Pedido::all()&lt;/code&gt; não traz nada que foi soft-deletado. Você não precisa filtrar nada na mão.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trazendo de volta e vendo os apagados
&lt;/h2&gt;

&lt;p&gt;Restaurar é uma linha:&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;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// deleted_at volta pra null, registro "revive"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pra incluir os apagados numa consulta, &lt;code&gt;withTrashed&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="nc"&gt;Pedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withTrashed&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="c1"&gt;// traz tudo, apagados junto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pra ver &lt;strong&gt;só&lt;/strong&gt; os apagados (útil pra uma tela de "lixeira"):&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;Pedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;onlyTrashed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E se precisar apagar de verdade, aí sim, sem volta:&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;$pedido&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;forceDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// agora foi pro além de verdade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Lixeira pro usuário.&lt;/strong&gt; Uma tela onde ele apaga itens, mas tem 30 dias pra restaurar. &lt;code&gt;onlyTrashed()&lt;/code&gt; lista, &lt;code&gt;restore()&lt;/code&gt; recupera.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auditoria e histórico.&lt;/strong&gt; Setores como financeiro e jurídico costumam exigir que nada suma de verdade. Soft delete mantém o rastro sem poluir as telas do dia a dia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checar o estado no código.&lt;/strong&gt; Dá pra saber se um registro tá apagado:&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;if&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;trashed&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// esse aqui tá na lixeira&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pegadinha 1: &lt;code&gt;unique&lt;/code&gt; não sabe de soft delete
&lt;/h2&gt;

&lt;p&gt;Essa pega muita gente. Você tem uma coluna &lt;code&gt;email&lt;/code&gt; com índice &lt;code&gt;unique&lt;/code&gt;. O usuário &lt;code&gt;joao@email.com&lt;/code&gt; é soft-deletado. Ele tenta se cadastrar de novo... e o banco recusa, porque a linha antiga &lt;strong&gt;ainda está lá&lt;/strong&gt;, ocupando o email.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;unique&lt;/code&gt; do banco não liga pro &lt;code&gt;deleted_at&lt;/code&gt; — pra ele, a linha existe. Na validação, você precisa dizer isso explicitamente. Na Rule &lt;code&gt;unique&lt;/code&gt;, ignore os apagados:&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\Validation\Rule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="o"&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;'usuarios'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&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;whereNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deleted_at'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim a validação só considera os registros ativos, e o email "liberado" pelo soft delete volta a funcionar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pegadinha 2: relacionamento com registro apagado
&lt;/h2&gt;

&lt;p&gt;Outra que confunde. O pedido foi soft-deletado, mas você carrega ele por um relacionamento e o item some:&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;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// não traz os pedidos apagados&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso normalmente é o que você quer. Mas quando &lt;strong&gt;não&lt;/strong&gt; for — tipo montar um histórico completo do cliente — o relacionamento também aceita &lt;code&gt;withTrashed&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pedido&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withTrashed&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;Ou pontualmente, sem mexer na model:&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;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pedidos&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;withTrashed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Olha as tabelas do seu projeto e pergunta pra cada uma: &lt;em&gt;"se alguém apagar isso por engano, dói?"&lt;/em&gt; Pedido, fatura, contrato, usuário — provavelmente sim. Categoria de teste que você cria e joga fora — provavelmente não. Soft delete é ótimo, mas não precisa estar em tudo; coluna a mais e query um pouco mais complexa têm um custo.&lt;/p&gt;

&lt;p&gt;E cuidado com a ideia de "guardar tudo pra sempre". Dependendo do dado (LGPD, por exemplo), você pode ser &lt;strong&gt;obrigado&lt;/strong&gt; a apagar de verdade depois de um tempo. Aí um job periódico com &lt;code&gt;forceDelete()&lt;/code&gt; nos registros antigos resolve.&lt;/p&gt;

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

&lt;p&gt;Já perdeu algum registro importante em produção por causa de um &lt;code&gt;delete()&lt;/code&gt; sem volta? Ou já foi salvo por um soft delete que alguém teve a sabedoria de colocar antes?&lt;/p&gt;

&lt;p&gt;Me conta aí embaixo. E se esse post pode evitar um choro futuro no seu projeto, salva ele e manda pro colega que ainda apaga tudo direto no banco. 😅&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>eloquent</category>
      <category>database</category>
    </item>
    <item>
      <title>Casts do Eloquent que você não sabia que existiam</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Fri, 03 Jul 2026 15:09:26 +0000</pubDate>
      <link>https://dev.to/denisgusto1/casts-do-eloquent-que-voce-nao-sabia-que-existiam-4k85</link>
      <guid>https://dev.to/denisgusto1/casts-do-eloquent-que-voce-nao-sabia-que-existiam-4k85</guid>
      <description>&lt;h2&gt;
  
  
  Você ainda faz &lt;code&gt;json_decode&lt;/code&gt; na mão, né?
&lt;/h2&gt;

&lt;p&gt;Confessa. Tem um lugar no seu projeto onde você lê uma coluna do banco, dá um &lt;code&gt;json_decode&lt;/code&gt;, mexe no array, dá &lt;code&gt;json_encode&lt;/code&gt; de volta e salva. E toda vez você reza pra não esquecer o &lt;code&gt;true&lt;/code&gt; no &lt;code&gt;json_decode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ou pior: aquela coluna que guarda uma lista separada por vírgula e vive num vai-e-vem de &lt;code&gt;explode&lt;/code&gt; e &lt;code&gt;implode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O Eloquent resolve isso há anos com &lt;strong&gt;casts&lt;/strong&gt;. Só que a maioria dos devs conhece uns quatro (&lt;code&gt;array&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;datetime&lt;/code&gt;, &lt;code&gt;integer&lt;/code&gt;) e para por aí. O problema é que a lista é bem maior — e tem uns casts que são pura mão na roda esperando você descobrir.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relembrando rapidinho: o que é um cast
&lt;/h2&gt;

&lt;p&gt;Cast é uma tradução automática entre o formato que fica no banco e o formato que você quer usar no PHP. Você declara no model e o Eloquent faz a ponte sozinho, nos dois sentidos:&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;Produto&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'ativo'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'boolean'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'preco'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'decimal:2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'publicado_em'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'datetime'&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;Até aqui, nada de novo. A graça começa nos casts que ninguém mostra.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;AsArrayObject&lt;/code&gt;: array de verdade, mexível
&lt;/h2&gt;

&lt;p&gt;O cast &lt;code&gt;array&lt;/code&gt; clássico tem uma pegadinha chata: você não consegue mutar um item direto. Isso aqui &lt;strong&gt;não funciona&lt;/strong&gt; como você espera:&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;$produto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'azul'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ignorado silenciosamente 😑&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com &lt;code&gt;AsArrayObject&lt;/code&gt;, funciona:&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\Eloquent\Casts\AsArrayObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AsArrayObject&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="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&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;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cor'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'azul'&lt;/span&gt;&lt;span class="p"&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;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'tamanho'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'M'&lt;/span&gt;&lt;span class="p"&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;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// salva certinho&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coluna JSON que você mexe direto, sem cerimônia.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;AsCollection&lt;/code&gt;: sua coluna JSON vira Collection
&lt;/h2&gt;

&lt;p&gt;Se você ama as Collections do Laravel (e você ama), por que trabalhar com aquele JSON como array cru? O &lt;code&gt;AsCollection&lt;/code&gt; te devolve uma Collection completa, com &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;where&lt;/code&gt;, o pacote 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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Casts\AsCollection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'tags'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AsCollection&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="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&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;tags&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;filter&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="nv"&gt;$tag&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;$tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ativo'&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;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nome'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E tem um detalhe pouco conhecido: dá pra apontar uma &lt;strong&gt;Collection customizada&lt;/strong&gt; sua, com métodos próprios de negócio:&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;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AsCollection&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OptionCollection&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;AsEnumCollection&lt;/code&gt;: lembra dos Enums? Agora um array deles
&lt;/h2&gt;

&lt;p&gt;Já falei aqui sobre &lt;a href="https://dev.to"&gt;castar uma coluna pra Enum&lt;/a&gt;. Mas e quando a coluna guarda &lt;strong&gt;vários&lt;/strong&gt; enums? Tipo as permissões de um usuário, os status pelos quais um pedido passou?&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;App\Enums\Permissao&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\Database\Eloquent\Casts\AsEnumCollection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'permissoes'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AsEnumCollection&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Permissao&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="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;Salva como JSON no banco, volta como uma Collection de enums tipados. Type-safety num array inteiro, de graça.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;AsStringable&lt;/code&gt;: string com superpoderes
&lt;/h2&gt;

&lt;p&gt;Toda vez que você faz &lt;code&gt;Str::of($model-&amp;gt;titulo)-&amp;gt;slug()&lt;/code&gt;, tá tratando na mão o que o cast já faz. Com &lt;code&gt;AsStringable&lt;/code&gt;, a coluna já sai como um objeto &lt;code&gt;Stringable&lt;/code&gt;, pronto pra encadear:&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\Eloquent\Casts\AsStringable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'titulo'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AsStringable&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="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// já é fluente&lt;/span&gt;
&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&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;upper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;encrypted&lt;/code&gt; e &lt;code&gt;hashed&lt;/code&gt;: segurança sem boilerplate
&lt;/h2&gt;

&lt;p&gt;Precisa guardar um dado sensível criptografado no banco? Tem cast pra isso, e ele criptografa na gravação e descriptografa na leitura sozinho:&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;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'token_api'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'encrypted'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'dados_bancarios'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'encrypted:array'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// criptografa E serializa&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 pra senha, o &lt;code&gt;hashed&lt;/code&gt; aplica o hash automático quando você atribui — chega de &lt;code&gt;Hash::make()&lt;/code&gt; espalhado:&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;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'hashed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'senha123'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// já vai hasheada pro banco&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Configurações de usuário.&lt;/strong&gt; Uma coluna &lt;code&gt;preferencias&lt;/code&gt; JSON com &lt;code&gt;AsArrayObject&lt;/code&gt; deixa você fazer &lt;code&gt;$user-&amp;gt;preferencias['tema'] = 'dark'&lt;/code&gt; e salvar, sem decode nenhum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roles/permissões.&lt;/strong&gt; &lt;code&gt;AsEnumCollection::of(Permissao::class)&lt;/code&gt; guarda a lista tipada e você compara com &lt;code&gt;contains()&lt;/code&gt; sem medo de string errada.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metadados de qualquer coisa.&lt;/strong&gt; Pedido, produto, post — aquela coluna &lt;code&gt;meta&lt;/code&gt; JSON com &lt;code&gt;AsCollection&lt;/code&gt; te dá &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt; e amigos direto no model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pegadinha: quer o seu? Cria um custom cast
&lt;/h2&gt;

&lt;p&gt;Nenhum dos prontos serve? Você cria o seu implementando &lt;code&gt;CastsAttributes&lt;/code&gt; (leitura e gravação) ou &lt;code&gt;CastsInboundAttributes&lt;/code&gt; (só na gravação, tipo um hash). Exemplo real de um cast que hasheia antes de salvar:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Casts&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\Contracts\Database\Eloquent\CastsInboundAttributes&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\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AsHash&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CastsInboundAttributes&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="nv"&gt;$model&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;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&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;$attributes&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'codigo_secreto'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AsHash&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regra de bolso: se você percebe que fica transformando o mesmo atributo do mesmo jeito toda vez, isso é um cast pedindo pra nascer.&lt;/p&gt;

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

&lt;p&gt;Abre o model mais gordo do seu projeto e procura por &lt;code&gt;json_decode&lt;/code&gt;, &lt;code&gt;explode&lt;/code&gt;, &lt;code&gt;Str::of($this-&amp;gt;&lt;/code&gt; ou &lt;code&gt;Hash::make&lt;/code&gt; dentro dele. Cada um desses é forte candidato a virar cast e sumir do controller.&lt;/p&gt;

&lt;p&gt;A doc oficial lista uns quantos que nem citei aqui — &lt;code&gt;AsUri&lt;/code&gt;, &lt;code&gt;AsFluent&lt;/code&gt;, &lt;code&gt;immutable_datetime&lt;/code&gt;, &lt;code&gt;decimal&lt;/code&gt;. Vale um passeio de 10 minutos.&lt;/p&gt;

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

&lt;p&gt;Qual desses casts você não conhecia? Aposto que pelo menos um vai entrar no seu próximo commit. 😄&lt;/p&gt;

&lt;p&gt;Me conta aí embaixo qual coluna JSON do seu projeto tá implorando por um &lt;code&gt;AsArrayObject&lt;/code&gt;. E se o post te economizou uns &lt;code&gt;json_decode&lt;/code&gt;, salva ele e manda pro colega que ainda trata dado na unha.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>eloquent</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Migrations que não quebram em produção — o guia que ninguém te deu</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Fri, 03 Jul 2026 15:09:25 +0000</pubDate>
      <link>https://dev.to/denisgusto1/migrations-que-nao-quebram-em-producao-o-guia-que-ninguem-te-deu-363o</link>
      <guid>https://dev.to/denisgusto1/migrations-que-nao-quebram-em-producao-o-guia-que-ninguem-te-deu-363o</guid>
      <description>&lt;h2&gt;
  
  
  A migration rodou liso. Aí você deu deploy.
&lt;/h2&gt;

&lt;p&gt;Você criou a migration, rodou &lt;code&gt;php artisan migrate&lt;/code&gt; no seu PC, tudo verdinho. Testou, funcionou, subiu pra produção numa sexta às 18h porque "é só uma coluninha".&lt;/p&gt;

&lt;p&gt;E aí o deploy travou. Ou pior: rodou, mas a tabela de 8 milhões de linhas ficou travada por 40 segundos e o site deu timeout pra todo mundo.&lt;/p&gt;

&lt;p&gt;Migration é traiçoeira assim. No banco vazio da sua máquina tudo é rápido e reversível. Em produção, com dados de verdade e usuários online, cada comando tem consequência. Bora ver os cuidados que separam um deploy tranquilo de um plantão não planejado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regra 1: o &lt;code&gt;down&lt;/code&gt; tem que existir e funcionar
&lt;/h2&gt;

&lt;p&gt;Toda migration tem &lt;code&gt;up&lt;/code&gt; e &lt;code&gt;down&lt;/code&gt;. O &lt;code&gt;up&lt;/code&gt; você caprichou. O &lt;code&gt;down&lt;/code&gt;? Geralmente tá vazio ou errado — e você só descobre no pior momento, quando precisa de um rollback às pressas.&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;up&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;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pedidos'&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;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rastreio'&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;nullable&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;down&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;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pedidos'&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;dropColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rastreio'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// o inverso EXATO do up&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;Regra de bolso: se você não consegue escrever um &lt;code&gt;down&lt;/code&gt; que desfaz o &lt;code&gt;up&lt;/code&gt;, sua migration provavelmente tá fazendo coisa demais. Teste o rollback &lt;strong&gt;antes&lt;/strong&gt; do deploy: &lt;code&gt;migrate&lt;/code&gt; e depois &lt;code&gt;migrate:rollback&lt;/code&gt; na sua máquina. Se voltar limpo, respira aliviado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regra 2: nunca edite uma migration que já rodou em produção
&lt;/h2&gt;

&lt;p&gt;Essa dói porque é tentador. Você criou a coluna com o tamanho errado, abre a migration antiga e conserta ali mesmo. No seu PC funciona (você deu &lt;code&gt;migrate:fresh&lt;/code&gt;). Em produção, aquela migration &lt;strong&gt;já rodou&lt;/strong&gt; — o Laravel não vai executá-la de novo. A correção nunca chega no servidor.&lt;/p&gt;

&lt;p&gt;O certo é sempre uma migration &lt;strong&gt;nova&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;// Nova migration, não mexe na antiga&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;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&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;string&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="mi"&gt;100&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;change&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;Migration é histórico. Você não reescreve o passado, você adiciona um capítulo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regra 3: adicionar coluna? &lt;code&gt;nullable&lt;/code&gt; ou com &lt;code&gt;default&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Adicionar uma coluna &lt;code&gt;NOT NULL&lt;/code&gt; sem valor padrão numa tabela que já tem dados é pedido de erro na certa — o banco não sabe o que colocar nas linhas existentes.&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;// 💣 explode se a tabela já tiver linhas&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;'cpf'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ seguro&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;'cpf'&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;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ou&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;boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ativo'&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;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Precisa mesmo que a coluna seja obrigatória e preenchida? Faz em três passos: cria &lt;code&gt;nullable&lt;/code&gt;, preenche os dados, depois torna obrigatória. Nunca tudo de uma vez numa tabela grande.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regra 4: &lt;code&gt;dropColumn&lt;/code&gt; apaga dados. Pra sempre.
&lt;/h2&gt;

&lt;p&gt;Rollback de coluna adicionada é tranquilo. Rollback de coluna &lt;strong&gt;removida&lt;/strong&gt; não existe — o &lt;code&gt;down&lt;/code&gt; recria a coluna vazia, mas os dados que estavam lá já foram pro além.&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;up&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;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&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;dropColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bio'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// os dados somem AGORA&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;Antes de dropar em produção: tem backup? Tem certeza que nada mais usa essa coluna? Uma prática segura é deixar a coluna morrer aos poucos — para de usá-la no código num deploy, e só remove no banco semanas depois, quando tem certeza absoluta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regra 5: separe mudança de schema de mudança de dados
&lt;/h2&gt;

&lt;p&gt;A tentação é preencher os dados na mesma migration que altera a estrutura. O problema: um &lt;code&gt;User::all()&lt;/code&gt; dentro da migration pode carregar milhões de registros na memória e travar o deploy.&lt;/p&gt;

&lt;p&gt;Se precisar transformar dados, use &lt;code&gt;chunk&lt;/code&gt; pra não estourar a memória — ou, melhor ainda, jogue isso num comando/job separado que roda depois da migration:&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;// Se for na migration mesmo, ao menos vá de chunk:&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tipo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&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;chunkById&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="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;)&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;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'tipo'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'comum'&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;Schema é uma coisa, dado é outra. Misturar os dois numa migration só é onde os deploys vão morrer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regra 6: no deploy, sempre &lt;code&gt;--force&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Em produção o Laravel pergunta "tem certeza?" antes de rodar migration — e num pipeline automatizado isso trava esperando um "yes" que nunca vem. A flag &lt;code&gt;--force&lt;/code&gt; pula a confirmação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coloca isso no seu script de deploy e nunca mais veja o pipeline pendurado esperando input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pegadinha: tabela grande = lock demorado
&lt;/h2&gt;

&lt;p&gt;Num MySQL, alterar uma coluna (&lt;code&gt;change()&lt;/code&gt;) ou adicionar índice numa tabela gigante pode &lt;strong&gt;travar&lt;/strong&gt; a tabela inteira durante a operação. No seu PC com 200 linhas é instantâneo. Em produção com milhões, são segundos (ou minutos) de tabela indisponível.&lt;/p&gt;

&lt;p&gt;Pra tabelas realmente grandes, vale olhar ferramentas de migração online (como &lt;code&gt;pt-online-schema-change&lt;/code&gt;) ou rodar a alteração numa janela de baixo tráfego. Só de &lt;strong&gt;saber&lt;/strong&gt; que o lock existe você já evita a surpresa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: seu checklist de pré-deploy
&lt;/h2&gt;

&lt;p&gt;Antes de dar push numa migration, passa por essa lista mental:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;code&gt;down&lt;/code&gt; desfaz o &lt;code&gt;up&lt;/code&gt;? Testei o rollback local?&lt;/li&gt;
&lt;li&gt;Estou editando uma migration antiga? (Se sim, para. Cria uma nova.)&lt;/li&gt;
&lt;li&gt;Coluna nova é &lt;code&gt;nullable&lt;/code&gt; ou tem &lt;code&gt;default&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Tem &lt;code&gt;dropColumn&lt;/code&gt;? Tem backup e certeza?&lt;/li&gt;
&lt;li&gt;Tem manipulação de dados pesada? Dá pra separar num job?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Migration não é código descartável — é a coisa que roda direto no seu banco de produção, sem rede de proteção. Um pouco de paranoia aqui economiza muitos plantões.&lt;/p&gt;

&lt;p&gt;Me conta aí embaixo: qual migration já te deu o maior susto em produção? Todo dev tem uma história dessas. 😅 E se esse checklist te salvou de um deploy na sexta, salva o post e manda pro colega que ainda edita migration antiga.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>Seu status de pedido não devia ser uma string</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Thu, 02 Jul 2026 19:58:53 +0000</pubDate>
      <link>https://dev.to/denisgusto1/seu-status-de-pedido-nao-devia-ser-uma-string-5137</link>
      <guid>https://dev.to/denisgusto1/seu-status-de-pedido-nao-devia-ser-uma-string-5137</guid>
      <description>&lt;h2&gt;
  
  
  Quantos jeitos de escrever "pendente" existem no seu banco?
&lt;/h2&gt;

&lt;p&gt;Abre a tabela de pedidos aí do seu projeto e dá uma olhada na coluna &lt;code&gt;status&lt;/code&gt;. Aposto que você acha &lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;Pending&lt;/code&gt;, talvez um &lt;code&gt;pendente&lt;/code&gt; perdido de quando o cliente pediu pra traduzir, e vai saber, um &lt;code&gt;pnding&lt;/code&gt; que passou batido numa madrugada.&lt;/p&gt;

&lt;p&gt;Isso acontece porque status virou uma &lt;strong&gt;string solta&lt;/strong&gt;. Qualquer um pode escrever qualquer coisa ali. Não tem trava, não tem autocomplete, não tem ninguém pra dizer "opa, esse valor não existe".&lt;/p&gt;

&lt;p&gt;Já perdeu meia hora caçando um bug que era só um &lt;code&gt;paid&lt;/code&gt; escrito &lt;code&gt;payed&lt;/code&gt;? Senta aí, porque o PHP 8.1 resolveu isso e o Laravel abraçou a solução com carinho.&lt;/p&gt;

&lt;h2&gt;
  
  
  O reino da string mágica
&lt;/h2&gt;

&lt;p&gt;O cenário clássico. Status espalhado como texto puro pelo código 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="c1"&gt;// No controller&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// No outro controller&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;$pedido&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="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Naquela blade&lt;/span&gt;
&lt;span class="o"&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;$pedido&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="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'canceled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// E no service, com um pequeno "typo" que ninguém viu&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;update&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;'canceld'&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 desses &lt;code&gt;'pending'&lt;/code&gt;, &lt;code&gt;'paid'&lt;/code&gt;, &lt;code&gt;'canceled'&lt;/code&gt; é o que a gente chama de &lt;strong&gt;string mágica&lt;/strong&gt;: um valor cru, sem significado formal, que você reza pra ter digitado certo.&lt;/p&gt;

&lt;p&gt;E o problema não é só o typo. É que &lt;strong&gt;nada te avisa&lt;/strong&gt; dos valores possíveis. Chegou dev novo no time? Ele vai ter que caçar no grep quais status existem. Mudou &lt;code&gt;paid&lt;/code&gt; pra &lt;code&gt;payment_confirmed&lt;/code&gt;? Boa sorte achando os 23 lugares.&lt;/p&gt;

&lt;h2&gt;
  
  
  A meia-solução que muita gente usa
&lt;/h2&gt;

&lt;p&gt;Aí você lembra das constantes e pensa "já resolvi 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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pedido&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;STATUS_PENDING&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;STATUS_PAID&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;STATUS_CANCELED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'canceled'&lt;/span&gt;&lt;span class="p"&gt;;&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="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;STATUS_PAID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Melhorou? Um pouco. Ganhou autocomplete e centralizou os valores. Mas ainda tem furo: nada impede alguém de gravar &lt;code&gt;$pedido-&amp;gt;status = 'qualquer_coisa'&lt;/code&gt; direto. A constante é uma sugestão, não uma regra. E você continua sem um lugar único pra pendurar comportamento (a cor do badge, o label bonito, se aquele status permite cancelamento...).&lt;/p&gt;

&lt;p&gt;Dá pra fazer melhor. E de fábrica.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: Enums de verdade
&lt;/h2&gt;

&lt;p&gt;O PHP 8.1 trouxe os &lt;strong&gt;backed enums&lt;/strong&gt; — enums com um valor por trás. É um tipo novo, de verdade, que só aceita os casos que você definiu:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Enums&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Pending&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Paid&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Canceled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'canceled'&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 &lt;code&gt;: string&lt;/code&gt; ali no topo: é isso que faz cada caso ter um valor de string por trás, perfeito pra salvar no banco. Agora &lt;code&gt;StatusPedido::Paid&lt;/code&gt; é um &lt;strong&gt;objeto tipado&lt;/strong&gt;, não um texto solto. E aqui mora a mágica: no Laravel, você fala pro model tratar a coluna como esse enum, usando o cast:&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;Pedido&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&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="k"&gt;return&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="nc"&gt;StatusPedido&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="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;Pronto. A partir daqui o Eloquent faz a ponte sozinho: grava a string no banco, devolve o enum quando você lê:&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;$pedido&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="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Paid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// grava 'paid' no banco&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;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                      &lt;span class="c1"&gt;// volta como StatusPedido::Paid (objeto)&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;$pedido&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="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Paid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// type-safe, com autocomplete, sem chance de typo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tentou atribuir uma string inválida? Estoura na hora. O valor errado nem chega no banco.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Comparação sem medo.&lt;/strong&gt; Acabou o &lt;code&gt;=== 'paid'&lt;/code&gt;. Agora é &lt;code&gt;=== StatusPedido::Paid&lt;/code&gt;, e o editor te entrega o autocomplete de todos os 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;if&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="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Canceled&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;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;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'erro'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Esse pedido já foi cancelado.'&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;Comportamento junto do dado.&lt;/strong&gt; O melhor dos enums: eles têm métodos. Aquela lógica de "qual a cor do badge" para de viver espalhada na blade e vira parte do próprio status:&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="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Pending&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Paid&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Canceled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'canceled'&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;label&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Pending&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Aguardando pagamento'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Paid&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="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Canceled&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Cancelado'&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;cor&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Pending&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'yellow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Paid&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Canceled&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'red'&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;Na view, fica limpo assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="badge badge-{{ $pedido-&amp;gt;status-&amp;gt;cor() }}"&amp;gt;
    {{ $pedido-&amp;gt;status-&amp;gt;label() }}
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Preencher um select.&lt;/strong&gt; O método &lt;code&gt;cases()&lt;/code&gt; já te dá todos os valores possíveis, prontos pra iterar:&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="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ &lt;/span&gt;&lt;span class="nv"&gt;$status-&amp;gt;value&lt;/span&gt;&lt;span class="s2"&gt; }}"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;label&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;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endforeach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pegadinha: &lt;code&gt;value&lt;/code&gt; vs. &lt;code&gt;name&lt;/code&gt; vs. o caso em si
&lt;/h2&gt;

&lt;p&gt;Aqui rola uma confusão comum. Um backed enum tem três coisas fáceis de embolar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StatusPedido::Paid&lt;/code&gt; — o caso em si, o objeto tipado. É o que você usa em comparação.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StatusPedido::Paid-&amp;gt;value&lt;/code&gt; — a string por trás (&lt;code&gt;'paid'&lt;/code&gt;). É o que vai pro banco, pro &lt;code&gt;value&lt;/code&gt; do &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;, pra API.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StatusPedido::Paid-&amp;gt;name&lt;/code&gt; — o nome do caso no código (&lt;code&gt;'Paid'&lt;/code&gt;). Raramente é o que você quer mostrar pro usuário.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regra rápida: comparou lógica? Usa o caso. Precisou do valor cru (banco, HTML, JSON)? Usa &lt;code&gt;-&amp;gt;value&lt;/code&gt;. E pra transformar uma string de volta em enum — tipo o que chega de um request — tem o &lt;code&gt;from()&lt;/code&gt; e o &lt;code&gt;tryFrom()&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="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// StatusPedido::Paid&lt;/span&gt;
&lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'inexistente'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// estoura ValueError&lt;/span&gt;
&lt;span class="nc"&gt;StatusPedido&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tryFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'xpto'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// null, sem estourar (ótimo pra input do usuário)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bônus: validação de request de brinde
&lt;/h2&gt;

&lt;p&gt;Já que estamos falando de dados que entram, o Laravel valida enum direto na regra. Se alguém mandar um status que não existe no request, nem passa:&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\Validation\Rule&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Rule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StatusPedido&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Junta isso com o Form Request que você já usa e o status inválido morre antes de chegar no controller. String mágica não passa nem na porta.&lt;/p&gt;

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

&lt;p&gt;Faz um teste rápido: dá um grep por &lt;code&gt;'pending'&lt;/code&gt;, &lt;code&gt;'paid'&lt;/code&gt; ou qualquer status do seu projeto. Quantos lugares apareceram? Cada um deles é uma string mágica esperando pra te dar dor de cabeça numa refatoração.&lt;/p&gt;

&lt;p&gt;Me conta aí embaixo: qual é a string mágica mais cabeluda que ainda vive no seu código? Status de pedido, tipo de usuário, forma de pagamento... todo projeto tem a sua. 😄&lt;/p&gt;

&lt;p&gt;E se esse post te deu vontade de matar umas strings soltas hoje, salva ele e manda pro colega que ainda escreve &lt;code&gt;=== 'paid'&lt;/code&gt; por aí.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Seu controller tá fazendo o trabalho do Form Request (e ele não devia)</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:42:12 +0000</pubDate>
      <link>https://dev.to/denisgusto1/seu-controller-ta-fazendo-o-trabalho-do-form-request-e-ele-nao-devia-512o</link>
      <guid>https://dev.to/denisgusto1/seu-controller-ta-fazendo-o-trabalho-do-form-request-e-ele-nao-devia-512o</guid>
      <description>&lt;h2&gt;
  
  
  Abre o seu controller. Vai, eu espero
&lt;/h2&gt;

&lt;p&gt;Tem um método &lt;code&gt;store()&lt;/code&gt; aí dentro com umas 40 linhas? Começando com um &lt;code&gt;$request-&amp;gt;validate([...])&lt;/code&gt; gigante, seguido de um monte de &lt;code&gt;if&lt;/code&gt;, depois a regra de negócio, depois o retorno?&lt;/p&gt;

&lt;p&gt;Pois é. Seu controller tá fazendo um trabalho que não é dele.&lt;/p&gt;

&lt;p&gt;A função de um controller é simples: receber a requisição, chamar quem resolve o problema, devolver a resposta. Ele é o garçom, não o cozinheiro. E ainda assim a gente vive enfiando validação, autorização e tratamento de dados ali no meio, até virar aquele monstrinho de 80 linhas que ninguém quer abrir na sexta-feira.&lt;/p&gt;

&lt;p&gt;Já viu (ou escreveu) isso? Senta aí que tem solução, e ela já vem de fábrica no Laravel.&lt;/p&gt;

&lt;h2&gt;
  
  
  O controller-faz-tudo
&lt;/h2&gt;

&lt;p&gt;Olha esse clássico:&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;store&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="nv"&gt;$validated&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;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'titulo'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|string|max:255'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'conteudo'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|string|min:10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'categoria'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|exists:categorias,id'&lt;/span&gt;&lt;span class="p"&gt;,&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;$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;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'criar-post'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;abort&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'titulo'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&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;$validated&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;'posts.show'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&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? Funciona. Mas repara quantas responsabilidades diferentes tão espremidas aí: validação, autorização, transformação de dado e a regra de negócio de verdade. Tudo no mesmo lugar.&lt;/p&gt;

&lt;p&gt;Agora imagina esse mesmo bloco repetido no &lt;code&gt;update()&lt;/code&gt;. E na versão da API. Copiado, colado, com pequenas diferenças. De novo a velha história.&lt;/p&gt;

&lt;h2&gt;
  
  
  O controller tem UMA função
&lt;/h2&gt;

&lt;p&gt;Existe um princípio chamado &lt;strong&gt;Single Responsibility&lt;/strong&gt;: cada classe deveria ter um motivo só pra mudar. O controller muda quando o fluxo da requisição muda — não quando uma regra de validação muda, não quando uma permissão muda.&lt;/p&gt;

&lt;p&gt;Validação é um conhecimento. Autorização é outro. Cada um merece morar na sua própria casa, e o Laravel já construiu essa casa pra você: o &lt;strong&gt;Form Request&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: tira o peso do controller
&lt;/h2&gt;

&lt;p&gt;Gera com um comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:request StorePostRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso cria &lt;code&gt;app/Http/Requests/StorePostRequest.php&lt;/code&gt;. Joga tudo que não é função de controller pra dentro dele:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Requests&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\Foundation\Http\FormRequest&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\Support\Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StorePostRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Autorização: este usuário pode fazer isso?&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;authorize&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;bool&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;$this&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;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'criar-post'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Validação: os dados estão corretos?&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;rules&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'titulo'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'max:255'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'conteudo'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'min:10'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'categoria'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'exists:categorias,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="c1"&gt;// Preparação: ajusta os dados ANTES de validar&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;prepareForValidation&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;merge&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'slug'&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;slug&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;titulo&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;E agora olha o que sobrou do controller:&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;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StorePostRequest&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;$post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&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="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;'posts.show'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&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;Duas linhas. &lt;strong&gt;Duas.&lt;/strong&gt; O controller voltou a ser garçom.&lt;/p&gt;

&lt;p&gt;O pulo do gato é o type-hint: ao trocar &lt;code&gt;Request&lt;/code&gt; por &lt;code&gt;StorePostRequest&lt;/code&gt;, o Laravel &lt;strong&gt;roda a autorização e a validação automaticamente&lt;/strong&gt; antes do método executar. Falhou? Nem entra no &lt;code&gt;store()&lt;/code&gt;. O usuário já recebe o erro certo, redirecionado de volta, com as mensagens preenchidas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Form Request não é só &lt;code&gt;rules()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Esse é o ponto que quase ninguém usa direito. O Form Request tem três super-poderes, não um:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autorização&lt;/strong&gt; — o &lt;code&gt;authorize()&lt;/code&gt; decide quem pode. Retornou &lt;code&gt;false&lt;/code&gt;, vira 403 sozinho:&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;authorize&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;bool&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;$this&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="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="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'post'&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;user_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;&lt;strong&gt;Mensagens customizadas&lt;/strong&gt; — sem espalhar texto pelo 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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;messages&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'titulo.required'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'O título é obrigatório, capricha nele.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'conteudo.min'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Conteúdo muito curto, conta mais história.'&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;&lt;strong&gt;Preparação de dados&lt;/strong&gt; — o &lt;code&gt;prepareForValidation()&lt;/code&gt; ajusta a entrada antes da regra rodar. Ótimo pra normalizar CPF, gerar slug, limpar máscara de telefone.&lt;/p&gt;

&lt;p&gt;Tudo isso saiu do controller e foi morar onde faz sentido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pausa: e quando NÃO usar?
&lt;/h2&gt;

&lt;p&gt;Porque sim, dá pra exagerar. Form Request vira muleta quando você cria um pra &lt;strong&gt;toda e qualquer&lt;/strong&gt; rota, mesmo as triviais.&lt;/p&gt;

&lt;p&gt;Tem um endpoint que valida só um campo? Um filtro de busca com um &lt;code&gt;q&lt;/code&gt; opcional? Criar uma classe inteira pra isso é cerimônia demais. Um &lt;code&gt;$request-&amp;gt;validate(['q' =&amp;gt; 'nullable|string'])&lt;/code&gt; ali mesmo resolve e é mais legível.&lt;/p&gt;

&lt;p&gt;A régua é simples: se a validação é curta, usada num lugar só e não tem autorização envolvida, deixa inline. Se ela cresce, se repete, ou se mistura validação com permissão — chama o Form Request. Ele existe pra tirar peso, não pra adicionar burocracia.&lt;/p&gt;

&lt;p&gt;E mais um aviso: Form Request &lt;strong&gt;não é&lt;/strong&gt; lugar de regra de negócio. Calcular preço, disparar evento, mexer em outras tabelas — isso é trabalho da camada de serviço, não da validação. Não troque um controller inchado por um Form Request inchado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: olha quantos controllers seus pedem isso
&lt;/h2&gt;

&lt;p&gt;Pega seu projeto e procura por &lt;code&gt;$request-&amp;gt;validate(&lt;/code&gt;. Quantos resultados? Cada um daqueles, especialmente os que se repetem entre &lt;code&gt;store&lt;/code&gt; e &lt;code&gt;update&lt;/code&gt;, é um Form Request esperando pra nascer.&lt;/p&gt;

&lt;p&gt;Começa pelo controller mais bagunçado que você tem. Extrai um. Vê como ele encolhe. Vicia.&lt;/p&gt;

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

&lt;p&gt;Pergunta sincera: &lt;strong&gt;qual é o controller mais inchado do seu projeto hoje?&lt;/strong&gt; Aquele que você sente um arrepio só de abrir.&lt;/p&gt;

&lt;p&gt;Comenta aí embaixo o tamanho do método &lt;code&gt;store()&lt;/code&gt; mais cabeludo que você já viu (ou escreveu, sem vergonha). E se esse post te deu vontade de fazer um refactor agora, salva ele e manda pro colega que ainda valida tudo no controller.&lt;/p&gt;

&lt;p&gt;Seu controller magro agradece. 😄&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Seu código de validação de CPF tá gritando por socorro (e você nem percebeu)</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:37:27 +0000</pubDate>
      <link>https://dev.to/denisgusto1/seu-codigo-de-validacao-de-cpf-ta-gritando-por-socorro-e-voce-nem-percebeu-ni2</link>
      <guid>https://dev.to/denisgusto1/seu-codigo-de-validacao-de-cpf-ta-gritando-por-socorro-e-voce-nem-percebeu-ni2</guid>
      <description>&lt;p&gt;Deixa eu adivinhar.&lt;/p&gt;

&lt;p&gt;Você tá com um projeto Laravel rodando, tem uns 5, 10, talvez 15 formulários que recebem CPF. Cadastro de cliente, cadastro de fornecedor, atualização de perfil, checkout, área administrativa… e em cada um desses lugares tem aquela mesma lógica de validação de CPF. Copiada. Colada. Com pequenas variações.&lt;/p&gt;

&lt;p&gt;E tá tudo bem. Até o dia em que o cliente pede pra mudar uma regra. Ou um bug aparece em um formulário e funciona normal no outro. Aí você abre o projeto, dá um Ctrl+Shift+F procurando "cpf" e… surpresa: tem oito lugares diferentes com a mesma validação. Com mensagens de erro escritas de oito jeitos. Uma delas até com erro de digitação.&lt;/p&gt;

&lt;p&gt;Já passou por isso? Então senta que essa conversa é pra você.&lt;/p&gt;

&lt;h2&gt;
  
  
  O crime acontecendo em câmera lenta
&lt;/h2&gt;

&lt;p&gt;Olha esse cenário aqui, que eu garanto que você já viu (ou escreveu):&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;// app/Http/Requests/StoreClienteRequest.php&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;rules&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;'cpf'&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;'required'&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;$attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$cpf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[^0-9]/'&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="nv"&gt;$value&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="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cpf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CPF inválido.'&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="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// ... mais 20 linhas do algoritmo&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;E aí, três dias depois, no outro Form Request:&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;// app/Http/Requests/StoreFornecedorRequest.php&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;rules&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;'cpf'&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;'required'&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;$attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$cpf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[^0-9]/'&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="nv"&gt;$value&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="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cpf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'O CPF informado não é válido!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// mensagem diferente, claro&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// ... mais 20 linhas quase iguais, mas não exatamente&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;Multiplica isso por 8 telas. Agora imagina o seu "eu do futuro" tentando manter isso.&lt;/p&gt;

&lt;p&gt;Dá pra sentir a dor daqui.&lt;/p&gt;

&lt;h2&gt;
  
  
  DRY: a sigla que vai salvar seu projeto (e sua sanidade)
&lt;/h2&gt;

&lt;p&gt;DRY significa &lt;strong&gt;Don't Repeat Yourself&lt;/strong&gt;. Em bom português: não se repita, caramba.&lt;/p&gt;

&lt;p&gt;A ideia é simples: cada pedaço de conhecimento (uma regra de negócio, um cálculo, uma validação) deve existir em &lt;strong&gt;um único lugar&lt;/strong&gt; no seu sistema. Se precisar mudar, você muda em um lugar só, e todo o resto continua funcionando.&lt;/p&gt;

&lt;p&gt;No nosso caso, a "regra de validar CPF" é um conhecimento. E esse conhecimento merece ter um endereço fixo no seu projeto, não ficar espalhado igual confete depois do Carnaval.&lt;/p&gt;

&lt;h2&gt;
  
  
  E se eu te dissesse que o Laravel já resolveu isso pra você?
&lt;/h2&gt;

&lt;p&gt;O Laravel tem um recurso chamado &lt;strong&gt;Custom Validation Rule&lt;/strong&gt;. É basicamente uma classe que encapsula uma regra de validação. Você escreve uma vez, usa em qualquer lugar.&lt;/p&gt;

&lt;p&gt;Gera com um comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:rule Cpf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso cria o arquivo &lt;code&gt;app/Rules/Cpf.php&lt;/code&gt;. Abre ele e coloca o algoritmo de validação ali dentro:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Rules&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;Closure&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\Contracts\Validation\ValidationRule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cpf&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ValidationRule&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;validate&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;$attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&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;$fail&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;// Tira tudo que não é número&lt;/span&gt;
        &lt;span class="nv"&gt;$cpf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[^0-9]/'&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// CPF tem 11 dígitos. Ponto.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cpf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'O :attribute informado não é um CPF válido.'&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="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Rejeita sequências repetidas (111.111.111-11, 222.222.222-22, etc)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/(\d)\1{10}/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cpf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'O :attribute informado não é um CPF válido.'&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="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Calcula os dois dígitos verificadores&lt;/span&gt;
        &lt;span class="k"&gt;for&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="mi"&gt;9&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;11&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="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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$c&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;$c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;$c&lt;/span&gt;&lt;span class="o"&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;$d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$cpf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$c&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="nv"&gt;$t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$c&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="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&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;$cpf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'O :attribute informado não é um CPF válido.'&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="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;Pronto. Acabou. Esse é o &lt;strong&gt;único lugar&lt;/strong&gt; do seu projeto que sabe validar CPF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usando na prática
&lt;/h2&gt;

&lt;p&gt;Agora vem a parte boa. Você usa essa Rule em qualquer lugar, com uma linha:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Em um Form Request:&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Rules\Cpf&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;rules&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;'cpf'&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;'required'&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;Cpf&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;&lt;strong&gt;Inline no controller:&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="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'cpf'&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;'required'&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;Cpf&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;Com Validator facade:&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="nc"&gt;Validator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&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="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'cpf'&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;'required'&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;Cpf&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;validate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo código. Mesma mensagem de erro. Mesmo comportamento. Em todo lugar.&lt;/p&gt;

&lt;p&gt;Mudou a regra? Mexe no &lt;code&gt;app/Rules/Cpf.php&lt;/code&gt;. Pronto, mudou em todo lugar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pausa rápida: Rule não é Role
&lt;/h2&gt;

&lt;p&gt;Detalhe que confunde muita gente (e tudo bem, eu também já confundi):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rule&lt;/strong&gt; = regra de validação. É disso que a gente tá falando.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt; = papel de usuário, ligado a permissões e autorização (tipo "admin", "editor", "cliente").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;São coisas completamente diferentes, só têm nome parecido. Se você buscou no Google "Laravel Role validação" e tava perdido, agora sabe o porquê. 😄&lt;/p&gt;

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

&lt;p&gt;Pegou a ideia? Então olha quantas outras validações no seu projeto provavelmente tão duplicadas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CNPJ&lt;/li&gt;
&lt;li&gt;Telefone (com DDD, formato brasileiro)&lt;/li&gt;
&lt;li&gt;CEP&lt;/li&gt;
&lt;li&gt;Data de nascimento (maioridade, idade mínima, etc)&lt;/li&gt;
&lt;li&gt;Placa de veículo&lt;/li&gt;
&lt;li&gt;Cartão de crédito&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada uma dessas é uma Rule esperando pra nascer. Que tal escolher uma e criar agora? Lição de casa rápida pra quando você terminar de ler isso aqui.&lt;/p&gt;

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

&lt;p&gt;Aqui vai uma pergunta sincera: &lt;strong&gt;qual validação você ainda mantém copiada e colada no seu projeto Laravel?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Comenta aí embaixo. Eu juro que não vou julgar (eu mesmo tenho um projeto antigo que precisa urgentemente desse refactor).&lt;/p&gt;

&lt;p&gt;E se esse post te poupou de uma dor de cabeça futura, salva ele, compartilha com aquele dev que tá começando agora, ou manda pro seu colega de equipe que tá com 14 controllers validando CPF do jeito errado.&lt;/p&gt;

&lt;p&gt;Seu eu do futuro agradece. Seu projeto, também.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curtiu? Tem mais conteúdo sobre Laravel, boas práticas e como deixar seu código mais limpo por aqui. Me segue pra não perder os próximos.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
