Cache de variáveis no PHP (Parte 1)
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.

November 26th, 2007 às 4:31 pm
Parece bem útil! Isso é pra qq versão de PHP, não? ;~)
November 26th, 2007 às 4:56 pm
Ao menos na 4 e na 5 sei que funciona.
November 26th, 2007 às 6:44 pm
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.November 26th, 2007 às 7:00 pm
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!
December 1st, 2007 às 10:33 am
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?
December 1st, 2007 às 12:39 pm
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
staticrealmente dá essa idéia de algo global dentro do contexto de execução.January 3rd, 2008 às 10:40 pm
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:
Com isso você utiliza código do tipo:
January 3rd, 2008 às 10:42 pm
__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:
January 3rd, 2008 às 11:06 pm
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.