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.

Você vai aprender
  • 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 and e or, 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 true ou false).

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ó é true se os dois lados forem true. 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:

ABA && B
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

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:

ABA || B
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

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 (0 vale false, 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".
O ! 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.

ABA xor B
truetruefalse
truefalsetrue
falsetruetrue
falsefalsefalse

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ê escreveuO 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 mandamfalse

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.

Regra prática

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)
LinhaO lado direito rodou?Por quê
false && print(…)NãoCom false à esquerda, o "e" já é false.
true && print(…)SimO PHP precisa do lado direito para decidir.
true || print(…)NãoCom true à esquerda, o "ou" já é true.
false || print(…)SimO 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 ??.)

Ordem das perguntas

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"
Para quem vem do JavaScript

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

A variável recebeu 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.

Fatal error: Uncaught DivisionByZeroError: Division by zero
$ 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.

Saiu 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 ||.

Warning: Undefined array key, mesmo com 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

OperadorNomeDá true quando…Curto-circuito
$a && $bEos dois são truepula $b se $a for false
$a || $bOupelo menos um é truepula $b se $a for true
!$aNão$a é false—
$a xor $bOu exclusivoexatamente um é truenão (precisa dos dois)
$a and $bE (palavra)igual ao &&, com prioridade abaixo do =sim
$a or $bOu (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:

$podeEntrar = $idade >= 18 $temIngresso;

2. Inverter

Complete para $visitante ser o contrário de $logado:

$visitante = ;

3. Proteger a divisão

Complete a proteção, à esquerda, para a divisão só acontecer quando $itens for maior que zero:

$grande = && $total / $itens > 10;

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): true só se os dois forem true. || (ou): true se pelo menos um for. ! (não): inverte.
  • xor: true se exatamente um for true. Use com parênteses.
  • O && vem antes do ||. Ao misturar os dois, escreva os parênteses.
  • and/or fazem a mesma conta, mas ficam abaixo do =: $x = true and false guarda true. 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).