Cache de variáveis no PHP (Parte 1)

Em: 26/11/2007 Tags: Referencie do seu blog (Trackback)

Projetos PHP costumam sofrer de uma alta dose de variáveis globais. Em PHP, por padrão, as variáveis ficam acessíveis apenas no escopo onde são declaradas. Quando dentro de uma função, por exemplo, só é possível utilizar uma variável global se a primeira linha dessa função possui a palavra-chave global:

function funcao_com_2_var_globais() {
    global $var_1, $var_2;

    // implementacao
}

Neste exemplo, funcao_com_2_var_globais() tem acesso a $var_1 e a $var_2. Embora seja simples, portanto, importar variáveis globais para o contexto local, o código pode se tornar mais difícil de manter, caso essas variáveis não tenham acesso somente-leitura. Em um sistema onde diversas funções podem editar o estado de uma variável global é complicado saber qual o seu valor em um dado momento da execução do programa.

Em certos casos variáveis globais são utilizadas sem necessidade, onde uma variável declarada como static poderia tornar o mesmo código mais simples.

O código com a qual estou lidando essa semana envolve a utilização da função get_role_from_id_name. “Role”, no caso, é uma tabela do meu banco de dados, a qual armazena papéis que usuários podem desempenhar no sistema. O objetivo dessa função é receber um nome abreviado para um papel (”admin”, por exemplo), e retornar o valor da chave primária referente ao registro cujo campo “name” possui valor igual a essa string.

O autor do código original decidiu utilizar a variável global $roles para armazenar um mapeamento (array associativo) entre nomes de papéis e id’s de papéis, conforme o código abaixo (as declarações “print”, obviamente, não fazem parte do código original).

function get_role_id_from_name($name) {

    global $db, $roles;

    if (empty($roles)) {
        print ">> Acessei o BD...\n";
        $_roles = $db->get_roles();
        foreach ($_roles as $_role_id => $_role) {
            $roles[$_role['name']] = $_role_id;
        }
    }

    else {
        print ">> NAO acessei o BD...\n";
    }

    return $roles[$name];
}

Para testar o código acima podemos executar este script:

print "id do papel 'admin' = "  . get_role_id_from_name('admin')."\n";
print "id do papel 'editor' = " . get_role_id_from_name('editor')."\n";
print "id do papel 'guest' = "  . get_role_id_from_name('guest')."\n";

… que nos daria a seguinte saída:

Acessei o BD...
  id do papel 'admin' = 1
NAO acessei o BD...
  id do papel 'editor' = 2
NAO acessei o BD...
  id do papel 'guest' = 3

Como a função get_role_id_from_name() é bastante usada na aplicação, é interessante minizar o acesso ao banco para realizar essa tarefa. Mas em nome da otimização acabou sendo introduzida uma variável global desnecessária. Desnecessária pois ela será utilizada apenas nessa função, e em nenhuma outra.

Podemos resolver esse problema de forma parecida com ajuda da palavra-chave static, conforme a nova versão da função.

function get_role_id_from_name($name) {

    global $db;

    static $roles;

    if (empty($roles)) {
        print "Acessei o BD...\n";
        $roles = array();
        $_roles = $db->get_roles();
        foreach ($_roles as $_role_id => $_role) {
            $roles[$_role['name']] = $_role_id;
        }
    }

    else {
        print "NAO acessei o BD...\n";
    }

    return $roles[$name];
}

A declaração static $roles; indica ao PHP que queremos manter em cache a variável $roles. Quando a variável estiver vazia (na primeira vez em que a função é executada), acessaremos o banco de dados (através do objeto $db) para inicializá-la. Nas próximas chamadas a essa função, no entanto, $roles não estará empty(), pois o PHP guardou o valor atribuído a ela na primeira chamada.

Nosso script de testes anterior retornaria, portanto, a seguinte saída:

Acessei o BD...
  id do papel 'admin' = 1
NAO acessei o BD...
  id do papel 'editor' = 2
