Pois é, eu não sabia que teria parte 2, mas este é para ser uma continuação do post PHP / refactoring: Como ?.
Vejamos agora como modificar o nome de uma função e as chamadas para a mesma. O exemplo anterior com algumas modificações será utilizado:
<?php
// arquivo sample2.php
$foo = ‘foo’;
$hello = “Ola $foo\n”;
// funcao foo() no comentario nao sera modificada
function foo()
{
// variavel $foo nao sera modificada
$foo = “dentro de foo()\n”;
echo $foo;
}
// exibimos $hello
echo $hello;
// observe que foo() e foo () sao diferentes:
// foo() equivale a:
// T_STRING (foo)
// ( (abre parenteses)
//
// enquanto foo () equivale a:
// T_STRING (foo)
// T_WHITESPACE (‘ ’)
// ( (abre parenteses)
//
// e por esse motivo deve ser tratado como diferente
foo ( );
?>
E a função com exemplo de uso:
<?php
// arquivo change_function_name.php
/**
* Funcao que ira modificar o nome de uma funcao
*
* @param array $tokens Array com os tokens a serem analisados
* @param string $oldName Nome da funcao que sera modificada
* @param string $newName Novo nome da funcao
* @return string Codigo-fonte referente aos tokens fornecidos, com a
* funcao devidamente modificada
*/
function changeFunctionName($tokens, $oldName, $newName)
{
// variavel que ira conter o codigo-fonte a ser retornado
$ret = ”;
// contador, marcara o indice do token em uso
$tokenIndex = -1;
// como $tokens e’ um array, utilizamos foreach para
// passar por todos os seus itens
foreach ($tokens as $token) {
$tokenIndex++;
// quando se obtem os tokens de um arquivo, eles vem em um
// array contendo o numero do token e o valor ou em uma string,
// contendo apenas o valor. Entao verificamos qual o caso.
if (!is_array($token)) {
$tokenName = false;
$tokenValue = $token;
} else {
$tokenName = token_name($token[0]);
$tokenValue = $token[1];
}
// verificamos se o token atual pode ser considerado uma funcao
if ($tokenName == ‘T_STRING’) {
// obtemos os dois proximos tokens
$nextToken = $tokens[$tokenIndex + 1];
$nnextToken = $tokens[$tokenIndex + 2];
// aqui temos o seguinte:
// se o proximo token for igual a ”(“ E se o valor do token atual for igual
// ao nome da funcao que queremos modificar…
$flag = ($nextToken[0] == ‘(‘ && $tokenValue == $oldName);
// … OU o proximo token for um espaco E o token apos o proximo for igual a ”(“
// E o valor do token atual for igual ao nome da funcao que queremos modificar…
$flag |= (@token_name($nextToken[0]) == ‘T_WHITESPACE’ && $nnextToken[0] == ‘(‘ && $tokenValue == $oldName);
// … entao modificamos o valor do token,
if ($flag) {
$tokenValue = $newName;
$ret .= $tokenValue;
continue;
}
}
// se chegou ate aqui, significa que nao e’ um token que nos interessa, apenas pegamos
// o valor do token e o adicionamos ao final do fonte a ser retornado
$ret .= $tokenValue;
}
// retornamos o fonte
return $ret;
}
// exemplo de uso da funcao
// obtemos o conteudo do arquivo que iremos modificar
$arquivo = file_get_contents(’sample2.php’);
// e utilizamos a funcao token_get_all para obter todos
// os tokens do arquivo. Observe que tudo no script e’
// tratado como sendo um token
$tokens = token_get_all($arquivo);
// modificamos foo() para bar()
$bla = changeFunctionName($tokens, ‘foo’, ‘bar’);
echo $bla;
?>
Executando:
$ php change_function_name.php
<?php
// arquivo sample2.php$foo = ‘foo’;
$hello = "Ola $foo\n";// funcao foo() no comentario nao sera modificada
function bar()
{
// variavel $foo nao sera modificada
$foo = "dentro de foo()\n";
echo $foo;
}// exibimos $hello
echo $hello;// observe que foo() e foo () sao diferentes:
// foo() equivale a:
// T_STRING (foo)
// ( (abre parenteses)
//
// enquanto foo () equivale a:
// T_STRING (foo)
// T_WHITESPACE (‘ ‘)
// ( (abre parenteses)
//
// e por esse motivo deve ser tratado como diferente
bar ( );
?>
Observe que a declaração da função foi modificada ( function bar ) e a chamada à função também foi modificada (bar ( ); ).
Ótimo, esta pronto ? Não…
Como bem sabemos, é possível executar funções de outras formas em PHP, como por exemplo, utilizando call_user_func, eval, variáveis utilizadas como funções ( $foo(); ), funções utilizadas como callback, …
Então, em uma implementação completa desta função, deveriamos nos atentar a estes fatos.
Para alguns casos, seria relativamente fácil criar uma implementação:
- Em eval, poderiamos utilizar a string que esta sendo executada e, recursivamente, chamar a função changeFunctionName para os tokens obtidos para esta string
- Em call_user_func / call_user_func_array, poderiamos verificar se o token atual é um T_STRING e o valor do token é ‘call_user_func’, desta forma. Se for, analisaria-se os tokens seguintes para ver se não teria relação com a função sendo renomeada;
Já em outros casos, seria necessário um pouco mais de malabarismos:
- Em variáveis utilizadas como funções, provavelmente precisariamos de um trackback das variáveis, para verificar se o valor dela não corresponde ao nome da função que desejamos renomear
- Utilizar um grande switch para verificar todas as funções builtin do PHP que utilizam funções como callback
De qualquer forma, para fontes onde não se utilize a função sendo renomeada de forma mais “avançada”, este exemplo deve funcionar.
change_var_name.php (exemplo anterior)
change_function_name.php