PHP para Iniciantes: Operadores de Anulidade

Primeiramente, preciso te contar que esse artigo não existe na documentação oficial do PHP. Eu inventei esse título - o que pode parecer genial e ao mesmo tempo estúpido - baseado no que acredito fazer sentido sobre o assunto da vez. E por ser um nome inventado, depois de ler tudo, comenta aí se fez sentido, pode ser? Obrigado desde já.

Esse é o último artigo da seção que fala sobre Operadores e eu já falei sobre tudo o que será mencionado aqui.

Ué, Kiko... Se você já falou, por que escrever um artigo?

Por que o artigo onde escrevi foi o Operador de Controle de Erro, o que pode levar a entender que os operadores que quero apresentar só servem para evitar erros e não é esse o ponto. Eu apenas mencionei formas de se fazer tratativas, assim como faço em todo artigo, não é mesmo?

Então hoje nós vamos falar sobre dois operadores relativamente recentes na linguagem:

  • null coalescing operator, que chamarei de operador de alternativa de anulidade (mas a tradução correta é operador de coalescência nula);
  • nullsafe operator, que vou traduzir para operador de prevenção de anulidade.

Vale ressaltar que ninguém os chama como as traduções que fiz, então se for comentar com alguém, usa o nome em inglês, beleza? Gosto de traduzir dessa forma para que iniciantes possam ter uma ideia clara do motivo da existência desses operadores e ter uma certa clareza no próprio nome.

Começando pela ordem cronológica de lançamentos...

Operador ?? (alternativa de anulidade)

Esse operador surgiu na versão 7 do PHP e veio para abalar as equações ternárias. Basicamente, existia um monte de apliações PHP que, para evitar valores negativos (os false quando convertido para booleano), escrevia ternários redundantes. Por exemplo:

<?php

$pessoa = ['nome' => null]; // essa pessoa não tem nome :O

$nomeDaPessoa = $pessoa['nome'] ? $pessoa['nome'] : 'Sem Nome';
var_dump($nomeDaPessoa); // string(Sem Nome);

Para quem não sabe, uma equação ternária é o equivalente a if-else que serve somente para atribuir um valor numa mesma variável. O código acima é o equivalente a:

<?php

$pessoa = ['nome' => null];

if ($pessoa['nome']) {
    $nomeDaPessoa = $pessoa['nome'];
} else {
    $nomeDaPessoa = 'Sem Nome';
}

var_dump($nomeDaPessoa); // string(Sem Nome);

O benéficio de utilizar equação ternária nesses casos é que você não repete a declaração da variável $nomeDaPessoa duas vezes. E apesar de, nesse exemplo, a ternária estar relativamente legível, na prática não é bem assim.

O padrão é que o ternário acabe sendo uma linha extensa. Tem quem coloque ternários dentro de ternários (confesso que já cometi esse crime). Então a coisa vai ficando feia.

Daí veio o PHP 7 com essa solução maravilhosa. Se a intenção é usar o ternário para dar um comando "use esse valor ou o outro se o primeiro não estiver definido", você pode resumir isso com o operador de alternativa de anulidade (daí essa tradução):

<?php

$pessoa = ['nome' => null];

$nomeDaPessoa = $pessoa['nome'] ?? 'Sem Nome';

var_dump($nomeDaPessoa); // string(Sem Nome)

Com isso, você não precisa repetir o $pessoa['nome'] duas vezes. Além disso, você pode fazer uma cadeia de alternativas, que vão sendo "testadas" da esquerda pra direita (se você não isolar um grupo com parênteses):

<?php

// não me pergunte por que alguém teria três nomes...
$pessoa = ['nome1' => null, 'nome2' => null, 'nome3' => 'Cleyton'];

$nomeDaPessoa = $pessoa['nome1'] ?? $pessoa['nome2'] ?? $pessoa['nome3'] ?? 'Sem Nome';

var_dump($nomeDaPessoa); // string(Cleyton)

Legal?! Então vamos para o próximo.

Operador ?-> (prevenção de anulidade)

Este operador veio ainda mais tarde que anterior, chegando bem fresquinho no PHP 8. Sua principal função é garantir que o que quer que esteja querendo acessar de um objeto só seja acessado se o objeto não for nulo.

Como assim, Kiko?

E lá vamos nós... :hehe:

<?php

class Exemplo
{
    public function echo(): void
    {
        echo 'Deu sorte, gerou uma instância' . PHP_EOL;
    }

    public static function getInstance(): self|null
    {
        if (rand(0,1)) {
            return null;
        }
        return new self;
    }
}

for($index = 0; $index < 10000; $index++) {
    $exemplo = Exemplo::getInstance();
    $exemplo->echo();
}

No código acima, você chama o método estático getInstance para obter uma nova instância da classe Exemplo. O que acontece é: você tem 50% de chance de receber null ou a tal nova instância.

O que acontece na linha seguinte se você receber null? Bom, vai encerrar a execução com um Fatal Error, pois não tem como acessar o método echo de um null.

<b>Fatal Error</b>: Uncaught Error: Call to a member function echo() on null in [...]

Então pode ser interessante se prevenir contra esse cenário. E com esse operador isso é extremamente fácil: basta adicionar uma interrogação antes da chamada do método, ficando ?->echo():

<?php

class Exemplo
{
    public function echo(): void
    {
        echo 'Deu sorte, gerou uma instância' . PHP_EOL;
    }

    public static function getInstance(): self|null
    {
        if (rand(0,1)) {
            return null;
        }
        return new self;
    }
}

for($index = 0; $index < 10000; $index++) {
    $exemplo = Exemplo::getInstance();
    $exemplo?->echo();
}

Assim, quando $exemplo for null, o interpretador não irá executar mais nada, apenas retornar null.

Ele retorna null pra quem, Kiko?

Pra quem estiver recebendo o resultado da chamada. No caso acima, como o método echo é void, não esperamos retorno algum. Mas poderíamos estar retornando boolean e esse fluxo adicionaria um caso de exceção que aplica null.

Dito isso, é preciso estar atento(a) sobre onde você irá aplicar esse operador, pois se o cenário não prever um valor null, tudo pode virar uma catástrofe, talvez pior do que sem o operador, rs.

Observação importante: esse operador serve SOMENTE para leitura de dados. Se você o chamar num fluxo de atribuição ou tentar criar uma referência a partir dele, será recebido(a) com um belíssimo Fatal Error. Tenta aí:

<?php

class Exemplo
{
    public string $nome = 'Cleyton';

    public static function getInstance(): self|null
    {
        if (rand(0,1)) {
            return null;
        }
        return new self;
    }
}


$exemplo = Exemplo::getInstance();
$exemplo?->nome = 'Clarissa'; // Fatal Error
// comente a linha de cima pra ver o Fatal Error debaixo
$nome = &$exemplo?->nome; // Fatal Error

Assim como o null coalescing, você também pode fazer uma cadeia de prevenções...

<?php

$pessoa = null; // tá explícito que é null só por causa do exemplo, ok?!

$resultado = $pessoa?->buscaPai()?->buscaTelefone()?->mandaSms('Seu filho bateu nos moleques da escola');

var_dump($resultado); // NULL

E é isso! Um artigo bem curtinho pra conhecer esses operadores que merecem sim um artigo exclusivo pra falar sobre eles.

Curtiu? Comenta e compartilha! Chama os amigos pra estudar PHP e tirar dúvidas comigo, hehe. No próximo artigo, começaremos a próxima seção: Estruturas de Controle.

Inté!