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:
- Declare a versão mínima no Composer. No
composer.json, coloque"php": ">=8.3"emrequire. O Composer recusa instalar dependências num ambiente abaixo disso. - 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. - Verifique no servidor. Rode
composer check-platform-reqsno deploy. Ele compara o PHP e as extensões instaladas com o que o projeto exige. - 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 nodocker composelocal. - Confira a versão que serve a web, não a do terminal. O
php -vmostra o PHP da linha de comando. O PHP-FPM ou o módulo do Apache pode ser outro. ExponhaPHP_VERSIONnuma 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",0e 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 defilter_varou da sua regra. - Conferir a versão com
php -ve 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 efilter_varou validação própria para formato. - O
composer.jsondeclara a versão mínima do PHP e a plataforma de produção. - O deploy roda
composer check-platform-reqse 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?
Referência: http://tableless.com.br/10-novidades-do-php-7/