PHP ?? e ?->

Muitas vezes um valor pode estar faltando: o cliente não informou o telefone, o endereço não tem complemento, a página foi aberta sem o nome na URL. O operador ?? resolve isso numa linha: "use este valor; se ele não existir ou for null, use aquele outro". Nesta aula você vai conhecer o ??, o seu irmão ??= e o ?->, que evita um erro fatal quando o objeto é null.

Você vai aprender
  • O que o ?? faz e por que ele não mostra Warning quando a variável não existe.
  • A diferença entre ??, isset, o ternário e o ?: (com 0 e "" isso importa muito).
  • Como encadear vários ?? e como usar o ??= para preencher só o que está faltando.
  • O operador nullsafe ?-> PHP 8.0+ e o erro Call to a member function … on null.
  • A ideia do $_GET["nome"] ?? "visitante", que você vai usar em todo formulário.

Antes, é bom ter visto null (o valor "nada") e Booleanos. Alguns exemplos usam um array com nomes nas posições, como $aluna["nome"]; basta saber que é uma "ficha" com campos, e a aula Arrays Associativos explica tudo.


A ideia: o plano B

Pense numa ficha de cadastro em papel. Quando a Ana vai ler o campo "Apelido" e ele está em branco, ela usa o nome. Quando o campo "Telefone" está em branco, ela escreve "não informado". Ninguém para o trabalho por causa de um campo vazio: existe um plano B.

O ?? é exatamente isso. Ele olha o valor da esquerda:

  • se ele existe e não é null, usa ele;
  • senão, usa o valor da direita (o plano B, ou "valor padrão").

O nome oficial é null coalescing operator, "operador de coalescência nula". Coalescer quer dizer "juntar": ele junta vários candidatos e fica com o primeiro que não é null.


O problema: o que não existe gera um aviso

Primeiro, veja o que acontece sem o ?? quando a variável não existe:

<?php
echo $sobrenome, "\n";
echo "fim\n";
$ php sem.php

Warning: Undefined variable $sobrenome in /home/ana/site/sem.php on line 2

fim

O PHP avisa "variável indefinida: $sobrenome" (veja Lendo Erros do PHP), trata o valor como null e continua. Com uma chave de array que não existe, acontece o mesmo:

<?php
$aluna = ["nome" => "Ana", "nota" => 8];

echo $aluna["nome"], "\n";
echo $aluna["email"], "\n";
echo $aluna["email"] ?? "sem e-mail", "\n";
$ php aluno.php
Ana

Warning: Undefined array key "email" in /home/ana/site/aluno.php on line 5

sem e-mail

A linha 5 deu o aviso Undefined array key "email" ("chave de array indefinida") e mostrou uma linha vazia. A linha 6 é a mesma coisa com o ??: nada de aviso, e o plano B apareceu.


O operador ??

<?php
$apelido = null;
$nome = "Ana";

echo $apelido ?? "sem apelido", "\n";
echo $nome ?? "sem nome", "\n";
echo $sobrenome ?? "sem sobrenome", "\n";
$ php coalescencia.php
sem apelido
Ana
sem sobrenome
$apelido
O valor que você quer usar (o plano A).
??
"Se o da esquerda não existir ou for null…"
"sem apelido"
"…use este" (o plano B). Pode ser um texto, um número, outra variável, o resultado de uma conta.

Linha por linha:

ExpressãoO valor da esquerdaResultado
$apelido ?? "sem apelido"existe, mas é null"sem apelido"
$nome ?? "sem nome"existe e vale "Ana""Ana"
$sobrenome ?? "sem sobrenome"não existe"sem sobrenome", sem Warning

Esse "sem Warning" é o grande trunfo do ??. Ele verifica se a variável existe antes de ler o valor. Funciona até com várias chaves seguidas: se o pedido não tiver cliente, $pedido["cliente"]["email"] ?? "sem e-mail" dá "sem e-mail", sem nenhum aviso.

