PHP para Iniciantes - Classes e Objetos - Operador de Resolução de Escopo

Olá, leitores da taverna, tudo belezinha? Antes de irmos direto ao assunto desse artigo, gostaria de quebrar um pouco esse costume de deixar a escrita pessoal pro final, pode ser?

Ah, não, Kiko... Que que você quer dessa vez?!

Hahaha, só conhecer quem está lendo! Eu tenho dois grandes objetivos para esse ano de 2022:

  1. ajudar 100 aspirantes a dev a, de fato, conseguir um emprego como dev: pelo andar da carruagem, acho que não vou conseguir... Mas já ajudei quase 30, hein?! e não, eu não mexo pauzinhos para alguém pular uma catraca e entrar... A única coisa que faço é guiar as pessoas a seguir uma direção de forma lógica e explorar os conhecimentos para ver com o que se identifica. Bora?
  2. criar um evento cíclico de coding dojo para devs Golang, nem que seja só na empresa onde estou: percebi que a correria da vida está sendo um verdadeiro problema pra tocar isso. Nem eu tenho energia pra fazer no horário livre, nem os devs demonstram querer, de fato, que isso aconteça. Será que é porque nunca foram a um coding dojo ou só não acham isso tão relevante assim? Talvez o problema seja eu?

Enfim... Se você se encaixa de alguma forma em um dos meus objetivos, seja por querer participar de um coding dojo ou por querer trabalhar como dev, não hesite em me procurar, fechado?! Eu também ajudo empresas a organizar coding dojos para seus colaboradores, mas geralmente eu não participo dos eventos, por serem fechados. Eu só digo o que fazer, opino se o desafio tá bem interessante, etc. Eu nem cobro por essas paradas! Aiai...

Agora chega, vamos falar de PHP!

Operador de Resolução de Escopo (::)

Esse nome pode nos confundir bastante, mas eu já mostrei esse operador diversas vezes ao longo dos artigos sobre classes, será que você reparou? Em algum momento até aqui, eu te ensinei, de forma explícita, como acessar propriedades e métodos de um objeto, usando o operador ->. Esse operador é específico para dados não-estáticos, ou seja, que precisam de uma instância para existir.

E da mesma forma que esse tipo de estrutura existe, também temos os dados estáticos, que não precisam de uma instância. São eles:

  • constantes: se o valor é fixo, por que raios eu precisaria instanciar a classe para acessá-lo?;
  • propriedades estáticas: ok, não é fixo, mas tem um valor que transita entre instâncias, ou seja, compartilhado... Não necessariamente precisa de uma instância para existir;
  • métodos estáticos: são funções quase desacopladas de uma classe, presente por apenas dois motivos - ou para tornar a legibilidade de um código mais semântico, ou para poder acessar outros dados estáticos da própria classe que tem visibilidade protegida ou restrita. Temos apenas uma exceção para fábricas, que é um design pattern que usa função estática como gerador de instâncias... Isso não vai ser abordado aqui, mas achei relevante mencionar.

Para exemplificar os cenários acima, escrevi o código a seguir:

<?php

class Sirene {
    // Constantes
    public const SOM = "UEEENNNNNNNNNN";
    private const REPETICOES = 20;

    // Propriedades estáticas
    public static $RESPONSAVEL = "";

    // Métodos estáticos
    public static function tocar(string $autor): void {
        Sirene::$RESPONSAVEL = $autor;
        for ($contador = 0; $contador < Sirene::REPETICOES; $contador++) {
            echo Sirene::SOM . PHP_EOL;
        }
    }
}

// 1. Qual é o som da sirene?
echo "O som da sirene é: " . Sirene::SOM . PHP_EOL;

// 2. Ligue a sirene!
Sirene::tocar("Fulano");

// 3. Quem ligou a sirene?!
echo "Quem ligou a sirene foi " . Sirene::$RESPONSAVEL;

Veja o código em ação: https://onlinephp.io/c/900c7

Tá, Kiko, se o objetivo desse operador é acessar dados de uma classe, por que se chama Operador de Resolução de Escopo?

Porque ele pode ser aplicado de diversas formas. Eu te disse que ele acessa dados estáticos, certo? Mas jamais falei que só poderia ser usado em classes, pois também é possível aplicarmos em um objeto ou em palavras-chave como self, static e parent. Ele irá funcionar em qualquer uma, desde que o dado que está tentando acessar exista para aquela referência.

Ou seja: você diz o escopo e a referência do dado que você quer acessar e o operador resolve isso pra você. Faz mais sentido agora?

Nós podemos reescrever o código acima para mostrar a aplicação em objeto e nas palavras-chave self e static:

<?php

class Sirene {
    // Constantes
    public const SOM = "UEEENNNNNNNNNN";
    private const REPETICOES = 20;

    // Propriedades estáticas
    public static $RESPONSAVEL = "";

    // Métodos estáticos
    public static function tocar(string $autor): void {
        self::$RESPONSAVEL = $autor;
        // "self" equivale a Sirene, pois está dentro dessa classe
        // e nesse caso, poderíamos usar também a palavra "static"
        // "self" representa a classe que implementa o método acionado
        // "static" representa a classe acionada
        // vou mostrar mais exemplos, não se preocupe... mas vamos de static:
        for ($contador = 0; $contador < static::REPETICOES; $contador++) {
            echo static::SOM . PHP_EOL;
        }
    }
}

// Digamos que, ao invés de acessar a classe, por algum motivo nós temos um objeto Sirene
$sirene = new Sirene();

