PHP para Iniciantes: Tipos de Dados Primitivos - Acionáveis

A primeira coisa que me vêem a cabeça quando utilizo um termo como "acionáveis" é alarme de carro, rs. Mas na linguagem de programação, um tipo de dado "acionável" na verdade não é exatamente um dado, e sim uma referência para um conjunto de instruções.

Funções

Função é o exemplo mais fácil disso:

<?php

function acionavel(): void
{
    echo "Lalalala";
}

if (is_callable("acionavel")) {
    echo "simmmmmmm";
} else {
    echo "naaaaaaaooo";
}

A função é claramente um acionável, porque se você "executá-la", estará acionando o conjunto de instruções que tem dentro dela. É basicamente isso que determina esse tipo de dado. No caso, funções representam o básico, independente de serem declaradas, anônimas (closures) ou arrow functions (PHP 7.4+).

<?php

function funcaoDeclarada(): bool
{
    return true;
}

$funcaoAnonima = function() {
    return false;
};

$arrowFunction = fn(): bool => is_callable($funcaoAnonima) && is_callable("funcaoDeclarada");

if (is_callable($arrowFunction) && $arrowFunction()) {
    echo "Todas saoooo";
} else {
    echo "Eh nada...";
}

Só para mostrar uma prévia do futuro artigo sobre funções... Mas falando em callable, percebeu que o teste de uma função declarada é diferente das demais? Isso acontece que, por ser declarada, a função possui um nome que representa sua identidade naquele escopo. É por esse motivo que é impossível declarar o nome de uma função que já está ocupado.

Já as funções anônimas (arrow function também é anônima), como o próprio nome diz, não possuem nome algum. Elas são armazenadas temporariamente, sendo em variáveis no mesmo escopo ou apenas repassadas direto na chamada de outro acionável. Assim, ao invés de simplesmente passar um nome, você deve passar a variável onde a função anônima está armazenada.

Mas tem outras formas de criar uma referência dessas, Kiko?

Com certeza!! No artigo sobre objetos, mencionei alguns spoilers sobre o futuro artigo de Classes & Objetos, certo? Um desses spoilers foi sobre métodos, que podem ser públicos, privados, protegidos... Mas não dei o spoiler sobre métodos estáticos e não-estáticos. Hoje vai rolar /hehehe

Métodos Estáticos vs Não-Estáticos

Primeiramente, métodos seguem o mesmíssimo comportamento de funções, exceto que eles possuem contextos mais fechados. Com isso, o que quero dizer, é que há uma classe por trás do método que fornece outros dados para ele. Com isso, no escopo interno existem palavras mágicas reservadas, como $this, self, parent, etc. Todas essas palavras só existem em métodos, jamais em funções. Ou até tem, mas você vai estar fazendo uma gambiarra bem louca.

E sendo diferente de funções, é óbvio que criar uma referência callable para um método precisa ser algo bem diferenciado: utilizando array. Se o método for estático até dá para criar uma referência em string, mas o resto não.

Como assim um array pode virar callable, Kiko?

Não é qualquer array. Para ser mais preciso, é uma dupla. Todo array com dois elementos, sendo o primeiro um objeto ou o nome de uma classe e o segundo uma string representando o nome do método a ser acessado torna-se um callable.

Vamos aos exemplos!

<?php

class Classe {
    public static function metodoEstatico(): bool
    {
        return true;
    }
}

$objeto = new Classe();

$acionavelPeloObjeto = [$objeto, "metodoEstatico"];

$acionavelPelaClasse = ["Classe", "metodoEstatico"];
// ou
$acionavelPelaClasse = "Classe::metodoEstatico";

Mas Kiko, eu ainda não sei o que é um método estático!

Err... É o seguinte. Métodos estáticos são métodos que não precisam de um objeto construído para ser acionado. Já os não-estáticos, precisam que o objeto esteja construído para existir. A compreensão sobre isso é que os dados estáticos são propriedades da classe e os não-estáticos são propriedades de uma instância da classe. Deu pra entender? Acho que esse é o melhor resumo que eu posso dar sem mergulhar de cara nesse tópico.

Mas com outro exemplo, seria mais ou menos isso:

<?php

class Classe {
    public function metodoNaoEstatico(): bool
    {
        return is_callable([$this, "metodoPrivado"]); // funciona, pois $this = $objeto
        // e ao acionar de dentro da classe, possuímos acesso aos métodos privados
    }

    private function metodoPrivado(): bool
    {
        return true;
    }
}

$objeto = new Classe();

$acionavelPeloObjeto = [$objeto, "metodoNaoEstatico"]; // isso funciona
$acionavelPelaClasse = ["Classe", "metodoNaoEstatico"]; // isso não funciona
$acionavelPelaClasse = "Classe::metodoNaoEstatico"; // também não funciona

No exemplo acima, como o método não é estático, ele depende da construção do objeto para existir. Então se você tentar montar uma referência callable a partir da classe, não vai dar certo pois o método só vai passar a existir quando o objeto for instanciado.

Objetos acionáveis

Ok, além de todas essas formas, temos também os objetos que podem ser acionáveis. São, geralmente, classes que tem um único propósito. É uma estratégia similar a criar uma função com o adicional de armazenar outros dados antes de executar.

Mas como raios faz isso, Kiko?

É bem simples. Basta adicionar o método mágico __invoke:

<?php
class Classe {
    public function __invoke(): void
    {
        echo "Chorando se foi quem um dia so me fez chorar";
    }
}

$objeto = new Classe();
$acionavelPeloObjeto = is_callable([$objeto, "__invoke"]);
// é o mesmo que
$acionavelPeloObjeto = is_callable($objeto);

Qual a utilidade disso tudo?

Com isso, agora você pode criar certos fluxos esperando receber algum acionável para tratar. Por exemplo:

<?php
class Maquina {
    public function executar(callable $instrucao): void
    {
        $instrucao();
    }
}
$algumNumero = 0;

$maquina = new Maquina();
$maquina->executar(fn(): void => $algumNumero++);

O papel do callable nesse exemplo foi garantir que, o que quer que seja passado como argumento ao acionar o método executar deve ser um callable, que será executado logo em seguida.

Foi um exemplo bem ruim, eu sei, mas diz aí se deu pra entender, pode ser? Estou curioso pra saber se você teve alguma ideia de como aplicar o tipo callable também, então deixa seu comentário e vamos nessa.

Inté!