Missão: rode e veja o Warning da última linha. Depois use o ?? para mostrar Telefone: não informado, sem aviso nenhum. Copie o jeito da linha do e-mail (os parênteses são necessários; a próxima seção explica).

<?php
$aluna = ["nome" => "Ana", "email" => "ana@exemplo.com"];

echo "Nome: " . $aluna["nome"] . "\n";
echo "E-mail: " . ($aluna["email"] ?? "não informado") . "\n";
echo "Telefone: " . $aluna["telefone"] . "\n";
<?php
$aluna = ["nome" => "Ana", "email" => "ana@exemplo.com"];

echo "Nome: " . $aluna["nome"] . "\n";
echo "E-mail: " . ($aluna["email"] ?? "não informado") . "\n";
echo "Telefone: " . ($aluna["telefone"] ?? "não informado") . "\n";

Por que os parênteses com o .?

O ?? tem prioridade menor que o ponto que junta textos. Sem parênteses, "Telefone: " . $aluna["telefone"] ?? "não informado" é lido como ("Telefone: " . $aluna["telefone"]) ?? "não informado": o PHP junta os textos primeiro (com o Warning) e o resultado, um texto, nunca é null. Então, sempre que o ?? estiver no meio de outros operadores, coloque-o entre parênteses. A aula Precedência mostra esse caso com a saída real.


Encadeando: plano B, plano C…

Dá para colocar vários ?? seguidos. O PHP fica com o primeiro valor que existe e não é null:

<?php
$apelido = null;
$nome = null;
echo $apelido ?? $nome ?? "visitante", "\n";
$nome = "Ana";
echo $apelido ?? $nome ?? "visitante", "\n";
$ php cadeia.php
visitante
Ana

Na primeira vez, o apelido e o nome são null, então sobra "visitante". Na segunda, o apelido continua null, mas o nome vale "Ana", e o PHP para ali.


??, isset e o ternário

Antes do PHP 7.0 não existia ??. O jeito de escrever "valor ou plano B" era com a função isset ("está definido?") e o operador ternário (condição ? se sim : se não, que tem aula própria):

<?php
$apelido = null;
echo isset($apelido) ? $apelido : "sem apelido", "\n";
echo $apelido ?? "sem apelido", "\n";
var_dump(isset($apelido));
var_dump(isset($nome));
$nome = "Ana";
var_dump(isset($nome));
$ php isset.php
sem apelido
sem apelido
bool(false)
bool(false)
bool(true)
isset($apelido)
Responde true se a variável existe e não é null. Aqui, false: ela existe, mas vale null.
? $apelido : "sem apelido"
O ternário: se o isset deu true, usa $apelido; senão, "sem apelido".
$apelido ?? "sem apelido"
Faz exatamente a mesma coisa, e você escreve o nome da variável uma vez só.

Em outras palavras: $a ?? $b é um atalho para isset($a) ? $a : $b. Você ainda vai encontrar a forma longa em código antigo. Em código novo, prefira o ??. Use o isset sozinho quando a pergunta for só "existe?", sem plano B (por exemplo, numa decisão com if).

Cuidado: ?? só se importa com null

O ?? não troca 0, "" (texto vazio) nem false. Esses valores existem e não são null, então o ?? os mantém. Existe outro atalho, o ?: (apelidado de "operador Elvis", porque ?: lembra o topete do cantor), que troca qualquer valor que vale false. Compare:

<?php
$quantidade = 0;
$cupom = "";

var_dump($quantidade ?? 1);
var_dump($quantidade ?: 1);
var_dump($cupom ?? "nenhum");
var_dump($cupom ?: "nenhum");
$ php zero.php
int(0)
int(1)
string(0) ""
string(6) "nenhum"

Se a Ana tem zero itens no carrinho, o ?: "corrigiu" para 1, o que é um erro. O ?? respeitou o zero. E tem mais uma diferença: o ?: mostra o Warning quando a variável não existe:

<?php
echo $sobrenome ?: "sem sobrenome", "\n";
$ php elvis.php

Warning: Undefined variable $sobrenome in /home/ana/site/elvis.php on line 2
sem sobrenome

