PHP para Iniciantes: Operadores Lógicos

Como eu já falei sobre os Operadores Bitwise, vou presumir que você já entende 90% do conteúdo mesmo se nunca tiver lido sobre isso em outro lugar.

Eu falo dessa forma porque é preciso entender operadores lógicos para trabalhar com bits, ao menos a lógica da álgebra booleana. Na computação quântica, tudo fica meio diferente, mas pode ficar tranquilo(a) pois nem vou tocar nesse assunto.

O lance é que eu mencionei tabela da verdade naquele artigo e isso é a base para compreender as operações lógicas que vamos falar hoje.

Então você vai reescrever as tabelas da verdade, Kiko?

Sim.

Porém, diferente dos operadores bitwise, os operadores de hoje só trabalham com booleanos. Não significa que você está sendo forçado a converter os dados antes de utilizá-los, mas com certeza o interpretador vai tentar fazer essa transformação.

Dito isso, primeiro eu vou fazer um resumo sobre casting de outros tipos de dados primitivos para boolean, pois sei que essa informação ficou bem espalhada ao longo dos artigos, e depois vou mostrar os operadores destacando suas possibilidades com uma tabela da verdade. Vamos começar?

Casting para booleano

Se você já conhece os termos truthy ou falsy, você provavelmente já entende do que irei falar nesta seção. Se for o caso, sinta-se a vontade para saltar para a próxima, ok?

Enfim, muitas vezes precisamos constatar fatos na lógica de programação, sempre almejando ter a maior precisão possível. Estes fatos devem ser destacados de tal forma que você não poderia contradizê-los.

Falando dessa forma, significa que uma afirmação na lógica ou é verdade ou é falsa. Não existe meio termo. Isso é o que representamos como um booleano.

Na maioria das vezes, os booleanos que criamos destacam uma afirmação que perguntamos, sendo facilmente legível para saber o que estamos tentando validar. Mas e quando, ao invés de uma pergunta, temos apenas uma informação?

Por exemplo, qual é o valor booleano da string 'Kaique'? Por que raios você quer saber se 'Kaique' é verdadeiro ou falso? É exatamente essa a sensação que dá quando você converte um dado para booleano sem armazenar em uma variável clara, retirando a total semântica sem contexto algum.

Continuando com o exemplo, se, ao invés de somente perguntar se é verdade ou falso, eu armazenasse $nome = 'Kaique' e depois $temNome = boolval($nome), faria mais sentido?

Provavelmente sim. O fato é que tudo depende do contexto e de qual verdade você busca no seu código. Os castings não estão aí só de enfeite, obviamente.

Tá, Kiko, acho que entendi... Mas quais são as conversões que você tava mencionando?

É bem simples! Vou escrever uma tabelinha abaixo com os tipos de dados primitivos, com exceção do booleano que já é booleano:

TipoValores FalsosValores Verdadeiros
int0qualquer outro valor
float0.0qualquer outro valor
string'' (texto vazio) ou '0' (numérico int 0)qualquer outro valor, incluindo o '0.0' (numérico float 0.0)
array[] (array vazio)qualquer outro valor
objectnunca, se realmente for um objetoqualquer valor
callablenunca, se realmente for um acionávelqualquer valor
iterablenunca, se realmente for um iterávelqualquer valor
resourcenunca, se realmente for um recursoqualquer valor
nullqualquer valornunca, se realmente for nulo

Caraca, Kiko, por que o texto numérico '0.0'true?! Sendo que o ponto flutuante 0.0 dá false...

Essa é uma boa pergunta e um dos maiores erros de design do PHP. Você pode até dar uma boa pesquisada sobre isso, tem muitas discussões fervorosas sobre o assunto e o ponto é: por que o interpretador aceita que '0' seja falso, mas não '0.0'? O bom senso pode nos levar ao engano aqui. O ideal era que somente a string vazia fosse false, mas criaram uma exceção para o texto numérico '0', gerando o CAOS.

XABLAU

E se aceita o texto numérico '0', há quem argumente que também poderia ter aceitado os booleanos escritos 'false' ou 'true', porém eu discordo desse ponto. Afinal, nós realmente temos um conceito de texto numérico. Nunca existiu texto booleano na linguagem, não faria sentido armazenar booleanos como strings.

Conclusão do rolê: muito cuidado ao lidar com texto numérico e casting para booleano. Pode dar muito ruim.

Agora que você sabe quando cada tipo de dado é false ou true, vamos seguir para os operadores com exemplos variados. Bora lá?


Operador AND ou &&

Começando com a tabela da verdade:

$a$b$a && $b
falsefalsefalse
falsetruefalse
truefalsefalse
truetruetrue

Podemos destacar apenas a última linha da tabela, que é o único momento em que o resultado da operação dá true: quando tanto o primeiro quanto o segundo dado são true.

Semanticamente falando, $a && $b significa "$a e $b são verdade", por isso que permanece daquela forma.

Kiko, não seria mais legível escrever $a and $b?

Sim, seria! Mas os operadores and e && não são exatamente iguais. Eles diferem na ordem de precedência e o and pode causar uma confusão enorme, pois ele tem menos prioridade do que os operadores de atribuição.

