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.

Você vai aprender
  • 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 / 2 não dá a média.
  • As armadilhas do !, do and/or e 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ê escreveuO PHP entendeuResultadoPor quê
2 + 3 * 42 + (3 * 4)14* vem antes de +
(2 + 3) * 4(2 + 3) * 420os seus parênteses mandam
10 - 4 / 210 - (4 / 2)8/ vem antes de -
10 % 3 * 2(10 % 3) * 22% 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, daria 10 - 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 daria 8 ** 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ê escreveuO PHP entendeuResultado
"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"
Antes do PHP 8.0 era diferente

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 vale true; o "não" dele é false). Depois compara false == "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ê escreveuO PHP entendeuValor 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ívelOperadoresO que sãoEmpate
1clone newObjetos (Nível 3)—
2**Potênciadireita
3++ -- - (sinal) + (sinal) ~ (int) (float) (string) (bool) (array) @Incremento, sinal, conversão de tipo, silenciar erro—
4instanceof"É um objeto desta classe?" (Nível 3)esquerda
5!Não lógico—
6* / %Multiplicação, divisão, restoesquerda
7+ -Soma e subtraçãoesquerda
8<< >>Deslocamento de bits (raro)esquerda
9.Concatenação (desde o PHP 8.0)esquerda
10< <= > >=Maior e menornão aceita empate
11== != === !== <> <=>Igualdade e nave espacialnã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ógicoesquerda
16||Ou lógicoesquerda
17??Coalescência nuladireita
18? :Ternárionão aceita empate
19= += -= *= /= .= ??= …Atribuiçãodireita
20yield printGeradores e print—
21andE lógico (palavra)esquerda
22xorOu exclusivo lógicoesquerda
23orOu lógico (palavra)esquerda

Três "atalhos" para lembrar o que importa no dia a dia:

  1. Contas, depois texto, depois comparações, depois lógica, depois atribuição. É por isso que $ok = $preco * 2 > 10 && $nome != ""; funciona sem parênteses.
  2. ! gruda só no vizinho. Para negar uma comparação, use !=/!== ou parênteses.
  3. and, or e xor ficam 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çãoEm vez deEscreva
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)
Parênteses demais também atrapalham

((($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

A conta dá um número estranho, sem nenhum erro
$ 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.

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

Parse error: syntax error, unexpected token "<="
$ 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.

Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported
$ 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.

O ?? 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ãoO PHP lê comoResultado
2 + 3 * 42 + (3 * 4)14
10 - 5 - 2(10 - 5) - 23
2 ** 3 ** 22 ** (3 ** 2)512
-3 ** 2-(3 ** 2)-9
1 + 2 . "3"(1 + 2) . "3""33"
!$a == $b(!$a) == $bquase 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):

var_dump( * 4);

2. Porcentagem

A Ana quer 10% de desconto sobre $preco + $frete. Complete a conta:

$total = * 0.9;

3. O "e" certo

Complete com o operador que faz $ok receber o resultado da conta inteira:

$ok = $pago $enderecoOk;

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?":

var_dump($nome "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/or ficam abaixo do =: $x = true and false guarda true. 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.