&& e and produzem exatamente a mesma lógica — os dois são "E". A diferença está na precedência: and e or têm prioridade menor que o =, enquanto && e || têm prioridade maior. A consequência é esta, e ela é real:
$r1 = true and false;
var_dump($r1); // bool(true) ← ?!
$r2 = true && false;
var_dump($r2); // bool(false) ← o esperado
A primeira linha não guarda o resultado do "E" — ela guarda true. O PHP não emite aviso nenhum, o código roda, e a condição que depende de $r1 toma a decisão errada. A regra prática é curta: use sempre && e ||. Mas vale entender por que isso acontece, onde a pegadinha morde (é mais restrito do que se costuma dizer) e por que essa precedência estranha existe de propósito.
O mecanismo: onde cada operador fica na fila
Precedência é a ordem em que o PHP agrupa as partes de uma expressão — o mesmo princípio de 2 + 3 * 4 valer 14 e não 20, porque * vem antes de +. A tabela do PHP, do mais forte para o mais fraco, tem esta fatia relevante:
! ← negação (bem no topo)
* / %
+ -
< > <= >=
== != === !==
&& ← E "forte"
|| ← OU "forte"
??
? :
= += -= ... ← ATRIBUIÇÃO
and ← E "fraco"
xor
or ← OU "fraco"
Repare que o = fica entre os dois grupos. É isso e nada mais. Quando o PHP encontra $r = true and false, ele agrupa primeiro o que é mais forte — e o = é mais forte que o and:
O que você escreveu
Sua intenção era: calcular true and false e guardar o resultado.
$r = true and false;
Como o PHP agrupa
Como = tem precedência maior que and, a atribuição é resolvida primeiro. É como se houvesse parênteses invisíveis aqui:
($r = true) and false;
O que acontece na prática
$r recebe true. Só depois o and compara o resultado da atribuição (true) com false — e joga esse resultado fora, porque ninguém o guarda.
$r = true; // a atribuição aconteceu
true and false; // calculado e descartado
var_dump($r); // bool(true)
Com && o agrupamento é o oposto
&& tem precedência maior que =, então a lógica é resolvida primeiro e o resultado é atribuído.
$r = (true && false); // parênteses invisíveis aqui
var_dump($r); // bool(false)
O conserto quando você quer manter o and
Parênteses explícitos resolvem, porque forçam a ordem. Mas se você precisa de parênteses para o código ficar correto, mais simples é usar &&.
$r = (true and false); // bool(false) — funciona
$r = true && false; // bool(false) — melhor
O mesmo vale para or, com um resultado igualmente surpreendente:
$r = false or true;
var_dump($r); // bool(false) ← agrupou como ($r = false) or true
$temPermissao = $usuario->ativo and $usuario->admin;
// vira: ($temPermissao = $usuario->ativo) and $usuario->admin
// $temPermissao só reflete "ativo" — o admin é ignorado!
if ($temPermissao) {
liberarPainelAdmin(); // usuário comum ativo entra
}$temPermissao = $usuario->ativo && $usuario->admin;
// agora sim: as duas condições contam
if ($temPermissao) {
liberarPainelAdmin();
}Onde a pegadinha não morde
Aqui está a parte que quase todo artigo sobre o assunto erra — e que evita você sair "consertando" código correto. O problema só aparece quando há um operador de precedência intermediária entre os dois lados, e na prática isso significa: atribuição. Nestes dois lugares, and e && são intercambiáveis:
// 1. dentro de um if — equivalentes
if ($a and $b) { } // idêntico a
if ($a && $b) { } // este
// 2. em um return — equivalentes
function f(): bool { return true and false; } // devolve false
function g(): bool { return true && false; } // devolve false
No if, não existe atribuição competindo pela precedência: a condição inteira é avaliada. No return, a instrução recebe a expressão completa — return não é um operador que dispute a fila, então true and false é calculado inteiro antes de voltar. Testado nas duas formas: o resultado é false em ambas.
Ou seja: se você encontrar if ($a and $b) num código legado, ele está correto — só não é o estilo recomendado. O que precisa de revisão urgente é $x = ... and ... e $x = ... or ....
Por que essa precedência esquisita existe?
Não é acidente nem herança mal resolvida: é um recurso deliberado, e conhecê-lo faz o resto fazer sentido. A precedência baixa do or permite o idioma clássico de tratamento de erro, herdado do Perl:
$recurso = abrirArquivo('dados.txt') or die('Falhou ao abrir');
Aqui o comportamento "estranho" é exatamente o desejado: $recurso recebe o valor útil retornado pela função (o handle do arquivo), e o or die(...) só dispara se esse valor for falsy. Com || o resultado seria outro — e inútil:
$recurso = abrirArquivo('dados.txt') or die('...'); // $recurso = handle
$recurso = abrirArquivo('dados.txt') || die('...'); // $recurso = true (!)
Com ||, a expressão lógica inteira é resolvida antes da atribuição, e o que sobra é um booleano — você perde o handle e fica com um true inútil. Esse é o único caso em que a precedência baixa trabalha a seu favor, e mesmo assim o idioma é considerado datado hoje: com exceções e try/catch, o tratamento de erro moderno é mais explícito. Mas ele explica por que os dois conjuntos de operadores existem lado a lado.
O que este código imprime?
$ativo = true;
$admin = false;
$acesso = $ativo and $admin;
var_dump($acesso);
Ver resposta
Resposta certa: bool(true) — $acesso recebe só o valor de $ativo
Como = tem precedência maior que and, o PHP agrupa como ($acesso = $ativo) and $admin. O $acesso recebe true (o valor de $ativo) e o and com $admin é calculado e descartado. Num sistema real, isso é uma falha de autorização: um usuário ativo, mas não administrador, passa na checagem. Trocar and por && corrige — e é por isso que a recomendação é usar &&/|| sempre.
Diagnóstico: caçar o padrão no código existente
Se você suspeita que esse bug já está no seu projeto, o alvo é bem específico — uma atribuição com and/or do lado direito:
grep -rnE '=[^=]+\b(and|or)\b' --include='*.php' .
O comando gera falsos positivos (strings, comentários, nomes com "and"), mas a lista é curta e a inspeção é rápida. Para cada ocorrência, o teste mental é único: envolva o lado direito em parênteses e veja se o resultado muda. Se mudar, você achou um bug real:
$x = $a and $b; // valor atual
$x = ($a and $b); // valor correto — se diferem, o original está errado
Ferramentas de análise estática (PHPStan, Psalm) e o PHP_CodeSniffer com o padrão PSR-12 apontam esse caso automaticamente — vale ligar um deles no projeto e nunca mais pensar no assunto.
Resumo de decisão
| Situação | Comportamento | Recomendação |
|---|---|---|
$x = $a && $b; |
Lógica primeiro, depois atribui | ✅ Use sempre |
$x = $a and $b; |
Atribui $a, depois calcula e descarta |
❌ Bug silencioso |
if ($a and $b) |
Idêntico a && |
Correto, mas prefira && |
return $a and $b; |
Idêntico a && |
Correto, mas prefira && |
$x = ($a and $b); |
Parênteses corrigem | Funciona — && é mais simples |
$r = f() or die(); |
Guarda o valor de f() |
Idioma antigo; hoje, exceções |
&& e or na mesma expressão |
Resultado imprevisível | Reescreva |
Em uma frase: and/or ficam abaixo do = na fila, &&/|| ficam acima — e como só a atribuição fica no meio, é lá (e só lá) que a escolha errada vira bug.
A página dos operadores lógicos mostra os dois conjuntos lado a lado (&&/and, ||/or, xor) e por que a linguagem mantém as duas formas — inclusive o comportamento de curto-circuito, que vale para ambas.
A tabela oficial de precedência é a referência que resolve qualquer dúvida desse tipo, não só a de and vs &&. Vale marcar: ela explica também ??, ? : e a associatividade de cada operador.