Por exemplo:

<?php

$nome = 'Kaique';
$aniversario= '31/10'; // tá chegando hein? :hehehehe:

$temNomeEAniversario = $nome && $aniversario;
var_dump($temNomeEAniversario); // bool(true)

$temNomeEAniversario = $nome and $aniversario;
var_dump($temNomeEAniversario); // string('Kaique')

QUE

Exatamente... No último exemplo, o que realmente acontece no interpretador é ($temNomeEAniversario = $nome) && $aniversario, antes mesmo de qualquer casting acontecer. Então se você mudar $aniversario para '', somente a primeira comparação vai dar certo, pois a segunda continuará sendo 'Kaique'.

Se você se interessou sobre ordem de precedência, eu mencionei no artigo Operadores - Precedência. Não vou mais mencionar esses exemplos nos próximos pois são basicamente a mesma coisa: o operador escrito semanticamente tem menos prioridade do que os operadores de atribuição.

Operador OR ou ||

$a$b$a or $b
falsefalsefalse
falsetruetrue
truefalsetrue
truetruetrue

No caso desse operador, sempre que um dos dados envolvidos for verdade, o resultado será true. A leitura semântica de $a || $b é "$a ou $b é verdade", podendo ser ambos simultaneamente.

Operador XOR

Aviso: esse não tem alternativa com símbolo, somente por semântica, o que pode ser um risco utilizar. Então, sempre que precisar dele, agrupe-o com os dados que deseja operar!

$a$b$a xor $b
falsefalsefalse
falsetruetrue
truefalsetrue
truetruefalse

O XOR é um eXclusive OR, que significa Ou exclusivo, e funciona exatamente como o operador anterior, exceto que não aceita o caso de quando os dois dados são verdade (última linha).

A leitura semântica de $a xor $b é "somente $a ou somente $b é verdade".

Operador !

Esse é o clássico da programação: o operador de negação. Diferente dos anteriores, esse age somente no dado à sua direita, ao invés de dois dados. Por tanto, a tabela da verdade não usa duas variáveis como as anteriores:

$a!$a
falsetrue
truefalse

E é isso mesmo, ele apenas reverte o valor. Sua semântica em cada caso é bem simples:

  • !false => true = o falso é falso, logo é verdade.
  • !true => false = a verdade é falsa, logo é falso.

Então a leitura semântica de !$a é "o inverso de $a", onde só sabemos se é verdade quando sabemos que $a é falso.


Exemplo

Vamos brincar? Copie esse código e cole no PHP Sandbox Online. O código abaixo irá gerar a tabela da verdade de cada operador! Legal?

<?php

// Gerador de tabelas da verdade
// https://blog.kaiquegarcia.dev
// Pode brincar à vontade ;)

// Primeiramente, mapeamos funções que serão nossos operadores com dois argumentos
$operadores = [
    'and' => fn($a, $b) => $a && $b,
    'or' => fn($a, $b) => $a || $b,
    'xor' => fn($a, $b) => ($a xor $b),
];

// Criamos uma função para converter booleano em string "true" ou "false" (só pra exibir bonitinho)
function strbool($bool) {
    return $bool ? 'true' : 'false';
}

// Depois fazemos um loop sobre os operadores de duplo argumento
foreach ($operadores as $operacao => $funcao) {
    echo "Operador $operacao" . PHP_EOL;

    // iniciamos o cabeçalho da tabela: | coluna 1 | coluna 2 | coluna 3 |
    echo '| $a | $b | $a  ' . $operacao . ' $b |' . PHP_EOL;
    echo '| --- | --- | --- |' . PHP_EOL;

    // fazemos um loop de 0,1 para variar $a
    for ($a = 0; $a < 2; $a++) {
        // e mais um loop de 0,1 para variar $b
        for ($b = 0; $b < 2; $b++) {
            // mesmo esquema do cabeçalho: | coluna 1 | coluna 2 | coluna 3 |
            echo '| ' . strbool($a) . ' | ' . strbool($b) . ' | ' . strbool($funcao($a, $b)) . ' |' . PHP_EOL;
        }
    }

    // Uma quebra de linha só pra separar na impressão
    echo PHP_EOL;
}

// Depois fazemos mais uma tabela pro operador de negação, que só usa um argumento

echo 'Operador de negação' . PHP_EOL;
echo '| $a | !$a |' . PHP_EOL;
echo '| --- | --- |' . PHP_EOL;

for ($a = 0; $a < 2; $a++) {
    echo '| ' . strbool($a) . ' | ' . strbool(!$a) . ' |' . PHP_EOL;
}

E por hoje é só! Curtiu? Comenta e compartilha! Deu pra ver como eu gosto de lógica, né? Essa é uma parte essencial para o bom entendimento de como o código se comporta. Óbvio que saber só isso não é exatamente um diferencial, é o básico. Sendo assim, nem preciso pedir para investir nisso, certo?

No próximo artigo, falaremos sobre o Operador de concatenação (e sua versão com atribuição). Spoiler: nós usamos ele hoje no gerador de tabelas da verdade! Muito! Mas irei explicar melhor no próximo, fechado?

Inté!