"Mas aqui na minha máquina roda perfeitamente"
Você terminou a feature. Testou local, passou. Subiu pra produção.
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.
Você abre o log, confere o .env, a variável tá lá, certinha. Roda local de novo: funciona. Em produção: null. 🙃
Se você já viveu essa cena de terror, provavelmente o culpado é um só: você usou env() num lugar que não devia.
O código que parece inofensivo
Digamos que você criou um service pra falar com uma API de frete. Precisa da chave, então você faz o óbvio:
class FreteService
{
public function __construct()
{
// parece certo, né? peguei do .env e pronto
$this->apiKey = env('FRETE_API_KEY');
$this->baseUrl = env('FRETE_API_URL', 'https://api.frete.com');
}
}
Local, isso funciona lindamente. O Laravel lê o .env, encontra FRETE_API_KEY, e a vida segue.
O problema é que esse código carrega uma bomba-relógio. E o pino é puxado por um comando que todo deploy sério roda.
O que o config:cache faz (e por que ele te trai)
Ler o arquivo .env do disco e parsear tudo a cada request é lento. Por isso, em produção, a recomendação oficial é rodar:
php artisan config:cache
Esse comando junta todos os seus arquivos de config/ num único arquivo PHP otimizado e cacheado. A partir daí, o Laravel nunca mais lê o .env — ele lê o cache.
E aqui tá a pegadinha: quando o config está cacheado, a função env() passa a retornar null pra tudo que não estava dentro dos arquivos de config.
Ou seja, aquele env('FRETE_API_KEY') lá no seu service vira null em produção. Local você provavelmente nunca rodou config:cache, por isso "funciona na sua máquina". O bug estava dormindo o tempo todo.
A regra de ouro
env() só deve ser chamado dentro dos arquivos da pasta config/. Nunca em services, controllers, models, jobs ou qualquer outro lugar.
O jeito certo é: você lê o env() no config, e no resto da aplicação você lê o config com config().
Primeiro, cria (ou usa) um arquivo de config:
// config/frete.php
return [
'api_key' => env('FRETE_API_KEY'),
'base_url' => env('FRETE_API_URL', 'https://api.frete.com'),
];
Depois, no service, você troca env() por config():
class FreteService
{
public function __construct()
{
// agora lê do config — que sobrevive ao cache
$this->apiKey = config('frete.api_key');
$this->baseUrl = config('frete.base_url');
}
}
Pronto. Agora o env() mora onde deve, o config:cache empacota o valor junto, e produção passa a se comportar igual ao seu local.
Como usar na prática
O padrão é sempre o mesmo, em qualquer contexto:
-
Chave de API num service → cria uma entrada em
config/services.phpe lê comconfig('services.frete.key'). Aliás, oconfig/services.phpjá existe justamente pra isso. -
Feature flag num controller →
config('app.features.novo_checkout')em vez deenv('NOVO_CHECKOUT'). -
Timeout de um job → coloca em
config/queue.phpou num config seu e chamaconfig('...')lá dentro.
Regra mental simples: se você tá prestes a escrever env() e o arquivo não está dentro de config/, para. Tá quase virando bug de produção.
A pegadinha da pegadinha
"Mas eu uso env() fora do config e funciona!" — sim, funciona enquanto você não rodar config:cache.
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.
Quer testar se seu código tá limpo agora? Roda isso local:
php artisan config:cache
Se algo quebrar depois disso, achou seus env() mal colocados. Pra voltar ao normal no dev:
php artisan config:clear
Bônus: e agora?
Passa um grep no seu projeto atrás de env( fora da pasta config/. Aposto que tem pelo menos um escondido por aí — geralmente num service antigo que "sempre funcionou".
Enquanto tá nisso, dá uma olhada no config/services.php. Ele foi feito pra guardar credenciais de serviços de terceiros, e muita gente ignora que ele existe.
Antes de você fechar a aba
O env() fora do config é aquele bug educado: ele espera você virar as costas pra produção pra dar as caras.
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. 👀
Se esse tipo de armadilha de Laravel te ajuda, salva o post e me segue: tem mais dessas por aqui.
Top comments (1)
I found this article to be really insightful, especially the part about how
config:cachecan cause issues withenv()calls outside of config files. I've definitely fallen into this trap before, and it's not immediately obvious why things work locally but not in production. One thing that might be worth adding to the article is a note about how to handle cases where you need to access environment variables in tests - would you recommend usingconfig()there as well, or is there a better approach? I've been usingputenv()in my tests, but I'm not sure if that's the best practice.