Programação Por Equipe DevBras

Capa do artigo: PHP 7 que ainda vale: tipos, strict_types e ?? sem quebrar no deploy

PHP 7 que ainda vale: tipos, strict_types e ?? sem quebrar no deploy

Use tipo no parâmetro, strict_types e o operador ?? para falhar cedo, e garanta que desenvolvimento e produção rodam a mesma versão do PHP.

Sexta, 17h. A função buscarPedido(int $id): ?array passou em todos os testes no seu notebook, você subiu por FTP e a loja inteira virou uma página branca com Parse error. O servidor compartilhado ainda rodava PHP 5.6, que não entende tipo de retorno nem o operador ??. Os recursos que o PHP 7 trouxe continuam sendo a base do PHP moderno, mas só valem se o servidor roda a mesma família de versão que a sua máquina.

A resposta curta: declare tipos nos parâmetros e retornos, ligue declare(strict_types=1) nos arquivos novos, use ?? para "não veio" e trave a versão do PHP no composer.json e na imagem do container. O resto deste guia mostra como fazer isso com um exemplo de loja.

Por que o PHP 7 ainda importa em 2026

O PHP 7.0, lançado em 2015, trouxe as peças que hoje parecem óbvias: tipos escalares (int, float, string, bool) em parâmetros, tipo de retorno, o operador de coalescência nula ?? e o operador de comparação combinada <=>. O 7.1 acrescentou tipos anuláveis (?int) e void. O 7.4 trouxe propriedades tipadas. O PHP 8 construiu em cima disso com union types, match e o operador nullsafe.

A série 7 em si está sem suporte oficial desde o fim de 2022. Então "o que vale do PHP 7" não é rodar PHP 7: é usar esse contrato de tipos numa versão suportada. A lista de versões com correção de segurança fica em php.net/supported-versions.php. Confira lá antes de escolher a imagem do servidor.

Como o tipo no parâmetro muda o erro de lugar

Sem tipo, a função aceita qualquer coisa e o problema aparece três chamadas depois, numa consulta SQL estranha ou num total zerado. Com tipo, o PHP recusa na entrada e a mensagem aponta a linha que chamou errado. O erro sai do relatório do cliente e vai para o log de quem escreveu a chamada.

Existe um detalhe que muita gente não sabe. Por padrão, o PHP trabalha em modo coercitivo: se a função pede int e recebe a string "42", ele converte para 42 sem reclamar. Uma string como "abc" gera TypeError. Para que até "42" seja recusado, você declara declare(strict_types=1); na primeira linha do arquivo.

O que o strict_types realmente cobre

A diretiva vale para as chamadas feitas a partir daquele arquivo, não para a função declarada nele. Se Pedidos.php tem strict_types e index.php não tem, uma chamada vinda do index.php continua no modo coercitivo. Por isso o hábito que funciona é colocar a diretiva em todos os arquivos novos, e não só nas classes "importantes".

<?php
declare(strict_types=1);

final class Pedidos
{
    public function __construct(private PDO $pdo) {}

    public function buscar(int $id): ?array
    {
        $stmt = $this->pdo->prepare('SELECT id, total_centavos, status FROM pedidos WHERE id = ?');
        $stmt->execute([$id]);
        $linha = $stmt->fetch(PDO::FETCH_ASSOC);
        return $linha === false ? null : $linha;
    }
}

O retorno ?array diz duas coisas para quem chama: pode vir um array ou pode vir null. Quem usa a função precisa tratar o caso "pedido não existe", e uma ferramenta de análise estática como PHPStan ou Psalm vai cobrar isso.

Quando usar ?? e quando ele esconde um erro

O operador ?? devolve o lado esquerdo se ele existe e não é null; senão, devolve o direito. Ele não gera aviso de índice indefinido, o que o torna ideal para ler $_GET e configuração opcional. Mas ele não troca string vazia nem zero, e isso é uma vantagem: vazio pode ser um valor inválido que você quer recusar, enquanto nulo significa "não veio".

Compare com o operador ternário curto ?:, que troca qualquer valor falso. Na loja, a pessoa pede a página "0" da listagem. Com ?:, a string "0" é falsa e vira a página 1 sem ninguém perceber. Com ??, o "0" passa adiante e a sua validação recusa de forma explícita.

