Configuração por ambiente no Back-end com Slim Framework: dev, teste e produção

Capítulo 9

Tempo estimado de leitura: 9 minutos

+ Exercício

Configuração por ambiente: por que e como organizar

Em um back-end com Slim, a mesma base de código precisa se comportar de forma diferente em dev, teste e produção. A configuração por ambiente é a prática de separar o que é “código” do que é “parâmetro de execução”, permitindo ajustar comportamento (logs, cache, CORS, timeouts, banco, exibição de erros) sem alterar o código-fonte.

Um modelo prático é trabalhar com camadas: defaults (valores seguros e comuns) + override por ambiente (apenas o que muda). Além disso, segredos (DSN, senhas, chaves) devem vir de variáveis de ambiente (ou secret manager), nunca versionados.

Objetivos da estratégia

  • Reprodutibilidade: subir o app em qualquer ambiente com o mesmo artefato.
  • Segurança: segredos fora do repositório e com menor superfície de exposição.
  • Observabilidade: logs e níveis adequados por ambiente.
  • Fail fast: validar configurações na inicialização e falhar cedo.

Estrutura recomendada de arquivos

Uma organização simples e eficaz:

project/ config/ defaults.php env/ dev.php test.php prod.php bootstrap/ config.php public/ index.php

defaults.php contém o baseline. Cada arquivo em env/ sobrescreve apenas o necessário. O bootstrap/config.php carrega, mescla e valida.

Camadas de configuração: defaults + override por ambiente

1) Defaults (config/defaults.php)

Defaults devem ser conservadores e seguros. Evite colocar segredos aqui.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

<?php // config/defaults.php return [ 'app' => [ 'name' => 'Slim API', 'env' => 'prod', 'timezone' => 'UTC', ], 'http' => [ 'trusted_proxies' => [], 'timeout_seconds' => 10, ], 'errors' => [ 'display_error_details' => false, ], 'log' => [ 'level' => 'warning', 'channel' => 'app', ], 'cors' => [ 'enabled' => true, 'allowed_origins' => [], 'allowed_methods' => ['GET','POST','PUT','PATCH','DELETE','OPTIONS'], 'allowed_headers' => ['Content-Type','Authorization','X-Request-Id'], 'exposed_headers' => ['X-Request-Id'], 'allow_credentials' => false, 'max_age' => 600, ], 'cache' => [ 'enabled' => false, 'driver' => 'array', 'ttl_seconds' => 60, ], 'db' => [ 'driver' => 'pdo_pgsql', 'dsn' => null, 'user' => null, 'password' => null, 'options' => [ 'ATTR_ERRMODE' => 'ERRMODE_EXCEPTION', 'ATTR_DEFAULT_FETCH_MODE' => 'FETCH_ASSOC', 'ATTR_TIMEOUT' => 5, ], ], ];

