Chamando métodos dinâmicamente com PHP

Fala galera, esse é meu primeiro artigo no blog, e vou abordar um assunto interessante que me chamou atenção quando comecei a estudar Laravel, se trata de chamar métodos que ainda não foram definidos dentro da Classe!

Se você já trabalhou com Laravel, deve ter visto que quando você vai chamar uma view, você tem algumas opções para enviar os dados para ela, por exemplo:

Enviando via Array


$produtos = Produtos::all();
return view('produto.listar', ['produtos' => $produtos]);

Envia via Método


$produtos = Produtos::all();
return view('produto.listar')->withProdutos($produtos);
// ou return view('produto.listar')->with('produtos', $produtos);

Foi ai que veio minha dúvida, como eu consigo chamar um metodo de acordo com o nome da propriedade que eu quero usar, sabendo que ele não existe na minha classe? No artigo não usarei o exemplo de views, mas sim um exemplo comum de consulta no banco de dados, sem delongas, vamos pro código!

Construindo nosso Método Dinâmico

Irei usar como exemplo uma persistencia no banco de dados, onde através de um método, passaremos a coluna e seu valor para fazer a consulta no banco.
Por tanto, imagina que tenhamos uma classe Model, que é usada para persistir uma tabela no banco de dados.
Na nossa Model, temos um método get(), que por sua vez é usado para buscar algum valor na nossa tabela, ele recebe dois parametro, o $field que é o nome da coluna, e $value que é o valor a ser procurado!
Resumidamente nosso método get() é assim:


public function get($field, $value) {
    $statement = $this->db->prepare(sprintf("SELECT * FROM contact WHERE %s = ?", $field));
    $statement->bindParam(1, $value);
    $statement->execute();
    return $statement->fetch(PDO::FETCH_OBJ);
}

Para buscarmos por algum nome na nossa tabela, podemos chama-lo assim:


$model = new Model;
$model->get('name', 'Guilherme');
// Isso resultara em: SELECT * FROM contact WHERE name = 'Guilherme'

Ele faria uma busca na tabela 'contact', por algum usuario com a coluna 'name' igual à 'Guilherme'
Certo, mas e se quiséssemos usar um método proprio pra procurar o nome? algo como:
getName('Guilherme') ?
Simples, basta criar o método getName($value) dentro da nossa Model!
Mas imagina que nossa tabela tenha mais campos, e queremos procurar pelo e-mail ou pelo ID do contato, teriamos que criar um método getEmail() e getId()? Isso deixaria nossa classe super acoplada!
Para resolver isso, vamos criar nosso método dinâmicamente!

Método Mágico

O Primeiro passo, é fazer o PHP interceptar, todo método chamado que não fizer parte do escopo da classe, para isso vamos o __call(), um método mágico do PHP!

O __call() é chamado sempre que algum método inexistente na classe é invocado! Ele recebe dois parametros, o $func que é o nome do método/função que esta sendo chamado, e $args é um array com os parametros passados!


public function __call($func, $args) {
    // implementação aqui
}

Bom, como já temos o método get() que faz o trabalho de buscar os contatos no banco, iremos reutiliza-lo, com isso, quando chamarmos nosso método dinâmico, o getName() por exemplo, ele separar o get de Name, para fazer isso vamos usar a função substr() para retornar parte da string.
Primeiro passo é saber se é o get que está sendo acessado, para isso vamos recortar os 3 primeiros caracteres da string, e ver se retorna o get:


public function __call($func, $args) {
    if(substr($func, 0, 3) == "get") {
        // implementação aqui
    }
}

Agora que sabemos que ele está querendo acessar o get, vamos pegar qual coluna ele quer acessar!
Novamente iremos usar o substr(), só que agora eliminando os 3 primeiros caracteres e retornando o nome da coluna!
Também usaremos a função strtolower, que serve deixar a string em caixa baixa!


public function __call($func, $args) {
    if(substr($func, 0, 3) == "get") {
        $field = strtolower(substr($func, 3));
    }
}

Bom, agora que já separamos o GET da COLUNA, vamos pegar o argumento, que na verdade sera o valor a ser procurado no banco!
O segundo parametro do __call() retorna um array com os argumentos passados. Como no nosso exemplo só iremos passar um unico argumento, podemos usar a função array_shift para remover o indice desse array, e retornar apenas o seu valor!


public function __call($func, $args) {
    if(substr($func, 0, 3) == "get") {
        $field = strtolower(substr($func, 3));
        $arg   = array_shift($args);
    }
}

Pronto, agora que já temos a função get(), o nome da coluna, e o valor a ser procurado, só falta chamarmos nosso método get(), passando os parametros necessários (coluna, valor).

Para chamarmos o método get, iremos usar a função call_user_func_array(), que recebe dois parametros, ambos array.
No primeiro parametro iremos passar um array com o objeto e o método que queremos chamar, e no segundo, um array com os argumentos.


/**
 * $this é a instancia da própria classe que esta chamando o método
 * "get" é o nome do nosso método que sera chamado
 * $field é a coluna da nossa tabela
 * $arg é o valor que sera procurado
 */
call_user_func_array([$this, "get"], [$field, $arg]);

Estamos dizendo para o call_user_func_array, chamar o objeto $this (que é a instancia da nossa classe), e chamar o método get(), com os parametros ($field que é a coluna, e $arg que é o valor a ser procurado);
Resumidamente ao chamar getName("Guilherme"), a call_user_func_array ira fazer o seguinte


call_user_func_array(["$this", "get"], ["name", "guilherme"]);
//  irá chamar $this->get("name", "guilherme")

Simples! Caso a gente queira chamar por outra coluna da tabela, como por exemplo get("email", "email@emai.com"), basta chamarmos getEmail("email@email.com") que ele ira retornar o mesmo resultado!
Nossa classe final ficara assim:


class Model {
	
    /*
     * Métodos de conexão com o banco e etc..
     */
	
    public function get($field, $value) {
        $statement = $this->db->prepare(sprintf("SELECT * FROM pessoas WHERE %s = ?", $field));
        $statement->bindParam(1, $value);
        $statement->execute();
        return $statement->fetch(PDO::FETCH_OBJ);
    }

    public function __call($func, $args) {
        // verifica se ele esta tentando chamar algum get
        if(substr($func, 0, 3) == "get") {
            // pega o nome da coluna
            $field = strtolower(substr($func, 3));
            // retira o indice do array retornando só o valor
            $arg   = array_shift($args);
            // retorna o método get() passando a coluna e o valor como parametro
            return call_user_func_array([$this, "get"], [$field, $arg]);
        }

        // caso não seja chamado nenhum método get, joga exceção
        throw new Exception(sprintf("Método %s não encontrado.", $func));
    }
}

Apesar de ser simples, é uma mão na roda quando precisamos de métodos especificos.
Espero que tenham gostado, caso vocês façam de outra forma ou tenham alguma dúvida, postem nos comentários.
Até a Próxima!

Comentários