<?php
declare(strict_types=1);

$bruto = $_GET['pagina'] ?? '1';

$pagina = filter_var($bruto, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if ($pagina === false) {
    http_response_code(400);
    exit('Página inválida');
}

// $pagina agora é int e pode ir para uma função tipada
$lista = $catalogo->listar($pagina);

Repare na sequência: ?? resolve a ausência, filter_var resolve o formato e só então o valor entra numa função tipada. Com strict_types ligado, passar $bruto direto para listar(int $pagina) geraria TypeError, que é exatamente o que você quer descobrir no desenvolvimento.

Como garantir que produção roda a mesma versão

O recurso de linguagem só existe se as duas pontas concordam. O caso da sexta-feira acontece porque ninguém escreveu em lugar nenhum qual versão o projeto exige. Siga estes passos:

  1. Declare a versão mínima no Composer. No composer.json, coloque "php": ">=8.3" em require. O Composer recusa instalar dependências num ambiente abaixo disso.
  2. Fixe a plataforma de resolução. Em config.platform.php, informe a versão de produção. Assim o Composer, rodando no seu PHP mais novo, não escolhe pacotes que exigem algo que o servidor não tem.
  3. Verifique no servidor. Rode composer check-platform-reqs no deploy. Ele compara o PHP e as extensões instaladas com o que o projeto exige.
  4. Use a mesma imagem nos dois lados. Se o deploy é em container, a tag da imagem (por exemplo php:8.3-fpm) vai no repositório e é a mesma no docker compose local.
  5. Confira a versão que serve a web, não a do terminal. O php -v mostra o PHP da linha de comando. O PHP-FPM ou o módulo do Apache pode ser outro. Exponha PHP_VERSION numa rota protegida de diagnóstico ou no log de inicialização.
{
    "require": {
        "php": ">=8.3",
        "ext-pdo": "*"
    },
    "config": {
        "platform": {
            "php": "8.3.0"
        }
    }
}

Com isso, o erro da sexta muda de lugar: em vez da página branca para o cliente, o deploy falha no passo do Composer com uma mensagem dizendo que o PHP do servidor não atende ao requisito. Ninguém gosta de deploy que falha, mas ele é muito melhor do que deploy que passa e quebra. Se você ainda está organizando o servidor, o guia de configuração do PHP sem susto cobre os ajustes de produção que acompanham essa decisão.

Erros comuns

  • Achar que servidor antigo ignora o tipo. Em PHP 5, o tipo de retorno e o ?? não são ignorados: são erro de sintaxe, e o arquivo inteiro não carrega.
  • Ligar strict_types só na classe. A diretiva vale para quem chama. Sem ela no arquivo chamador, a string "42" continua sendo convertida em silêncio.
  • Trocar ?? por ?: por estética. O ternário curto engole "0", 0 e string vazia, e você perde a distinção entre "não veio" e "veio inválido".
  • Usar ?? como validação. Ele só resolve a ausência. Formato, faixa e tamanho continuam sendo papel de filter_var ou da sua regra.
  • Conferir a versão com php -v e parar aí. CLI e FPM podem ser versões diferentes no mesmo servidor, e o site roda no FPM.
  • Atualizar o PHP e esquecer o cache de opcode. Depois de trocar versão ou arquivos, o OPcache pode servir código antigo por um tempo; veja como o cache de opcode esconde a correção.

Checklist

  • Os arquivos novos começam com declare(strict_types=1);.
  • Funções públicas têm tipo em todos os parâmetros e no retorno.
  • Leitura de entrada usa ?? para ausência e filter_var ou validação própria para formato.
  • O composer.json declara a versão mínima do PHP e a plataforma de produção.
  • O deploy roda composer check-platform-reqs e para se falhar.
  • A versão do PHP que serve a web foi conferida no próprio servidor, não só no terminal.
  • A versão em produção aparece na lista de versões suportadas do php.net.

A diferença entre o PHP do seu computador e o do servidor é a mesma diferença entre testar na rede local e atender o cliente. Abra agora o servidor de produção e responda: a versão que roda o site, no FPM e não no terminal, é a mesma que está escrita no seu composer.json?

Avalie este artigo

Referência: http://tableless.com.br/10-novidades-do-php-7/