As três parecem intercambiáveis e não são. Em uma linha cada: isset() responde "a variável existe e não é null?"; empty() responde "o valor é falsy (ou nem existe)?"; is_null() responde "o valor é exatamente null?". A diferença só machuca em valores de fronteira — e é sempre nos mesmos suspeitos: 0, '0' e a string vazia. Um formulário que usa empty() para validar a quantidade rejeita quem digitou 0; um código que usa isset() para saber se um campo veio na requisição erra quando o campo veio com valor null. Este artigo mostra a tabela completa, o mecanismo por trás e qual usar em cada situação.
O mecanismo: são duas perguntas diferentes
A confusão nasce de tratar como uma só coisa duas perguntas que não são a mesma:
- "Isso existe?" — a variável/chave foi definida? É território do
isset(). - "Isso tem conteúdo útil?" — o valor significa alguma coisa? É território do
empty().
isset() responde a primeira, com um detalhe: ele trata null como "não existe". Faz sentido dentro da lógica do PHP — null é literalmente "ausência de valor" —, mas é a origem de metade dos bugs, porque uma chave que existe com valor null faz o isset() devolver false.
empty() responde a segunda usando a mesma régua do if: ele converte o valor para booleano e devolve o inverso. Ou seja, empty($x) equivale a !isset($x) || !$x. Por isso ele considera "vazio" tudo que um if consideraria falso — inclusive coisas que o usuário digitou de propósito.
is_null() é a mais literal e a mais restrita: só devolve true para o valor null, exatamente igual a $x === null.
A tabela completa
Esta é a referência que resolve a dúvida na hora. Os valores foram verificados no PHP 8:
| Valor | isset() |
empty() |
is_null() |
if ($x) |
|---|---|---|---|---|
| variável não definida | false |
true |
true ⚠️ |
— |
null |
false |
true |
true |
false |
false |
true |
true |
false |
false |
0 |
true |
true |
false |
false |
0.0 |
true |
true |
false |
false |
'' (string vazia) |
true |
true |
false |
false |
'0' (string zero) |
true |
true |
false |
false |
' ' (um espaço) |
true |
false |
false |
true |
[] (array vazio) |
true |
true |
false |
false |
[0] |
true |
false |
false |
true |
'abc' |
true |
false |
false |
true |
'0.0' |
true |
false |
false |
true |
'false' |
true |
false |
false |
true |
⚠️ is_null() numa variável inexistente devolve true, mas emite Warning: Undefined variable — isset() e empty() não emitem nada.
Duas linhas merecem atenção especial. A '0' é a armadilha clássica (abaixo). E o par '0.0' vs '0' costuma surpreender: a string '0.0' é verdadeira, porque só a string '0' exata (e a vazia) são falsy — '0.0', '00' e ' ' são todas truthy.
// valida presença com empty
if (empty($_POST['quantidade'])) {
erro('Informe a quantidade');
}
// rejeita '0' (válido) e aceita ' ' (espaço, inválido)$qtd = $_POST['quantidade'] ?? '';
if (trim($qtd) === '') {
erro('Informe a quantidade'); // só string realmente vazia
} elseif (!is_numeric($qtd)) {
erro('A quantidade deve ser um número');
}
// '0' passa; ' ' é rejeitado; a mensagem é específicaArray: isset() mente quando o valor é null
Este é o segundo bug mais comum, e o motivo pelo qual array_key_exists() existe:
O array
Duas chaves definidas — uma com texto, outra com null. As duas existem no array.
$dados = ['nome' => 'Ana', 'apelido' => null];
isset() diz que a chave não existe
Como isset() trata null como ausência, ele devolve false para uma chave que está lá.
isset($dados['apelido']); // false ← mas a chave existe!
isset($dados['nome']); // true
array_key_exists() diz a verdade sobre a existência
Esta função responde só "a chave está no array?", ignorando o valor.
array_key_exists('apelido', $dados); // true ← correto
array_key_exists('sobrenome', $dados); // false
Quando a diferença importa de verdade
Numa atualização parcial (PATCH), "campo ausente" e "campo enviado como null" são intenções opostas: a primeira significa "não mexa neste campo", a segunda significa "apague este campo". Só array_key_exists() distingue as duas.
if (array_key_exists('apelido', $entrada)) {
$usuario->apelido = $entrada['apelido']; // pode ser null: limpar
}
// se a chave não veio, o campo fica intocado
A regra
Precisa saber se a chave existe? array_key_exists(). Precisa saber se tem valor utilizável? isset() (ou ??).
// existência real → array_key_exists
// valor utilizável → isset / ??
O caminho moderno: ?? no lugar de isset()
Em quase todo código novo, o isset() dentro de um if para definir um padrão foi substituído pelo operador null coalescing (??), que faz exatamente a mesma checagem — devolve o lado direito se o esquerdo não existir ou for null — sem emitir aviso:
// antes
$pagina = isset($_GET['pagina']) ? $_GET['pagina'] : 1;
// hoje
$pagina = $_GET['pagina'] ?? 1;
// encadeado: usa o primeiro que existir
$idioma = $_GET['lang'] ?? $_COOKIE['lang'] ?? 'pt_BR';
// atribuir só se ainda não tiver valor
$config['timeout'] ??= 30;
Isso importa por causa de um comportamento do PHP 8: acessar uma chave inexistente direto emite Warning: Undefined array key "x". O ?? silencia esse caso legitimamente — e sem a preguiça perigosa do @, que esconde todos os erros.
Uma API recebe ['nome' => 'Ana', 'bio' => null] e o código faz if (isset($dados['bio'])) { $perfil->bio = $dados['bio']; }. O usuário queria apagar a bio enviando null. O que acontece?
Ver resposta
Resposta certa: Nada acontece — a bio antiga permanece
isset($dados['bio']) devolve false quando o valor é null, mesmo com a chave presente no array. O if não entra e a bio antiga fica intacta — o usuário jura que apagou e o dado continua lá. É o caso que exige array_key_exists('bio', $dados), que separa "não enviou o campo" de "enviou o campo com null".
Resumo de decisão
| Você quer saber… | Use | Cuidado |
|---|---|---|
| Se a variável existe e tem valor utilizável | isset($x) |
Devolve false se o valor for null |
Se a chave existe no array (mesmo com null) |
array_key_exists() |
Só para arrays |
Se é exatamente null |
$x === null ou is_null($x) |
A variável precisa existir (senão, warning) |
| Definir um valor padrão | $x ?? 'padrão' |
Não use ?:, que engole 0 e '' |
| Se uma string tem conteúdo | trim($x) !== '' |
empty() rejeita '0' |
| Se um array tem itens | $arr === [] ou count($arr) === 0 |
empty() funciona aqui, mas seja explícito |
| Se um número foi informado | is_numeric($x) |
Nunca empty() em campo numérico |
A regra que evita 90% dos problemas: empty() só é seguro quando 0 e '' significam de fato "vazio" para o seu caso — o que raramente acontece com dados de formulário. Nas outras situações, escreva a condição que você realmente quer testar. Código explícito é mais longo e infinitamente mais fácil de depurar.
Os valores que o PHP considera falsos são a base de tudo o que o empty() faz. Entender a tabela de conversão para booleano (por que '0' é falso e '0.0' é verdadeiro) resolve a categoria inteira de bugs.
Chaves de array, valores null e a diferença entre "a chave não existe" e "a chave existe vazia" são conceitos de array — a página do tipo cobre as regras completas de acesso e existência.