NAO acessei o BD...
  id do papel 'guest' = 3

Ou seja, temos exatamente a mesma funcionalidade, mas com uma variável global a menos no nosso código.

Se decidir adotar esta técnica em um projeto, não esqueça que na maior parte das situações você deseja sempre trabalhar com a última versão dos registros do banco de dados, e não com uma versão em cache. No caso apresentado, a execução do programa não insere nem remove dados da tabela “roles”, e por isso foi adequado mantê-la em cache.

Artigos relacionados:

9 respostas para “Cache de variáveis no PHP (Parte 1)”

  1. Thânia Clair disse:

    Parece bem útil! Isso é pra qq versão de PHP, não? ;~)

  2. Caio disse:

    Ao menos na 4 e na 5 sei que funciona.

  3. Éverton disse:

    Caio muito boa a dica,

    Eu já utilizo a técnica a algum tempo, e aprendi lendo o código fonte do Drupal. Mas uma característica, que eu mesmo adicionei é um segundo parâmetro chamado $refresh, que por padrão é false, quando preciso força uma atualização do cache (por exemplo na página que administra as roles) eu chamo a função assim: get_role_id_from_name([name], true), assim fica mais flexível.

  4. Caio disse:

    Olá, Éverton

    Eu estava pensando exatamente nisso que você falou: como forçar a atualização do cache. Sua dica é ótima, pretendo inclui-la nos próximos artigos que tratarem do assunto.

    PS: E eu aprendi essas técnicas com o código do Moodle ;)

    Valeu!

  5. danielneis disse:

    HA! Muito bom, muito bom… Será que eles tiraram a idéia de chamar de static devido aos métodos (e atributos?) static, que são métodos(e atributos) de classe? Ou será que foi pra economizar palavras reservadas?

  6. Caio disse:

    Com o PHP 5 essa palavra-chave serve para as duas coisas: utilizada dentro de uma função, como mostrei no artigo, e para denotar métodos e atributos de classes que podem ser chamadas diretamente na classe, sem necessidade de se instanciar um objeto (como tu mencionou).

    Acho que não foi, portanto, para economizar palavras reservadas, e sim porquê o static realmente dá essa idéia de algo global dentro do contexto de execução.

  7. Rafael Cotta disse:

    Eu uso em PHP uma técnica parecida com o que eu uso com frameworks prontos em Java, como o OSCache. Tenho duas funções utilitárias como abaixo:

    // retorna um ítem do cache
    function getFromCache ($keyName) {
       $ret = $GLOBALS["cache{$keyName}"];
       return $ret;
    }
    
    // coloca um ítem no cache
    function putInCache($keyName, $obj) {
       $GLOBALS["cache{$keyName}"] = $obj;
       return $obj;
    }
    

    Com isso você utiliza código do tipo:

    function returnComplexResult() {
        if ($ret = getFromCache(__FUNCTION__)) {
            return $ret;
        }
    
        $ret = processamento_complexo();
        return putInCache(__FUNCTION__, $ret);
    }
    
  8. Rafael Cotta disse:

    __FUNCTION__ serve para retornar o nome da função corrente.

    Lembre-se também se a sua função recebe parâmetros provavelmente você vai querer compor o $keyName com algo do tipo:

    $keyName = __FUNCTION__ . ":{$param1}:{$param2}";
    
    putInCache($keyName, $var);
    
  9. Caio Moritz disse:

    Olá, Rafael

    Sua técnica é interessante (o uso de __FUNCTION__ é novo pra mim!), tenho minhas ressalvas apenas pelo utilização de uma variável global, que é o próprio cache. Como esse parece ser o cache global da aplicação acho que não dá pra fugir da variável global mesmo, pois ela é utilizada na função que recupera o valor do cache e na que insere um novo.

    Obrigado pelo comentário ;)

    PS: Tomei a liberdade de modificar seu comentário para que a formatação do código ficasse correta.

Escreva um comentário (utilize o formato Markdown)