Qual usar?

Você quer…UseExemplo
Um valor padrão quando a variável ou a chave não existe ou é null??$aluna["email"] ?? "não informado"
Um valor padrão também para "", 0 e false (a variável com certeza existe)?:$titulo ?: "Sem título"
Só saber se existe (true/false)isset()isset($aluna["email"])
A mesma coisa que o ??, mas em código antigo (PHP 5)isset() ? : isset($a) ? $a : "x"

??=: preencher só o que está faltando PHP 7.4+

Às vezes você não quer só mostrar um valor padrão, e sim guardá-lo na variável, mas apenas se ela ainda estiver vazia (null ou inexistente). É o ??=, uma atribuição combinada, como +=:

<?php
$tema = null;
$tema ??= "claro";
echo $tema, "\n";

$tema ??= "escuro";
echo $tema, "\n";

$config = ["idioma" => "pt-BR"];
$config["idioma"] ??= "en";
$config["moeda"] ??= "BRL";
print_r($config);
$ php atrib.php
claro
claro
Array
(
    [idioma] => pt-BR
    [moeda] => BRL
)
$tema ??= "claro";
Atalho de $tema = $tema ?? "claro";. Como $tema era null, passou a valer "claro".
$tema ??= "escuro";
$tema já tem valor, então nada muda: continua "claro".
$config["idioma"] ??= "en";
O idioma já existia (pt-BR), ficou como estava.
$config["moeda"] ??= "BRL";
A chave moeda não existia: foi criada com BRL, sem Warning. O print_r mostra o array por dentro (veja var_dump e print_r).

É o jeito ideal de preencher configurações com valores padrão: o que a pessoa escolheu fica; o que ela não escolheu ganha o padrão.


O operador nullsafe ?-> PHP 8.0+

Este operador funciona com objetos, que você vai estudar a fundo no Nível 3 (Classes e Objetos). Por enquanto, basta saber: um objeto é um valor que tem funções próprias, chamadas de métodos, e você chama um método com a seta ->. O PHP já vem com vários objetos prontos; um deles é a data, DateTime, que tem o método format para escrever a data do jeito que você quiser.

A lojinha da Ana guarda a data de entrega de cada pedido. Quando a entrega ainda não foi marcada, a variável vale null. Veja o que acontece:

<?php
$entrega = new DateTime("2026-10-20");
echo $entrega->format("d/m/Y"), "\n";

$entrega = null;
echo $entrega->format("d/m/Y"), "\n";
echo "fim\n";
$ php entrega.php
20/10/2026

Fatal error: Uncaught Error: Call to a member function format() on null in /home/ana/site/entrega.php:6
Stack trace:
#0 {main}
  thrown in /home/ana/site/entrega.php on line 6

Agora não é um simples aviso: é um erro fatal, e o programa parou (o "fim" nunca apareceu). A mensagem diz "chamada a um método format() em null": você pediu para o "nada" se formatar.

O ?-> (seta com interrogação) resolve: se o valor da esquerda for null, ele não chama o método e devolve null. Junto com o ??, vira um plano B completo:

<?php
$entrega = new DateTime("2026-10-20");
var_dump($entrega?->format("d/m/Y"));

$entrega = null;
var_dump($entrega?->format("d/m/Y"));

echo "Entrega: " . ($entrega?->format("d/m/Y") ?? "a combinar"), "\n";
$ php entrega2.php
string(10) "20/10/2026"
NULL
Entrega: a combinar
$entrega
Pode ser um objeto de data ou null.
?->
"Se não for null, chame o método; se for, pare aqui e devolva null."
format("d/m/Y")
O método chamado quando há data: escreve dia/mês/ano.
?? "a combinar"
Se o resultado foi null, usa o plano B.
Antes do PHP 8.0

Sem o ?->, era preciso perguntar antes: $entrega !== null ? $entrega->format("d/m/Y") : null. Em cadeias longas (objeto dentro de objeto), isso virava vários if. Com o nullsafe, $pedido?->cliente?->endereco?->cidade para no primeiro null que encontrar.


