PHP Precedência
Quando uma linha tem vários operadores, como 2 + 3 * 4, o PHP precisa decidir qual conta fazer primeiro. Essa ordem se chama precedência (do latim "ir na frente"). Na maior parte das vezes ela é igual à da matemática da escola, mas o PHP tem operadores que a escola não tem, como o ponto ., o ! e o and, e alguns deles têm surpresas. Esta aula mostra as surpresas reais e a regra que evita todas elas: use parênteses.
- O que é precedência e como o PHP coloca "parênteses invisíveis" nas suas contas.
- O que é associatividade: quem vai primeiro quando os operadores são do mesmo nível.
- Por que
1 + 2 . "3"dá"33"e"Média: " . 7 + 9 / 2não dá a média. - As armadilhas do
!, doand/ore do??. - Os erros que o PHP mostra quando a ordem fica ambígua (comparações em cadeia e ternário sem parênteses).
- A tabela completa de precedência, para consultar.
Esta aula fecha o grupo "Operadores". Ela usa operadores das aulas anteriores: aritméticos, de atribuição, de comparação, lógicos, de string e o ??. Para ver os resultados com o tipo, usamos o var_dump.
A ideia: parênteses invisíveis
Pense na calculadora do celular. Se você digitar 2 + 3 × 4, algumas calculadoras simples mostram 20 (fazem a conta na ordem em que você digitou: 2 + 3 = 5, vezes 4 = 20). A calculadora científica mostra 14, porque segue a regra da escola: multiplicação antes de soma.
O PHP é como a calculadora científica. Antes de calcular, ele lê a linha inteira e coloca parênteses invisíveis em volta das partes que vêm primeiro. Você escreve 2 + 3 * 4; o PHP entende 2 + (3 * 4).
Cada operador tem um "nível de prioridade". Quem tem prioridade mais alta "gruda" mais forte nos valores ao lado e é calculado antes. A lista desses níveis é a tabela de precedência, que está completa mais abaixo.
Contas: igual à escola
Com os operadores aritméticos, não há surpresa: *, / e % vêm antes de + e -. Os parênteses que você escreve vêm antes de tudo.
<?php var_dump(2 + 3 * 4); var_dump((2 + 3) * 4); var_dump(10 - 4 / 2); var_dump(10 % 3 * 2);
$ php precedencia.php int(14) int(20) int(8) int(2)
Veja como o PHP leu cada linha:
| Você escreveu | O PHP entendeu | Resultado | Por quê |
|---|---|---|---|
2 + 3 * 4 | 2 + (3 * 4) | 14 | * vem antes de + |
(2 + 3) * 4 | (2 + 3) * 4 | 20 | os seus parênteses mandam |
10 - 4 / 2 | 10 - (4 / 2) | 8 | / vem antes de - |
10 % 3 * 2 | (10 % 3) * 2 | 2 | % e * têm o mesmo nível: da esquerda para a direita (próxima seção) |
O erro mais comum: a média
A Ana quer a média de duas notas, 7 e 9. A resposta certa é 8. Ela escreveu assim:
<?php $nota1 = 7; $nota2 = 9; $media = $nota1 + $nota2 / 2; var_dump($media);
$ php media.php float(11.5)
O PHP não mostrou nenhum erro: ele fez exatamente o que está escrito. A divisão vem antes da soma, então ele calculou $nota1 + ($nota2 / 2), isto é, 7 + 4.5 = 11.5. Para somar primeiro, a soma precisa estar entre parênteses.
Missão: conserte a conta para o programa mostrar int(8), a média certa. Não troque a conta pelo número 8: use parênteses em volta de $nota1 + $nota2.
<?php $nota1 = 7; $nota2 = 9; $media = $nota1 + $nota2 / 2; var_dump($media);
<?php $nota1 = 7; $nota2 = 9; $media = ($nota1 + $nota2) / 2; var_dump($media);
Repare no tipo: 16 / 2 é uma divisão exata, então o resultado é o inteiro int(8). Na versão errada, 9 / 2 não é exata e o resultado virou float (veja int e float).
Associatividade: empate no mesmo nível
E quando os operadores têm o mesmo nível, como 10 - 5 - 2? Aí entra a associatividade: a regra que diz de que lado o PHP começa.
- Da esquerda para a direita (quase todos):
10 - 5 - 2é(10 - 5) - 2. - Da direita para a esquerda (a potência
**, as atribuições=,+=… e o??):2 ** 3 ** 2é2 ** (3 ** 2).
<?php var_dump(10 - 5 - 2); var_dump(100 / 10 / 5); var_dump(2 ** 3 ** 2); var_dump(-3 ** 2); var_dump((-3) ** 2);
$ php associatividade.php int(3) int(2) int(512) int(-9) int(9)
- 10 - 5 - 2 → 3
- Da esquerda:
(10 - 5) - 2=5 - 2=3. Se fosse da direita, daria10 - 3=7. - 100 / 10 / 5 → 2
- Da esquerda:
(100 / 10) / 5=10 / 5=2. - 2 ** 3 ** 2 → 512
- A potência vai da direita, como na matemática:
2 ** (3 ** 2)=2 ** 9=512. Da esquerda daria8 ** 2=64. - -3 ** 2 → -9
- A potência é mais forte que o sinal de menos. O PHP entende
-(3 ** 2)=-9. Também é assim na matemática: −3² = −9. - (-3) ** 2 → 9
- Para elevar o número negativo, coloque-o entre parênteses.
As atribuições irem da direita para a esquerda é o que faz $a = $b = 0; funcionar: o PHP entende $a = ($b = 0), guarda 0 em $b e depois o mesmo valor em $a (veja Operadores de Atribuição).
Surpresa 1: o ponto . e as contas PHP 8.0+
O ponto . junta textos (veja Operadores de String). Desde o PHP 8.0, ele tem prioridade menor que + e -. Ou seja: o PHP faz as contas primeiro e só depois junta os textos.
<?php $nota1 = 7; $nota2 = 9; echo "Soma: " . $nota1 + $nota2 . "\n"; echo "Média: " . $nota1 + $nota2 / 2 . "\n"; echo "Média: " . ($nota1 + $nota2) / 2 . "\n"; var_dump(1 + 2 . "3");
$ php concatenar.php Soma: 16 Média: 11.5 Média: 8 string(2) "33"
| Você escreveu | O PHP entendeu | Resultado |
|---|---|---|
"Soma: " . $nota1 + $nota2 . "\n" | "Soma: " . ($nota1 + $nota2) . "\n" | Soma: 16 (certo) |
"Média: " . $nota1 + $nota2 / 2 . "\n" | "Média: " . ($nota1 + ($nota2 / 2)) . "\n" | Média: 11.5 (errado: é o problema da média de novo) |
"Média: " . ($nota1 + $nota2) / 2 . "\n" | "Média: " . (($nota1 + $nota2) / 2) . "\n" | Média: 8 (certo) |
1 + 2 . "3" | (1 + 2) . "3" | "33": o 3 da soma colado no texto "3" |
Até o PHP 7, o ., o + e o - tinham o mesmo nível e eram feitos da esquerda para a direita. Por isso "Soma: " . 7 + 9 virava ("Soma: " . 7) + 9, uma soma com um texto, e dava resultado errado. Se você encontrar código antigo cheio de parênteses em volta das contas, é por isso. No PHP 8.5, que este guia ensina, as contas vão primeiro. Mesmo assim, os parênteses deixam a intenção clara para qualquer pessoa (e qualquer versão).
Surpresa 2: o ! é mais forte que a comparação
O ! ("não", da aula Operadores Lógicos) tem prioridade maior que ==. Ele "gruda" só no valor logo à direita, não na comparação inteira. A Ana quer saber se o nome não é "Bia":
<?php $nome = "Ana"; var_dump(!$nome == "Bia"); var_dump(!($nome == "Bia")); var_dump($nome !== "Bia");
$ php nao.php bool(false) bool(true) bool(true)
- !$nome == "Bia"
- O PHP entende
(!$nome) == "Bia".!"Ana"éfalse(um texto não vazio valetrue; o "não" dele éfalse). Depois comparafalse == "Bia", que éfalse. A pergunta que o PHP respondeu não foi a que a Ana fez. - !($nome == "Bia")
- Com parênteses: primeiro
"Ana" == "Bia"(false), depois o "não":true. Certo. - $nome !== "Bia"
- A forma mais clara: use o operador "não é idêntico" (veja Comparação). Nada de
!solto.
Surpresa 3: and e or perdem para o =
O PHP tem duas formas de escrever "e" e "ou": &&/|| e as palavras and/or. Elas fazem a mesma conta, mas não têm a mesma prioridade. As palavras and e or estão no fim da tabela, abaixo até da atribuição =. O resultado é uma armadilha clássica:
<?php $temIngresso = true; $maiorDeIdade = false; $podeEntrar = $temIngresso and $maiorDeIdade; var_dump($podeEntrar); $podeEntrar = $temIngresso && $maiorDeIdade; var_dump($podeEntrar);
$ php ingresso.php bool(true) bool(false)
A Ana não é maior de idade, e mesmo assim a primeira linha guardou true! Veja os parênteses invisíveis:
| Você escreveu | O PHP entendeu | Valor guardado |
|---|---|---|
$podeEntrar = $temIngresso and $maiorDeIdade; | ($podeEntrar = $temIngresso) and $maiorDeIdade; | true: o = foi feito primeiro, e o and ficou "sobrando", sem guardar nada |
$podeEntrar = $temIngresso && $maiorDeIdade; | $podeEntrar = ($temIngresso && $maiorDeIdade); | false: certo |
O PHP não mostra erro nem aviso: as duas linhas são válidas. Por isso esse problema é tão traiçoeiro. A regra prática: use sempre && e ||. A aula Operadores Lógicos mostra mais detalhes.
Entre os dois lógicos "de símbolo", && vem antes de || (como a multiplicação antes da soma). Então true || false && false é true || (false && false), que dá true. Se a sua ideia era (true || false) && false, escreva os parênteses.
Surpresa 4: o ?? está abaixo do ponto
O ?? (da aula ?? e ?->) quer dizer "use o valor da esquerda; se ele for null, use o da direita". Ele tem prioridade menor que o .. Veja o que acontece quando a Ana quer cumprimentar um visitante sem nome:
<?php $nome = null; echo "Olá, " . $nome ?? "visitante"; echo "\n"; echo "Olá, " . ($nome ?? "visitante"); echo "\n";
$ php visitante.php Olá, Olá, visitante
Na primeira linha, o PHP entendeu ("Olá, " . $nome) ?? "visitante". Juntar "Olá, " com null dá o texto "Olá, ", que não é null; então o ?? nunca usa o "visitante". Com parênteses em volta do ??, funciona.
Laboratório livre: rode, depois tire e coloque parênteses e veja os resultados mudarem. Tente prever cada resultado antes de clicar em Rodar.
<?php
var_dump(2 + 3 * 4);
var_dump(2 ** 3 ** 2);
var_dump(-2 ** 2);
var_dump(1 + 2 . "3");
var_dump(true || false && false);
$x = true and false;
var_dump($x);
$nome = null;
var_dump("Olá, " . ($nome ?? "visitante"));
Quando a ordem é ambígua, o PHP recusa
Em alguns casos, o PHP não escolhe uma ordem: ele para com um erro e pede que você decida. Isso é bom, porque o erro aparece na hora, em vez de uma conta errada silenciosa.
Comparações em cadeia
Na matemática se escreve 18 ≤ idade ≤ 60. No PHP, não dá:
<?php $idade = 20; var_dump(18 <= $idade <= 60);
$ php idade.php Parse error: syntax error, unexpected token "<=" in /home/ana/site/idade.php on line 3
Os operadores de comparação são não associativos: o PHP não aceita dois deles lado a lado sem parênteses. É um Parse error ("erro de leitura"): o PHP nem começa a rodar o programa. Para perguntar "está entre 18 e 60?", junte duas comparações com &&:
<?php $idade = 20; var_dump(18 <= $idade && $idade <= 60);
$ php idade2.php bool(true)
Aqui não precisa de parênteses: <= tem prioridade maior que &&, então o PHP faz as duas comparações e depois o "e".
Ternário dentro de ternário
O ternário condição ? valor1 : valor2 é ensinado na aula Operador Ternário. Colocar um dentro do outro sem parênteses é proibido desde o PHP 8.0:
<?php $nota = 7; echo $nota >= 7 ? "aprovada" : $nota >= 5 ? "recuperação" : "reprovada";
$ php nota.php
Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` in /home/ana/site/nota.php on line 3
Stack trace:
#0 {main}
A mensagem já diz como consertar: escolha onde vão os parênteses. Aqui, a intenção é "se não for aprovada, veja se é recuperação", então os parênteses ficam em volta do segundo ternário:
<?php $nota = 7; echo $nota >= 7 ? "aprovada" : ($nota >= 5 ? "recuperação" : "reprovada"); echo "\n";
$ php nota2.php aprovada
A tabela de precedência
Do mais forte (feito primeiro) para o mais fraco (feito por último). Operadores na mesma linha têm o mesmo nível; a coluna "Empate" diz a associatividade. Você não precisa decorar: consulte quando estiver em dúvida, e na dúvida, use parênteses.
| Nível | Operadores | O que são | Empate |
|---|---|---|---|
| 1 | clone new | Objetos (Nível 3) | — |
| 2 | ** | Potência | direita |
| 3 | ++ -- - (sinal) + (sinal) ~ (int) (float) (string) (bool) (array) @ | Incremento, sinal, conversão de tipo, silenciar erro | — |
| 4 | instanceof | "É um objeto desta classe?" (Nível 3) | esquerda |
| 5 | ! | Não lógico | — |
| 6 | * / % | Multiplicação, divisão, resto | esquerda |
| 7 | + - | Soma e subtração | esquerda |
| 8 | << >> | Deslocamento de bits (raro) | esquerda |
| 9 | . | Concatenação (desde o PHP 8.0) | esquerda |
| 10 | < <= > >= | Maior e menor | não aceita empate |
| 11 | == != === !== <> <=> | Igualdade e nave espacial | não aceita empate |
| 12 | & | "E" de bits (raro) | esquerda |
| 13 | ^ | "Ou exclusivo" de bits (raro) | esquerda |
| 14 | | | "Ou" de bits (raro) | esquerda |
| 15 | && | E lógico | esquerda |
| 16 | || | Ou lógico | esquerda |
| 17 | ?? | Coalescência nula | direita |
| 18 | ? : | Ternário | não aceita empate |
| 19 | = += -= *= /= .= ??= … | Atribuição | direita |
| 20 | yield print | Geradores e print | — |
| 21 | and | E lógico (palavra) | esquerda |
| 22 | xor | Ou exclusivo lógico | esquerda |
| 23 | or | Ou lógico (palavra) | esquerda |
Três "atalhos" para lembrar o que importa no dia a dia:
- Contas, depois texto, depois comparações, depois lógica, depois atribuição. É por isso que
$ok = $preco * 2 > 10 && $nome != "";funciona sem parênteses. !gruda só no vizinho. Para negar uma comparação, use!=/!==ou parênteses.and,orexorficam abaixo do=. Prefira&&e||.
A regra de ouro: use parênteses
Programadores experientes também não decoram a tabela inteira. Eles colocam parênteses sempre que a ordem não é óbvia. Os parênteses:
- não deixam o programa mais lento;
- mostram a sua intenção para quem lê (inclusive você, daqui a um mês);
- funcionam igual em qualquer versão do PHP;
- evitam as surpresas desta aula.
| Situação | Em vez de | Escreva |
|---|---|---|
| Média, porcentagem, conta com soma e divisão | $a + $b / 2 | ($a + $b) / 2 |
| Conta no meio de um texto | "Total: " . $preco * $qtd | "Total: " . ($preco * $qtd) |
| Negar uma comparação | !$nome == "Bia" | $nome !== "Bia" |
| Guardar o resultado de um "e"/"ou" | $ok = $a and $b; | $ok = $a && $b; |
?? junto com outros operadores | "Olá, " . $nome ?? "visitante" | "Olá, " . ($nome ?? "visitante") |
| Potência de número negativo | -3 ** 2 | (-3) ** 2 |
Misturar && e || | $a || $b && $c | $a || ($b && $c) |
((($a) + ($b))) não ajuda ninguém. Use parênteses onde a ordem muda o resultado ou onde um leitor poderia ficar em dúvida. $total = $preco * $qtd; não precisa de nenhum.
Quando dá errado
$ php media.php float(11.5)
O que aconteceu: o PHP fez as contas na ordem da tabela, não na ordem que você imaginou. $nota1 + $nota2 / 2 divide primeiro.
Como resolver: escreva os parênteses que estavam "na sua cabeça": ($nota1 + $nota2) / 2. Para descobrir onde está o problema, mostre cada pedaço com var_dump.
true quando deveria ser false
$ php ingresso.php bool(true) bool(false)
O que aconteceu: a linha usa and ou or junto com =. Como o = é mais forte, a variável recebeu só o primeiro valor.
Como resolver: troque and por && e or por ||.
$ php idade.php Parse error: syntax error, unexpected token "<=" in /home/ana/site/idade.php on line 3
O que aconteceu: você encadeou duas comparações, como 18 <= $idade <= 60. O PHP não aceita. O mesmo vale para ==: $a == $b == $c dá unexpected token "==".
Como resolver: separe em duas comparações ligadas por &&: 18 <= $idade && $idade <= 60.
$ php nota.php
Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` in /home/ana/site/nota.php on line 3
Stack trace:
#0 {main}
O que aconteceu: há um ternário dentro de outro, sem parênteses. Antes do PHP 8.0 isso era aceito e quase sempre fazia o contrário do esperado; hoje é proibido.
Como resolver: coloque parênteses em volta do ternário de dentro, como a própria mensagem sugere. Se ficar complicado, troque por if/elseif ou match.
?? nunca usa o valor padrão
$ php visitante.php Olá, Olá, visitante
O que aconteceu: o . foi feito antes do ??. O texto juntado nunca é null.
Como resolver: parênteses em volta do ??: "Olá, " . ($nome ?? "visitante").
Referência rápida
| Expressão | O PHP lê como | Resultado |
|---|---|---|
2 + 3 * 4 | 2 + (3 * 4) | 14 |
10 - 5 - 2 | (10 - 5) - 2 | 3 |
2 ** 3 ** 2 | 2 ** (3 ** 2) | 512 |
-3 ** 2 | -(3 ** 2) | -9 |
1 + 2 . "3" | (1 + 2) . "3" | "33" |
!$a == $b | (!$a) == $b | quase nunca o que você quer |
$a || $b && $c | $a || ($b && $c) | — |
$x = true and false | ($x = true) and false | $x vale true |
"a" . $n ?? "b" | ("a" . $n) ?? "b" | nunca usa "b" |
$a = $b = 0 | $a = ($b = 0) | as duas valem 0 |
1 < $x < 5 | — | Parse error |
Exercícios
1. Mudar a ordem
Complete com parênteses para o resultado ser int(20), e não int(14):
2. Porcentagem
A Ana quer 10% de desconto sobre $preco + $frete. Complete a conta:
3. O "e" certo
Complete com o operador que faz $ok receber o resultado da conta inteira:
4. Negar a comparação
Complete com um operador de comparação (sem usar ! sozinho) para perguntar "o nome não é idêntico a Bia?":
5. O que var_dump(1 + 2 . "3"); mostra no PHP 8.5?
6. Depois de $x = true and false;, quanto vale $x?
7. Quanto é -2 ** 2 no PHP?
8. O que acontece com var_dump(0 < $x < 10);?
Missão final: o pedido da lojinha
Missão: o programa da lojinha da Ana tem dois erros de precedência. (1) O desconto de 10% deve valer sobre tudo (produtos + frete): o total certo é R$ 63. (2) O pedido só pode ser enviado se estiver pago e com o endereço certo; como o endereço está errado, $podeEnviar deve ser bool(false). Conserte os dois.
<?php $preco = 20; $quantidade = 3; $frete = 10; $pago = true; $enderecoOk = false; // 10% de desconto em tudo (produtos + frete) $total = $preco * $quantidade + $frete * 0.9; echo "Total: R$ " . $total . "\n"; $podeEnviar = $pago and $enderecoOk; var_dump($podeEnviar);
<?php $preco = 20; $quantidade = 3; $frete = 10; $pago = true; $enderecoOk = false; // 10% de desconto em tudo (produtos + frete) $total = ($preco * $quantidade + $frete) * 0.9; echo "Total: R$ " . $total . "\n"; $podeEnviar = $pago && $enderecoOk; var_dump($podeEnviar);
Resumo
- Precedência é a ordem em que o PHP calcula os operadores: ele coloca "parênteses invisíveis" antes de calcular.
- Contas seguem a escola:
**, depois* / %, depois+ -. - Associatividade decide os empates: quase tudo vai da esquerda;
**,=e??vão da direita. - Desde o PHP 8.0, o
.fica abaixo de+e-: as contas são feitas antes de juntar o texto. !gruda só no vizinho:!$a == $bé(!$a) == $b. Use!==.and/orficam abaixo do=:$x = true and falseguardatrue. Use&&/||.- Comparações em cadeia dão
Parse error; ternário dentro de ternário sem parênteses dáFatal error. - A regra de ouro: na dúvida, parênteses.