2) Overrides por ambiente (config/env/*.php)

Em dev, normalmente você quer mais verbosidade e menos cache. Em prod, o oposto.

config/env/dev.php

<?php return [ 'app' => [ 'env' => 'dev', ], 'errors' => [ 'display_error_details' => true, ], 'log' => [ 'level' => 'debug', ], 'cors' => [ 'allowed_origins' => ['http://localhost:3000','http://localhost:5173'], 'allow_credentials' => true, ], 'cache' => [ 'enabled' => false, ], ];

config/env/test.php

<?php return [ 'app' => [ 'env' => 'test', ], 'errors' => [ 'display_error_details' => true, ], 'log' => [ 'level' => 'info', ], 'cache' => [ 'enabled' => false, ], 'db' => [ 'options' => [ 'ATTR_TIMEOUT' => 2, ], ], ];

config/env/prod.php

<?php return [ 'app' => [ 'env' => 'prod', ], 'errors' => [ 'display_error_details' => false, ], 'log' => [ 'level' => 'warning', ], 'cors' => [ 'allowed_origins' => ['https://app.suaempresa.com'], 'allow_credentials' => true, ], 'cache' => [ 'enabled' => true, 'driver' => 'redis', 'ttl_seconds' => 300, ], 'http' => [ 'timeout_seconds' => 5, ], ];

3) Carregamento e merge (bootstrap/config.php)

O merge deve respeitar a precedência: defaults < env file < variáveis de ambiente (para segredos e ajustes operacionais).

<?php // bootstrap/config.php function array_merge_recursive_distinct(array $base, array $override): array { foreach ($override as $key => $value) { if (is_array($value) && isset($base[$key]) && is_array($base[$key])) { $base[$key] = array_merge_recursive_distinct($base[$key], $value); } else { $base[$key] = $value; } } return $base; } $defaults = require __DIR__ . '/../config/defaults.php'; $env = getenv('APP_ENV') ?: ($defaults['app']['env'] ?? 'prod'); $envFile = __DIR__ . '/../config/env/' . $env . '.php'; $envConfig = file_exists($envFile) ? require $envFile : []; $config = array_merge_recursive_distinct($defaults, $envConfig); // Override final via env vars (segredos e parâmetros operacionais) $config['app']['env'] = $env; $config['db']['dsn'] = getenv('DB_DSN') ?: $config['db']['dsn']; $config['db']['user'] = getenv('DB_USER') ?: $config['db']['user']; $config['db']['password'] = getenv('DB_PASSWORD') ?: $config['db']['password']; $config['log']['level'] = getenv('LOG_LEVEL') ?: $config['log']['level']; $config['errors']['display_error_details'] = filter_var(getenv('DISPLAY_ERROR_DETAILS') ?: $config['errors']['display_error_details'], FILTER_VALIDATE_BOOL); $config['cors']['allowed_origins'] = getenv('CORS_ALLOWED_ORIGINS') ? array_map('trim', explode(',', getenv('CORS_ALLOWED_ORIGINS'))) : $config['cors']['allowed_origins']; $config['http']['timeout_seconds'] = getenv('HTTP_TIMEOUT_SECONDS') ? (int)getenv('HTTP_TIMEOUT_SECONDS') : $config['http']['timeout_seconds']; return $config;

Tratamento de segredos: chaves, DSN e credenciais

Princípios práticos

  • Nunca versionar segredos em config/*.php.
  • Preferir variáveis de ambiente (DB_PASSWORD, JWT_SECRET, REDIS_URL) ou um secret manager da infraestrutura.
  • Separar “configuração pública” (ex.: cache.enabled) de “configuração sensível” (ex.: senha).
  • Evitar imprimir segredos em logs e mensagens de erro.

Exemplo de variáveis de ambiente esperadas

VariávelExemploUso
APP_ENVdev, test, prodSeleciona override por ambiente
DB_DSNpgsql:host=db;port=5432;dbname=appConexão com banco
DB_USERapp_userUsuário do banco
DB_PASSWORD(segredo)Senha do banco
LOG_LEVELdebug, info, warningVerbosidade de logs
DISPLAY_ERROR_DETAILStrue/falseDetalhes de erro (nunca em prod)
CORS_ALLOWED_ORIGINShttps://app.com,https://admin.app.comOrigens permitidas

Níveis de log e exibição de erros por ambiente

Regras comuns:

  • dev: display_error_details=true e log.level=debug.
  • test: display_error_details=true (para facilitar diagnóstico) e logs moderados.
  • prod: display_error_details=false e log.level=warning (ou info se você precisa de auditoria).

Mesmo em dev, evite logar payloads sensíveis. Se precisar, mascare campos (ex.: senha, token).

Exemplos práticos: CORS, cache, timeouts e banco

CORS: configuração orientada a ambiente

O ideal é permitir somente as origens necessárias. Em dev, liberar localhost; em prod, apenas domínios oficiais.

<?php // Exemplo de leitura da config para um middleware de CORS (pseudo) $cors = $config['cors']; // allowed_origins, allowed_methods, allowed_headers, allow_credentials, max_age

Cuidados:

  • Se allow_credentials=true, não use * em allowed_origins.
  • Responda OPTIONS rapidamente (preflight) e com Access-Control-Max-Age adequado.

Cache: habilitar em produção, desabilitar em dev/test

Cache costuma ser fonte de confusão em desenvolvimento. Por isso, defaults podem vir desabilitados e o override de prod habilita com driver real (ex.: Redis).

<?php // Exemplo de parâmetros de cache $cache = $config['cache']; // enabled, driver, ttl_seconds

Parâmetros úteis por ambiente:

  • dev: enabled=false para evitar “estado invisível”.
  • prod: enabled=true e ttl_seconds maior para reduzir carga.

Timeouts: HTTP e banco

Timeouts devem ser mais agressivos em produção para evitar saturação. Em teste, podem ser menores para falhar rápido.

<?php // HTTP timeout (para clientes externos, se aplicável) $timeout = $config['http']['timeout_seconds']; // Banco (PDO) $pdoTimeout = $config['db']['options']['ATTR_TIMEOUT'] ?? 5;

Boas práticas:

  • Defina timeouts explícitos para conexões externas e banco.
  • Evite timeouts muito altos em prod; prefira retries controlados e circuit breaker (quando aplicável).

Banco de dados: DSN e opções por ambiente

Use DSN via variável de ambiente e mantenha opções sensatas no defaults. Em test, é comum apontar para um banco isolado e reduzir timeouts.

<?php // Exemplo de montagem de conexão a partir da config (sem container, apenas ilustrativo) $dsn = $config['db']['dsn']; $user = $config['db']['user']; $pass = $config['db']['password']; $options = []; foreach (($config['db']['options'] ?? []) as $k => $v) { $const = constant('PDO::' . $k); $options[$const] = constant('PDO::' . $v) ?: $v; } // Observação: o mapeamento acima depende de como você representa constantes; alternativa: guardar já como constantes. $pdo = new PDO($dsn, $user, $pass, $options);

Uma alternativa mais simples é armazenar as opções já como constantes (inteiros) no config, evitando conversões.

Validação de configuração na inicialização (fail fast)

Falhar cedo evita que o app suba “meio quebrado” e só apresente erro sob carga. A validação deve rodar no bootstrap, antes de registrar rotas e iniciar o processamento de requests.

Checklist de validação

  • Obrigatórios: DSN/credenciais em ambientes que usam banco.
  • Consistência: display_error_details não pode estar true em prod.
  • CORS: se allow_credentials=true, allowed_origins não pode conter *.
  • Cache: se driver=redis, exigir URL/host do Redis (variável de ambiente).
  • Tipos: inteiros positivos para timeouts e TTL.

Implementação de validação (bootstrap/validate_config.php)

<?php function assert_true(bool $cond, string $message): void { if (!$cond) { throw new RuntimeException('Config inválida: ' . $message); } } function validate_config(array $config): void { $env = $config['app']['env'] ?? 'prod'; assert_true(in_array($env, ['dev','test','prod'], true), 'APP_ENV deve ser dev|test|prod'); // Erros em produção assert_true(!($env === 'prod' && ($config['errors']['display_error_details'] ?? false) === true), 'DISPLAY_ERROR_DETAILS não pode ser true em produção'); // Banco: exigir DSN/user/pass em prod (ajuste conforme seu caso) if ($env !== 'test') { assert_true(!empty($config['db']['dsn']), 'DB_DSN é obrigatório'); assert_true(!empty($config['db']['user']), 'DB_USER é obrigatório'); assert_true(!empty($config['db']['password']), 'DB_PASSWORD é obrigatório'); } // Timeouts assert_true(($config['http']['timeout_seconds'] ?? 0) > 0, 'HTTP_TIMEOUT_SECONDS deve ser > 0'); $ttl = $config['cache']['ttl_seconds'] ?? 0; assert_true($ttl >= 0, 'cache.ttl_seconds deve ser >= 0'); // CORS rules $cors = $config['cors'] ?? []; $origins = $cors['allowed_origins'] ?? []; if (($cors['allow_credentials'] ?? false) === true) { assert_true(!in_array('*', $origins, true), 'CORS: não use * com allow_credentials=true'); } // Cache driver rules if (($config['cache']['enabled'] ?? false) === true && ($config['cache']['driver'] ?? '') === 'redis') { $redisUrl = getenv('REDIS_URL'); assert_true(!empty($redisUrl), 'REDIS_URL é obrigatório quando cache.driver=redis'); } }

Executando a validação no bootstrap

No ponto de entrada (ex.: public/index.php), carregue config e valide antes de seguir.

<?php $config = require __DIR__ . '/../bootstrap/config.php'; require __DIR__ . '/../bootstrap/validate_config.php'; validate_config($config); // A partir daqui, registre middlewares/rotas e rode o app

Passo a passo prático para aplicar no projeto

Passo 1: criar defaults e arquivos por ambiente

  • Crie config/defaults.php com valores comuns e seguros.
  • Crie config/env/dev.php, test.php, prod.php sobrescrevendo apenas o necessário.

Passo 2: definir variáveis de ambiente para segredos

  • Defina APP_ENV e segredos (DB_DSN, DB_USER, DB_PASSWORD etc.) no runtime (Docker/Kubernetes/CI/servidor).
  • Se usar arquivo local de variáveis, garanta que ele não seja versionado.

Passo 3: implementar carregamento com precedência

  • Carregue defaults.
  • Carregue override do ambiente.
  • Aplique overrides finais via variáveis de ambiente.

Passo 4: validar e falhar cedo

  • Implemente validate_config() com regras de obrigatoriedade e consistência.
  • Rode a validação antes de iniciar o app.

Passo 5: parametrizar CORS, cache, timeouts e banco via config

  • Leia as chaves de config nos pontos de integração (middleware de CORS, cliente HTTP, camada de persistência, cache).
  • Garanta que cada ambiente tenha valores coerentes com seu objetivo (debuggabilidade em dev, estabilidade em prod).

Agora responda o exercício sobre o conteúdo:

Ao organizar configurações por ambiente em um back-end com Slim, qual abordagem atende melhor aos objetivos de reprodutibilidade e segurança sem alterar o código-fonte entre dev, test e prod?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

A estratégia combina defaults + overrides por ambiente, mantendo segredos fora do repositório via variáveis de ambiente. Isso permite o mesmo artefato em todos os ambientes e melhora a segurança, além de validar e falhar cedo no bootstrap.

Próximo capitúlo

Persistência e banco de dados em Slim Framework: PDO, repositórios e transações

Arrow Right Icon
Capa do Ebook gratuito Back-end com Slim Framework (PHP): Rotas, Middlewares e Arquitetura Limpa
56%

Back-end com Slim Framework (PHP): Rotas, Middlewares e Arquitetura Limpa

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.