Na web: $_GET["nome"] ?? "visitante"

Este é o uso mais comum do ?? na vida real. Quando alguém abre ola.php?nome=Ana, o PHP coloca o nome num array especial chamado $_GET. Mas a pessoa pode abrir só ola.php, sem nada, e aí a chave "nome" não existe. A aula $_GET e $_POST explica tudo em detalhes; aqui fica só a ideia.

<?php
$nome = $_GET["nome"] ?? "visitante";
echo "<p>Olá, " . htmlspecialchars($nome) . "!</p>\n";

Testando com o servidor embutido e o curl (que abre a página pelo terminal), com e sem o nome na URL:

$ curl -s "localhost:8000/ola.php?nome=Ana"
<p>Olá, Ana!</p>
$ curl -s localhost:8000/ola.php
<p>Olá, visitante!</p>

Sem o ?? (com $nome = $_GET["nome"];), a página aberta sem o nome mostra os avisos para o visitante, em HTML:

$ curl -s localhost:8000/ola-sem.php
<br />
<b>Warning</b>:  Undefined array key "nome" in <b>/home/ana/site/ola-sem.php</b> on line <b>2</b><br />
<br />
<b>Deprecated</b>:  htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in <b>/home/ana/site/ola-sem.php</b> on line <b>3</b><br />
<p>Olá, !</p>

O htmlspecialchars está ali por segurança: ele impede que alguém coloque HTML (ou JavaScript) na URL e o seu site o mostre. Veja Escapar a Saída. Experimente: a caixa abaixo funciona como uma página de verdade, aberta no endereço index.php?nome=Ana.

Rode e veja o nome que veio da URL. Depois troque $_GET["nome"] por $_GET["apelido"] (uma chave que não está no endereço) e rode de novo: aparece o plano B, sem Warning. Por fim, tire o ?? "visitante" e veja o aviso que o visitante receberia.

<?php
$nome = $_GET["nome"] ?? "visitante";
echo "<p>Olá, " . htmlspecialchars($nome) . "!</p>\n";

Quando dá errado

Fatal error: Uncaught Error: Call to a member function format() on null
$ php entrega.php
20/10/2026

Fatal error: Uncaught Error: Call to a member function format() on null in /home/ana/site/entrega.php:6
Stack trace:
#0 {main}
  thrown in /home/ana/site/entrega.php on line 6

O que aconteceu: a variável antes da seta -> vale null, e null não tem métodos. O programa parou na linha 6.

Como resolver: se o null é esperado (a entrega pode não estar marcada), use ?-> e um ?? para o plano B. Se não deveria ser null, descubra de onde veio o null (veja Depurar).

O plano B não apareceu: o valor era 0 ou ""
$ php zero.php
int(0)
int(1)
string(0) ""
string(6) "nenhum"

O que aconteceu: o ?? só troca null e o que não existe. Zero, texto vazio e false são valores de verdade para ele.

Como resolver: se você quer trocar também o vazio, use ?: (e garanta que a variável existe). Se o zero é um valor válido, como uma quantidade, o ?? está certo.

Warning: Undefined array key, mesmo com ?? na linha
$ php telefone.php
Nome: Ana
E-mail: ana@exemplo.com

Warning: Undefined array key "telefone" in /home/ana/site/telefone.php on line 6
Telefone: 

O que aconteceu: o ?? não está "encostado" na variável. Ou ele foi esquecido, ou está fora de parênteses depois de um ., e aí o PHP leu a variável antes de chegar no ??.

Como resolver: "Telefone: " . ($aluna["telefone"] ?? "não informado"). O mesmo acontece se a variável estiver dentro de uma função: strtoupper($apelido) ?? "X" avisa, porque a função lê o valor primeiro. Escreva strtoupper($apelido ?? "x").

Deprecated: Passing null to parameter #1 ($string) of type string is deprecated
$ php funcao.php

Warning: Undefined variable $apelido in /home/ana/site/funcao.php on line 2

