PHP Operadores Lógicos
Uma comparação responde uma pergunta só: "a idade é maior que 18?". Na vida real, as perguntas são compostas: "é maior de idade e tem ingresso?", "pagou com Pix ou com cartão?", "não está logado?". Os operadores lógicos juntam várias respostas true/false numa só. Eles são a base de toda decisão que você vai escrever com if.
- Os três operadores principais:
&&(e),||(ou) e!(não), com as tabelas-verdade. - O
xor(ou exclusivo): "um ou outro, mas não os dois". - As palavras
andeor, e a armadilha delas com o=(com a demonstração real). - O curto-circuito: por que a ordem das perguntas evita um erro fatal.
- O que acontece quando os valores não são booleanos (no PHP, o resultado é sempre
trueoufalse).
Antes, é bom ter visto Booleanos (o que vale true e false) e Comparação (de onde vêm os true/false). Os resultados aparecem com o var_dump, que mostra bool(true) ou bool(false).
A ideia: as regras da portaria
A Ana trabalha na portaria de um show. Ela recebeu três regras:
- "Entra quem tem 18 anos e tem ingresso." As duas coisas precisam ser verdade.
- "Ganha meia-entrada quem é estudante ou tem 60 anos ou mais." Basta uma.
- "Quem não tem pulseira vai para a fila." Inverte a resposta.
Esses três "conectivos", e, ou e não, são os operadores lógicos. No PHP, eles se escrevem &&, || e !. O resultado de cada um é sempre true (verdadeiro) ou false (falso).
&&: "e" (as duas precisam ser verdade)
<?php $idade = 20; $temIngresso = true; var_dump($idade >= 18 && $temIngresso); var_dump($idade >= 18 && false); var_dump(true && true); var_dump(true && false); var_dump(false && true); var_dump(false && false);
$ php e.php bool(true) bool(false) bool(true) bool(false) bool(false) bool(false)
- $idade >= 18
- A primeira pergunta: "a idade é maior ou igual a 18?". Aqui,
true. - &&
- "E": o resultado só é
truese os dois lados foremtrue. Lê-se "e comercial e comercial", ou só "e". - $temIngresso
- A segunda pergunta. Uma variável booleana já é uma resposta pronta; não precisa escrever
$temIngresso == true.
As quatro últimas linhas formam a tabela-verdade do &&: todas as combinações possíveis. Só uma dá true:
| A | B | A && B |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
Um uso muito comum é perguntar se um valor está dentro de uma faixa: $idade >= 18 && $idade <= 60. O PHP não aceita 18 <= $idade <= 60, como a matemática escreve; a aula Precedência mostra o erro.
||: "ou" (basta uma ser verdade)
<?php $pagouPix = false; $pagouCartao = true; var_dump($pagouPix || $pagouCartao); var_dump(false || false);
$ php ou.php bool(true) bool(false)
O || (duas barras verticais; no teclado brasileiro, a barra | fica na tecla ao lado do Z, com Shift) dá true se pelo menos um lado for true. Só dá false quando os dois são false:
| A | B | A || B |
|---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
Repare: o "ou" do PHP inclui o caso dos dois. Se a pessoa pagou com Pix e com cartão, $pagouPix || $pagouCartao é true. Para "um ou outro, mas não os dois", existe o xor.
Missão: a meia-entrada vale para quem é estudante ou tem 60 anos ou mais. Complete a linha do $temDesconto usando $estudante, $idade e o ||. Com os valores do exemplo, o resultado deve ser bool(true). Depois mude a idade para 30 e veja virar false.
<?php $estudante = false; $idade = 65; $temDesconto = false; // troque false pela regra var_dump($temDesconto);
<?php $estudante = false; $idade = 65; $temDesconto = $estudante || $idade >= 60; var_dump($temDesconto);
!: "não" (inverte)
O ! (ponto de exclamação) vem antes de um valor e o inverte: true vira false e vice-versa.
<?php $logado = false; var_dump(!$logado); var_dump(!true); var_dump(!0); var_dump(!"Ana"); var_dump(!!"Ana");
$ php nao.php bool(true) bool(false) bool(true) bool(false) bool(true)
- !$logado
- "Não está logado?" Como
$logadoéfalse, a resposta étrue. - !0
- O
!funciona com qualquer valor. O PHP primeiro converte o valor para booleano (0valefalse, veja Booleanos) e depois inverte. - !"Ana"
- Um texto não vazio vale
true; invertido,false. - !!"Ana"
- Duas negações se anulam. O efeito é converter para booleano:
!!"Ana"é o mesmo que(bool) "Ana".
! gruda só no vizinho
!$nome == "Bia" não é "o nome não é Bia". O PHP entende (!$nome) == "Bia". Para negar uma comparação, use $nome !== "Bia" ou !($nome == "Bia"). A aula Precedência mostra a saída real.
Misturando && e ||
Quando os dois aparecem na mesma linha, o && é feito primeiro (como a multiplicação antes da soma). A regra da portaria mudou: "convidado VIP entra; os outros precisam ter 18 anos e ingresso". Veja a diferença que os parênteses fazem para um VIP de 16 anos sem ingresso:
<?php $convidadoVip = true; $idade = 16; $temIngresso = false; var_dump($convidadoVip || $idade >= 18 && $temIngresso); var_dump(($convidadoVip || $idade >= 18) && $temIngresso);
$ php vip.php bool(true) bool(false)
- $convidadoVip || $idade >= 18 && $temIngresso
- O PHP lê
$convidadoVip || ($idade >= 18 && $temIngresso): "é VIP, ou é maior e tem ingresso". VIP entra:true. - ($convidadoVip || $idade >= 18) && $temIngresso
- Outra regra: "(é VIP ou maior) e tem ingresso". Sem ingresso, ninguém entra:
false.
As duas regras fazem sentido; só você sabe qual é a certa. Por isso, sempre que misturar && e ||, escreva os parênteses, mesmo quando a ordem natural já é a que você quer: $convidadoVip || ($idade >= 18 && $temIngresso).
xor: "ou exclusivo" (um ou outro, não os dois)
xor vem do inglês exclusive or, "ou exclusivo". Ele dá true quando exatamente um dos lados é true. Se os dois forem iguais (ambos true ou ambos false), dá false.
<?php var_dump(true xor false); var_dump(false xor true); var_dump(true xor true); var_dump(false xor false); $pix = true; $cartao = true; var_dump($pix xor $cartao); $ok = $pix xor $cartao; var_dump($ok); $ok = ($pix xor $cartao); var_dump($ok); var_dump($pix != $cartao);
$ php xor.php bool(true) bool(true) bool(false) bool(false) bool(false) bool(true) bool(false) bool(false)
Exemplo prático: a lojinha da Ana aceita uma forma de pagamento por pedido. $pix xor $cartao é true se a pessoa escolheu exatamente uma; false se escolheu as duas ou nenhuma.
Mas repare na 6ª linha da saída: $ok = $pix xor $cartao; guardou true, embora a resposta certa seja false. O xor só existe em forma de palavra, e as palavras lógicas têm a armadilha da próxima seção. Com parênteses (7ª linha), funciona. Para booleanos, $pix != $cartao dá o mesmo resultado que o xor, sem armadilha.
| A | B | A xor B |
|---|---|---|
true | true | false |
true | false | true |
false | true | true |
false | false | false |
and e or: as palavras e a armadilha do =
O PHP também aceita as palavras and ("e") e or ("ou"), em letras minúsculas ou maiúsculas (AND, Or…). A conta é a mesma do && e do ||. A diferença está na prioridade: as palavras ficam no fim da tabela de precedência, abaixo do =. Veja o que isso causa:
<?php $temIngresso = true; $maiorDeIdade = false; $podeEntrar = $temIngresso and $maiorDeIdade; var_dump($podeEntrar); $podeEntrar = $temIngresso && $maiorDeIdade; var_dump($podeEntrar); $podeEntrar = ($temIngresso and $maiorDeIdade); var_dump($podeEntrar);
$ php and.php bool(true) bool(false) bool(false)
A pessoa não é maior de idade, e mesmo assim a primeira linha deixou entrar! Veja como o PHP leu cada uma:
| Você escreveu | O PHP entendeu | $podeEntrar |
|---|---|---|
$podeEntrar = $temIngresso and $maiorDeIdade; | ($podeEntrar = $temIngresso) and $maiorDeIdade; | true (errado) |
$podeEntrar = $temIngresso && $maiorDeIdade; | $podeEntrar = ($temIngresso && $maiorDeIdade); | false |
$podeEntrar = ($temIngresso and $maiorDeIdade); | os parênteses mandam | false |
Na primeira linha, o = foi feito antes: $podeEntrar recebeu só $temIngresso. O and foi calculado depois, e o resultado dele foi jogado fora. Com o or acontece o mesmo:
<?php $temCupom = false; $clienteVip = true; $desconto = $temCupom or $clienteVip; var_dump($desconto); $desconto = $temCupom || $clienteVip; var_dump($desconto);
$ php or.php bool(false) bool(true)
O PHP não mostra nenhum erro ou aviso nesses casos: as linhas são válidas. É um erro silencioso.
Então por que and e or existem?
Para um estilo antigo, que você ainda vai ver em tutoriais e código legado: "faça isto ou pare o programa".
<?php
$arquivo = fopen("nao-existe.txt", "r") or die("Não consegui abrir o arquivo.\n");
echo "Arquivo aberto!\n";
$ php die.php Warning: fopen(nao-existe.txt): Failed to open stream: No such file or directory in /home/ana/site/die.php on line 2 Não consegui abrir o arquivo.
Aqui a prioridade baixa do or é proposital: primeiro $arquivo = fopen(…) (abrir o arquivo, veja Ler e Escrever Arquivos); se o resultado for false, o or roda o die, que mostra a mensagem e encerra o programa. Hoje, o jeito recomendado de tratar falhas é com if ou exceções.
Use sempre &&, || e !. Deixe and e or para ler código antigo. Para xor, que só existe como palavra, use parênteses: $ok = ($a xor $b);.
Curto-circuito: o PHP para assim que sabe a resposta
Pense no &&: se o lado esquerdo já é false, o resultado vai ser false, não importa o lado direito. Então o PHP nem olha o lado direito. Com o ||, se o lado esquerdo já é true, o resultado é true, e o lado direito é pulado. Isso se chama curto-circuito (ou avaliação preguiçosa).
Dá para ver o curto-circuito com o print, que mostra um texto (veja echo e print) e pode ser usado no meio de uma expressão:
<?php
var_dump(false && print("lado direito do &&\n"));
var_dump(true && print("lado direito do &&\n"));
var_dump(true || print("lado direito do ||\n"));
var_dump(false || print("lado direito do ||\n"));
$ php curto.php bool(false) lado direito do && bool(true) bool(true) lado direito do || bool(true)
| Linha | O lado direito rodou? | Por quê |
|---|---|---|
false && print(…) | Não | Com false à esquerda, o "e" já é false. |
true && print(…) | Sim | O PHP precisa do lado direito para decidir. |
true || print(…) | Não | Com true à esquerda, o "ou" já é true. |
false || print(…) | Sim | O PHP precisa do lado direito para decidir. |
Para que serve: perguntar primeiro se é seguro
O curto-circuito permite colocar uma pergunta de proteção antes de uma conta perigosa. A Ana quer saber se o pedido é "grande" (mais de R$ 10 por item, em média), mas o carrinho pode estar vazio, e dividir por zero é um erro fatal:
<?php $itens = 0; $total = 50; var_dump($itens > 0 && $total / $itens > 10); var_dump($total / $itens > 10 && $itens > 0);
$ php divisao.php
bool(false)
Fatal error: Uncaught DivisionByZeroError: Division by zero in /home/ana/site/divisao.php:6
Stack trace:
#0 {main}
thrown in /home/ana/site/divisao.php on line 6
- $itens > 0 && $total / $itens > 10
- A proteção vem primeiro.
$itens > 0éfalse, então a divisão nunca acontece. Resultado:false, sem erro. - $total / $itens > 10 && $itens > 0
- As mesmas perguntas, na ordem errada. A divisão por zero acontece antes da proteção, e o programa para com
DivisionByZeroError("erro de divisão por zero").
O mesmo truque evita avisos com chaves que podem não existir num array:
<?php $aluna = ["nome" => "Ana"]; var_dump(isset($aluna["idade"]) && $aluna["idade"] >= 18); var_dump($aluna["idade"] >= 18 && isset($aluna["idade"]));
$ php chave.php bool(false) Warning: Undefined array key "idade" in /home/ana/site/chave.php on line 5 bool(false)
Com o isset ("está definido?") primeiro, a chave só é lida se existir. Na ordem inversa, o PHP lê a chave antes e avisa. (Para dar um valor padrão a uma chave que pode faltar, existe também o ??.)
Num &&, coloque à esquerda a pergunta que protege (existe? é maior que zero? não é null?) e à direita a que usa o valor. Bônus: se a pergunta da esquerda for mais rápida, o programa também fica mais rápido.
Com valores que não são booleanos
Os operadores lógicos aceitam qualquer valor: o PHP converte cada lado para booleano (as regras estão em Booleanos: 0, "", "0", null e [] valem false; o resto, true). E o resultado é sempre true ou false:
<?php
var_dump("Ana" && 0);
var_dump("Ana" || 0);
$nome = "";
var_dump($nome || "visitante");
var_dump($nome ?: "visitante");
$ php tipos.php bool(false) bool(true) bool(true) string(9) "visitante"
No JavaScript, nome || "visitante" devolve "visitante". No PHP, o || devolve bool(true), nunca o valor. Para "este valor ou um padrão", o PHP tem o ?: (quando o valor é vazio) e o ?? (quando é null ou não existe).
Laboratório livre: mude os valores das variáveis e preveja cada resultado antes de rodar. Experimente trocar && por and na linha do $podeEntrar.
<?php $idade = 17; $temIngresso = true; $convidadoVip = false; $podeEntrar = $convidadoVip || ($idade >= 18 && $temIngresso); var_dump($podeEntrar); var_dump(!$temIngresso); var_dump($idade >= 12 && $idade <= 17); var_dump(($convidadoVip xor $temIngresso));
Quando dá errado
true, mas a conta dava false
$ php and.php bool(true) bool(false) bool(false)
O que aconteceu: a linha usa and, or ou xor junto com =. Como o = é feito antes, a variável guardou só o primeiro valor.
Como resolver: troque por &&/||, ou coloque parênteses em volta da conta lógica.
$ php divisao.php
bool(false)
Fatal error: Uncaught DivisionByZeroError: Division by zero in /home/ana/site/divisao.php:6
Stack trace:
#0 {main}
thrown in /home/ana/site/divisao.php on line 6
O que aconteceu: a divisão veio antes da pergunta que a protegia. O curto-circuito só ajuda se a proteção estiver à esquerda.
Como resolver: inverta a ordem: $itens > 0 && $total / $itens > 10.
int(0) ou int(1) em vez de bool
$ php maiusc.php bool(false) bool(true) int(0) int(1)
O que aconteceu: as duas últimas linhas eram var_dump(true & false); e var_dump(true | false);, com um & e uma |. Esses são operadores de bits, outra coisa: devolvem números e não fazem curto-circuito. (As duas primeiras linhas mostram que AND e Or em maiúsculas funcionam como and e or.)
Como resolver: para lógica, sempre dois: && e ||.
isset na linha
$ php chave.php bool(false) Warning: Undefined array key "idade" in /home/ana/site/chave.php on line 5 bool(false)
O que aconteceu: o isset estava à direita do &&. A chave foi lida antes de ser verificada.
Como resolver: isset($aluna["idade"]) && $aluna["idade"] >= 18.
Referência
| Operador | Nome | Dá true quando… | Curto-circuito |
|---|---|---|---|
$a && $b | E | os dois são true | pula $b se $a for false |
$a || $b | Ou | pelo menos um é true | pula $b se $a for true |
!$a | Não | $a é false | — |
$a xor $b | Ou exclusivo | exatamente um é true | não (precisa dos dois) |
$a and $b | E (palavra) | igual ao &&, com prioridade abaixo do = | sim |
$a or $b | Ou (palavra) | igual ao ||, com prioridade abaixo do = | sim |
Prioridade, do mais forte para o mais fraco: ! → comparações → && → || → ?? → = → and → xor → or. A tabela completa está em Precedência.
Exercícios
1. As duas condições
Complete para dar true só se a pessoa tiver 18 anos ou mais e tiver ingresso:
2. Inverter
Complete para $visitante ser o contrário de $logado:
3. Proteger a divisão
Complete a proteção, à esquerda, para a divisão só acontecer quando $itens for maior que zero:
4. Depois de $ok = true and false;, o que var_dump($ok) mostra?
5. Quanto vale true xor true?
6. Em false && print("oi"), o que acontece?
7. $nome = ""; O que var_dump($nome || "visitante"); mostra no PHP?
Missão final: o pedido pode sair?
Missão: o programa tem dois problemas. (1) O pedido está pago mas sem estoque, então $podeEnviar deve ser bool(false); hoje sai true. (2) O carrinho está vazio e a divisão dá erro fatal: proteja-a com uma pergunta à esquerda do &&. No fim, o programa deve mostrar bool(false) duas vezes, sem erros.
<?php $pago = true; $temEstoque = false; $itens = 0; $total = 0; // 1. Só pode enviar se estiver pago E tiver estoque $podeEnviar = $pago and $temEstoque; var_dump($podeEnviar); // 2. Pedido grande = mais de R$ 10 por item, em média. // Proteja a divisão: só divida se $itens for maior que 0. $pedidoGrande = $total / $itens > 10; var_dump($pedidoGrande);
<?php $pago = true; $temEstoque = false; $itens = 0; $total = 0; // 1. Só pode enviar se estiver pago E tiver estoque $podeEnviar = $pago && $temEstoque; var_dump($podeEnviar); // 2. Pedido grande = mais de R$ 10 por item, em média. // Proteja a divisão: só divida se $itens for maior que 0. $pedidoGrande = $itens > 0 && $total / $itens > 10; var_dump($pedidoGrande);
Resumo
&&(e):truesó se os dois foremtrue.||(ou):truese pelo menos um for.!(não): inverte.xor:truese exatamente um fortrue. Use com parênteses.- O
&&vem antes do||. Ao misturar os dois, escreva os parênteses. and/orfazem a mesma conta, mas ficam abaixo do=:$x = true and falseguardatrue. Prefira&&/||.- Curto-circuito: o PHP para assim que sabe a resposta. Coloque a pergunta de proteção à esquerda.
- O resultado é sempre
bool, mesmo com textos e números. Um&ou|sozinho é outra coisa (bits).