Metaprogramação em PHP

Artigo descreve o conceito de metaprogramação e apresenta exemplos de como ela pode ser feita com a linguagem PHP.

Com PHP, você consegue criar um código que gera outro código, afinal, um script PHP é um arquivo texto.

Mas a linguagem também permite que um script consiga alterar seu próprio comportamento em tempo de execução. Isso pode ser útil para otimizar trechos de código. Este recurso é possível com o comando "eval".

Abaixo é mostrado um exemplo de código que não usa e outro que usa a metaprogramação.

Na função abaixo, são feitas duas comparações a cada iteração do loop:

/**
 * Funcao de exemplo
 * @param array[int] $array Array de numeros
 * @param bool $somar Indica se o elemento do array deve ser somado com 1
 * @param bool $multiplicar Indica se o elemento do array deve ser multiplicado por 2
 * @return void
 */
function exemplo($array, $somar, $multiplicar) {
    foreach ($array as $elemento) {
        if ($somar) {
            $elemento += 1;
        }
        if ($multiplicar) {
            $elemento *= 2;
        }
        echo $elemento;
    }
}

Usando metaprogramação, primeiro seria montando o código que desejamos executar, depois passariamos para que ele seja avaliado com "eval":

/**
 * Funcao de exemplo
 * @param array[int] $array Array de numeros
 * @param bool $somar Indica se o elemento do array deve ser somado com 1
 * @param bool $multiplicar Indica se o elemento do array deve ser multiplicado por 2
 * @return void
 */
function exemplo($array, $somar, $multiplicar) {
    $codigo = 'foreach ($array as $elemento) {';
    if ($somar) {
        $codigo .= '$elemento += 1;';
    }
    if ($multiplicar) {
        $codigo .= '$elemento *= 2;';
    }
    $codigo .= 'echo $elemento;';
    $codigo .= '}';

    // Executar o codigo gerado dinamicamente
    eval($codigo);
}

O segundo código tende a ser mais rápido pois na variável $codigo serão colocadas apenas as instruções desejaveis de serem realizadas durante o loop. Se forem passados os valores true e true para $somar e $multiplicar, então o código gerado na variável $codigo seria:

foreach ($array as $elemento) {
    $elemento += 1;
    $elemento *= 2;
    echo $elemento;
}

Note que o loop não apresenta condições internamente, o que tornaria a iteração mais rápida.

Entretanto, trabalhar com metaprogramação não é recomendado para otimizar tudo. Pode ser utilizado em códigos de caixa preta, onde a implementação não costuma ser consultada, ou em trechos onde a performance precisa ser muito alta. Escrever um código e jogá-lo em uma variável é uma tarefa arriscada, já que o interpretador só conseguirá detectar erros de sintaxe no código gerado no momento em que o mesmo é avaliado (com o comando "eval"). Observe que isso incluem os delimitadores de instruções (ponto e vírgula), e tudo mais.

A metaprogramação torna o código menos legível e, por isso, também deve ser muito bem pensada antes de ser usada. Porém, abre brecha para construção de estruturas bastante interessantes. Observe o código abaixo:

$busca = 'if ($i == $elemento) { $achou = true; $busca = ""; }';

$elemento = 7;
for ($i = 0; $i < 10; $i++) {
    eval($busca);
    echo $i;
}

Este código imprime números de 0 a 9, só que o código também quer aproveitar as iterações do loop para checar se um determinado elemento foi impresso ou não. Assim que o elemento é encontrado, não é mais necessário checar nas próximas iterações. Logo, o código que guarda a busca passa a guardar nada. Quando uma string vazia é passada para "eval", nada é feito.

No exemplo, uma variável guardou um código capaz de mudar o seu próprio valor quando avaliado. Ela poderia mudar o código para outra coisa diferente de uma string vazia. Percebe o quanto a metaprogramação é complexa?

1 comentário