PHP / refactoring: Como ? (Parte 2)

25 05 2007

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

Anúncios

Ações

Information

One response

28 05 2007
Rael

Agora só falta vc criar os plugins pro PDT :P

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s




%d blogueiros gostam disto: