Nullable types e o operador ?->
1. Introdução aos Nullable Types no PHP
Antes do PHP 7.1, lidar com valores nulos era uma tarefa ambígua e propensa a erros. Funções que podiam retornar null não tinham uma forma explícita de declarar essa possibilidade na assinatura, forçando os desenvolvedores a confiar em documentação ou inspeção manual do código. O programador precisava adivinhar se um retorno poderia ser nulo.
Com a introdução dos Nullable Types no PHP 7.1, esse cenário mudou drasticamente. Agora é possível declarar explicitamente que um tipo pode aceitar null usando o prefixo ? antes do tipo:
function saudacao(?string $nome): string {
return "Olá, " . ($nome ?? "visitante") . "!";
}
echo saudacao(null); // Olá, visitante!
echo saudacao("João"); // Olá, João!
A partir do PHP 8.0, uma sintaxe alternativa usando union types também está disponível:
function saudacao(string|null $nome): string {
return "Olá, " . ($nome ?? "visitante") . "!";
}
Ambas as formas são equivalentes, mas a sintaxe com ? é mais concisa para casos simples.
2. Nullable Types em Parâmetros e Retornos
Parâmetros nullable são especialmente úteis quando um argumento é opcional e seu valor padrão é null:
function configurarEmail(
string $destinatario,
?string $assunto = null,
?string $corpo = null
): string {
$assunto = $assunto ?? "Sem assunto";
$corpo = $corpo ?? "Sem conteúdo";
return "Enviando para $destinatario: $assunto - $corpo";
}
echo configurarEmail("user@example.com");
echo configurarEmail("user@example.com", "Olá", "Corpo da mensagem");
Retornos nullable são comuns em funções de busca que podem não encontrar resultados:
function buscarUsuario(int $id): ?array {
$usuarios = [
1 => ['nome' => 'Ana', 'email' => 'ana@exemplo.com'],
2 => ['nome' => 'Carlos', 'email' => 'carlos@exemplo.com'],
];
return $usuarios[$id] ?? null;
}
$usuario = buscarUsuario(3); // null
var_dump($usuario); // NULL
3. Nullable Types em Propriedades de Classe
Propriedades de classe também podem ser declaradas como nullable, permitindo inicialização tardia ou estado opcional:
class Produto {
public ?string $descricao;
public ?float $desconto;
public function __construct(
public string $nome,
public float $preco,
?string $descricao = null
) {
$this->descricao = $descricao;
$this->desconto = null;
}
public function aplicarDesconto(float $percentual): void {
$this->desconto = $percentual;
}
}
$produto = new Produto("Notebook", 3500.00);
echo $produto->descricao; // null (sem erro)
$produto->aplicarDesconto(10.0);
Com promoted properties (PHP 8.0+), a declaração fica ainda mais limpa:
class Pedido {
public function __construct(
public string $codigo,
public ?string $observacao = null,
public ?\DateTime $dataEntrega = null
) {}
}
$pedido = new Pedido("PED-123");
$pedido->dataEntrega = new \DateTime('2025-01-15');
4. O Operador Nullsafe: ?->
O operador nullsafe (?->) foi introduzido no PHP 8.0 e revolucionou a forma como lidamos com encadeamento de chamadas em objetos potencialmente nulos. Em vez de verificar manualmente cada nível, o PHP interrompe automaticamente a execução se encontrar null:
class Endereco {
public function __construct(
public string $cidade,
public string $estado
) {}
}
class Usuario {
public function __construct(
public string $nome,
public ?Endereco $endereco = null
) {}
public function getEndereco(): ?Endereco {
return $this->endereco;
}
}
$usuario = new Usuario("Maria");
// Sem nullsafe - múltiplas verificações
$cidade = null;
if ($usuario !== null && $usuario->getEndereco() !== null) {
$cidade = $usuario->getEndereco()->cidade;
}
// Com nullsafe - uma linha
$cidade = $usuario?->getEndereco()?->cidade;
var_dump($cidade); // NULL (sem erro)
5. Casos de Uso do Operador ?->
O grande poder do nullsafe está no encadeamento profundo de objetos:
class Empresa {
public function __construct(
public string $nome,
public ?Departamento $departamento = null
) {}
}
class Departamento {
public function __construct(
public string $nome,
public ?Funcionario $gerente = null
) {}
}
class Funcionario {
public function __construct(
public string $nome,
public ?string $email = null
) {}
}
function obterEmailGerente(?Empresa $empresa): ?string {
// Encadeamento seguro - interrompe se qualquer nível for null
return $empresa?->departamento?->gerente?->email;
}
$empresa = new Empresa("TechCorp");
echo obterEmailGerente($empresa); // NULL (sem erro)
$empresaCompleta = new Empresa(
"TechCorp",
new Departamento("TI", new Funcionario("Ana", "ana@techcorp.com"))
);
echo obterEmailGerente($empresaCompleta); // ana@techcorp.com
Isso elimina a necessidade de múltiplos blocos if:
// Antes - código verboso
function getCidadeUsuario(?Usuario $usuario): ?string {
if ($usuario === null) {
return null;
}
$endereco = $usuario->getEndereco();
if ($endereco === null) {
return null;
}
return $endereco->cidade;
}
// Depois - código elegante
function getCidadeUsuario(?Usuario $usuario): ?string {
return $usuario?->getEndereco()?->cidade;
}
6. Nullsafe com Arrays e Callables
O operador nullsafe também funciona com acesso a arrays e callables, embora com algumas particularidades:
// Acesso a índices de array com nullsafe
$config = [
'banco' => [
'host' => 'localhost',
'porta' => 3306,
]
];
$porta = $config?['banco']?['porta']; // 3306
$senha = $config?['banco']?['senha']; // null (índice não existe)
// Nullsafe em callables
$callable = null;
$resultado = $callable?(); // null (sem erro de "call to null")
$callable = function() {
return "Executado!";
};
$resultado = $callable?(); // "Executado!"
Limitação importante: O operador ?-> não funciona com métodos estáticos:
class Config {
public static function get(string $chave): ?string {
return $_ENV[$chave] ?? null;
}
}
// Isto NÃO funciona - erro de sintaxe
// $valor = Config?::get('DB_HOST');
// Forma correta
$valor = Config::get('DB_HOST');
7. Boas Práticas e Armadilhas
Quando usar nullable types vs. valores padrão:
// Prefira valores padrão quando possível
function saudacao(string $nome = "Mundo"): string {
return "Olá, $nome!";
}
// Use nullable quando null tem significado semântico diferente
function buscarOuCriar(string $email): ?Usuario {
$usuario = Usuario::findByEmail($email);
return $usuario ?? null; // null significa "não encontrado"
}
Nullsafe não substitui validação completa:
class Logger {
public function log(string $mensagem): void {
echo "[LOG] $mensagem\n";
}
}
$logger = new Logger();
// Nullsafe não valida se o objeto é válido, apenas se não é null
$logger?->log("Teste"); // Funciona
// Mas cuidado com objetos que implementam __call
class Proxy {
public function __call(string $name, array $args) {
// Pode retornar null ou lançar exceção
}
}
Combinação com operadores relacionados:
// Null coalescing (??) - valor padrão para null
$nome = $usuario?->nome ?? "Anônimo";
// Null coalescing assignment (??=) - atribui se for null
$usuario?->apelido ??= "Sem apelido";
// Combinando tudo
$cidade = $usuario?->endereco?->cidade
?? $usuario?->endereco?->estado
?? "Localização desconhecida";
8. Evolução e Comparação com Outros Recursos
Os nullable types e o operador nullsafe representam uma evolução significativa no tratamento de valores nulos no PHP:
// PHP 7.0 - Verificação manual
$cidade = is_null($usuario) ? null :
(is_null($usuario->getEndereco()) ? null :
$usuario->getEndereco()->cidade);
// PHP 7.1+ - Nullable types + operador ternário
$cidade = $usuario !== null ?
($usuario->getEndereco() !== null ?
$usuario->getEndereco()->cidade : null) : null;
// PHP 8.0+ - Nullsafe
$cidade = $usuario?->getEndereco()?->cidade;
// Com match expression (PHP 8.0+)
$status = match(true) {
$usuario?->ativo => "Ativo",
$usuario?->inativo => "Inativo",
default => "Desconhecido"
};
Com a introdução de enums no PHP 8.1, nullable types ganham ainda mais expressividade:
enum StatusUsuario: string {
case Ativo = 'ativo';
case Inativo = 'inativo';
}
class Usuario {
public function __construct(
public string $nome,
public ?StatusUsuario $status = null
) {}
}
O operador nullsafe, combinado com union types e enums, permite escrever código mais seguro e expressivo, reduzindo drasticamente a quantidade de verificações manuais de null sem sacrificar a segurança de tipos.
Referências
- PHP Manual: Nullable Types — Documentação oficial sobre a sintaxe de tipos nullable no PHP.
- PHP Manual: Nullsafe Operator — Documentação oficial do operador nullsafe (
?->) no PHP 8.0+. - PHP 8.0: Nullsafe Operator - stitcher.io — Artigo técnico detalhado sobre o operador nullsafe com exemplos práticos.
- PHP 8.0: Union Types - PHP Watch — Guia completo sobre union types no PHP 8.0, incluindo nullable types.
- PHP The Right Way: Type Hinting — Seção sobre type hinting que aborda nullable types e boas práticas.