// Então, ao invés de usar Sirene::, podemos usar $sirene::

// 1. Qual é o som da sirene?
echo "O som da sirene é: " . $sirene::SOM . PHP_EOL;

// 2. Ligue a sirene!
$sirene::tocar("Fulano");

// 3. Quem ligou a sirene?!
echo "Quem ligou a sirene foi " . $sirene::$RESPONSAVEL;

Veja o código na prática: https://onlinephp.io/c/0653b

Mas Kiko, se eu posso escrever o nome da classe, por que que eu usaria as outras formas? Onde que isso faz sentido?

Concordo que, em cenários onde é possível escrever o nome da classe, é muito mais legível deixar a classe por extenso. Ainda assim, nem sempre isso é possível de se fazer, principalmente se você está desenvolvendo um código utilizando interfaces, como mencionei no artigo anterior, que pode obrigar a desenvolver métodos estáticos e você não faz ideia de qual implementação está recebendo na função.

Por exemplo, veja a função calcularDano a seguir:

<?php

interface Arma {
    public static function gerarDano(float $defesa): float;
}

class Bazuca implements Arma {
    public static function gerarDano(float $defesa): float {
        return rand(500, 2000) - $defesa;
    }
}

class Metralhadora implements Arma {
    public static function gerarDano(float $defesa): float {
        return rand(50, 350) - $defesa;
    }
}

function calcularDano(Arma $arma, float $defesa): float {
    // não sabemos com qual implementação da interface Arma estamos lidando
    // a única coisa que sabemos é que, o que quer que seja, implementa o método estático "gerarDano"
    // nesse caso, podemos usar o operador de resolução de escopo para nos dar acesso a esse método:
    $dano = $arma::gerarDano($defesa);
    if ($dano < 0) {
        return 0.0;
    }
    return $dano;
}

$defesa = rand(0,200);
$bazuca = calcularDano(new Bazuca(), $defesa);
$metralhadora = calcularDano(new Metralhadora(), $defesa);
echo "Defesa: $defesa" . PHP_EOL;
echo "Dano aleatório da Bazuca: $bazuca" . PHP_EOL;
if ($bazuca > 2*$defesa) {
    echo "Foi crítico!" . PHP_EOL;
}
echo "Dano aleatório da Metralhadora: $metralhadora" . PHP_EOL;
if ($metralhadora > 2*$defesa) {
    echo "Foi crítico!" . PHP_EOL;
}

Veja o código na prática: https://onlinephp.io/c/e46e8

Ok, acho que entendi o uso desse operador com objetos... Mas e as palavras-chave? Quando faz sentido usar?

Em cenários onde há o uso de Herança. Vamos revisar cada palavra-chave:

self

Trata-se de uma referência para a classe onde o método acionado está registrado.

Representação: self

<?php

class Mae {
    public static $EXPERIMENTO = "MÃE";
    public static function teste(): void { // método registrado na classe Mae
        echo self::$EXPERIMENTO; // self = Mae, sempre
    }
}

class Filha extends Mae {
    public static $EXPERIMENTO = "FILHA";
}

Filha::teste(); // imprimirá "MÃE"

Veja o código em ação: https://onlinephp.io/c/81e90

static

Trata-se de uma referência para a classe acionada.

Representação: static

<?php

class Mae {
    public static $EXPERIMENTO = "MÃE";
    public static function teste(): void {
        echo static::$EXPERIMENTO; // static = depende
    }
}

class Filha extends Mae {
    public static $EXPERIMENTO = "FILHA";
}

Filha::teste(); // imprimirá "FILHA", pois o "static" aqui é a classe "Filha"

Veja o código em ação: https://onlinephp.io/c/d7fcc

Observações:

  • eu apenas troquei self::$EXPERIMENTO por static::$EXPERIMENTO;
  • se a classe Filha não sobreescrevesse a propriedade $EXPERIMENTO, herdaria o valor da classe Mae. Não significa que está acessando a classe Mae, apenas usando o valor que foi herdado, beleza? Faz o teste aí: https://onlinephp.io/c/271ce.

parent

Trata-se de uma referência a classe que vem antes da classe onde o método acionado está registrado.

Representação: static

Que descrição complexa, né?! Você pode pensar dessa forma: ele funciona como o self, só que busca a classe que foi extendida pela classe presente em self. Para isso, vamos fazer mais uma classe:

<?php

class Mae {
    public static $EXPERIMENTO = "MÃE";
}

class Filha extends Mae {
    public static $EXPERIMENTO = "FILHA";
    public static function teste(): void {
        echo parent::$EXPERIMENTO; // parent = Mae, sempre
    }
}

class Neta extends Filha {
    public static $EXPERIMENTO = "NETA";
}

Neta::teste(); // imprimirá "MÃE"

Veja o código em ação: https://onlinephp.io/c/ece

. . .

E por hoje é só! Curtiu?? Comenta e compartilha!! Uma curiosidade bacana sobre esse operador é que o nome oficial que deram a ele é Paamayim Nekudotayim, que é a fonética para פעעמיים נקודותיים, que significa dois pontos duplos em Hebreu!

Então, se por algum motivo você encontrar um erro com essas palavras no PHP, já vai ficar ligado que se trata de algum uso errado do operador de resolução de escopo! Hehehehe. Espero que tenham curtido, hein?! No próximo artigo, falaremos um pouco mais sobre a palavra-chave static. Hoje só falamos a ponta do iceberg, usando static como referência. No próximo, vamos falar sobre dados estáticos. Não perca, hein?

Inté!!