Deprecated: strtoupper(): Passing null to parameter #1 ($string) of type string is deprecated in /home/ana/site/funcao.php on line 2

O que aconteceu: a linha era echo strtoupper($apelido) ?? "SEM APELIDO", "\n";. O valor null foi entregue a uma função que espera texto. Deprecated quer dizer "obsoleto": hoje é só um aviso, mas numa versão futura do PHP vai virar erro. E o plano B nem foi usado, porque strtoupper devolveu um texto vazio, não null.

Como resolver: dê o plano B antes de entregar à função: strtoupper($apelido ?? "sem apelido").


Referência

OperadorNomeO que fazDesde
$a ?? $bCoalescência nula$a se existir e não for null; senão $b. Sem Warning.PHP 7.0
$a ?? $b ?? $cEm cadeiaO primeiro que existir e não for null.PHP 7.0
$a ??= $bAtribuição de coalescência nulaGuarda $b em $a só se $a não existir ou for null.PHP 7.4
$obj?->metodo()NullsafeChama o método se $obj não for null; senão devolve null.PHP 8.0
$a ?: $b"Elvis" (ternário curto)$a se ele valer true; senão $b. Avisa se $a não existir.PHP 5.3
isset($a)"Está definido?"true se $a existe e não é null.sempre

Exercícios

1. Plano B

Complete para mostrar a cidade, ou não informada se ela não existir:

echo $cliente["cidade"] "não informada";

2. Preencher o que falta

Complete para o tema virar "claro" só se ainda não tiver valor:

$tema "claro";

3. Objeto que pode ser null

Complete para não dar erro fatal quando $entrega for null:

echo $entregaformat("d/m/Y") ?? "a combinar";

4. $quantidade = 0; O que var_dump($quantidade ?? 1); mostra?

5. A variável $x nunca foi criada. Qual linha mostra padrão sem nenhum Warning?

6. $a ?? $b é um atalho para qual forma longa?

Missão final: a ficha do cliente

Missão: siga os três comentários. (1) Mostre o apelido ou, se ele for null, o nome do cliente. (2) Mostre não informada se a cidade for null. (3) Use ??= para guardar "Brasil" no país, que ainda não existe. No fim, nenhum Warning.

<?php
$cliente = ["nome" => "Ana", "cidade" => null];
$apelido = null;

// 1. Use o apelido; se ele for null, use o nome do cliente
echo "Cliente: " . $apelido . "\n";

// 2. Se a cidade for null ou não existir, mostre "não informada"
echo "Cidade: " . $cliente["cidade"] . "\n";

// 3. Se o cliente não tiver país, guarde "Brasil" nele (use ??=)

echo "País: " . $cliente["pais"] . "\n";
<?php
$cliente = ["nome" => "Ana", "cidade" => null];
$apelido = null;

// 1. Use o apelido; se ele for null, use o nome do cliente
echo "Cliente: " . ($apelido ?? $cliente["nome"]) . "\n";

// 2. Se a cidade for null ou não existir, mostre "não informada"
echo "Cidade: " . ($cliente["cidade"] ?? "não informada") . "\n";

// 3. Se o cliente não tiver país, guarde "Brasil" nele (use ??=)
$cliente["pais"] ??= "Brasil";
echo "País: " . $cliente["pais"] . "\n";

Resumo

  • $a ?? $b: usa $a se ele existir e não for null; senão, $b. Não mostra Warning para variável ou chave inexistente.
  • É o atalho de isset($a) ? $a : $b. Pode ser encadeado: $a ?? $b ?? "padrão".
  • O ?? mantém 0, "" e false. Para trocá-los também, existe o ?:, que avisa se a variável não existe.
  • $a ??= $b guarda o padrão só no que está faltando.
  • $obj?->metodo() PHP 8.0+ devolve null em vez de dar Call to a member function … on null.
  • No meio de um texto com ., coloque o ?? entre parênteses.
  • Na web: $_GET["nome"] ?? "visitante", sempre com htmlspecialchars na saída.