É válido lembrar que cache é, por definição, um meio de armazenamento volátil. Os dados guardados em cache podem ser perdidos mesmo antes que expirem. Portanto, não devemos utilizar cache quando necessitarmos que os dados sejam persistentes.

Caching é uma maneira rápida de acelerar e aumentar a performance de aplicações Web. Nessa técnica é armazenado em cache dados que não sofrerão mudanças, ou seja, dados estáticos, e ficaram a disposição para serem utilizados quando forem requisitados, dessa forma economizaremos o tempo necessário para gerá-los.

Durante esse artigo iremos aprender como podemos configurar e acessar um componente de cache no Yii.

O Yii oferece diversos componentes de cache que podem ser utilizados para armazenar dados nos mais diversos meios:

  • CMemCache: pode ser utilizado para encapsular a extensão memcache do PHP e utiliza a memória como meio para armazenar os dados. A PHP memcache é o modulo PHP que fornece a relação procedural e orientada a objetos, necessário para o memcached, que foi especialmente projetado para diminuir a carga de dados armazenados no banco de dados das aplicações web;
  • CApcCache: utiliza a extensão PHP APC (Alternative PHP Cache), que faz cache em memória a partir de uma chave fornecida previamente. Além dela, devem ser informados quais os dados a serem salvos e por quanto tempo devem ser mantidos em cache;
  • CXCache: componente disponível a partir da versão 1.0.1, utiliza a extensão PHP XCache, que é um gerenciador de cache que armazena os scripts PHP em opcode para um futuro uso. Possui um alto desempenho de velocidade, por isso é muito utilizado no mundo em servidores de alta demanda;
  • CEAcceleratorCache: utiliza a extensão PHP EAccelerator, que armazena scripts PHP em cache de forma a acelerar a execução do programa.
  • CDbCache: armazena os dados em uma tabela do banco de dados. Essa extensão utiliza como padrão o banco de dados SQLite3 e ao ser utilizada irá criar um banco de dados no diretório runtime de sua aplicação. Você pode utilizar a propriedade connectionID para especificar explicitamente o banco de dados.
  • CZendDataCache: componente disponível a partir da versão 1.0.4. Utiliza o Zend Data Cache como meio de armazenamento, que é um componente que disponibiliza um lugar para salvar dados em cache, inclusive entre aplicações no mesmo servidor.
  • CFileCache: Componente disponível a partir da versão 1.0.6., utiliza arquivos para armazenar os dados em cache, método muito útil para armazenar grandes quantidades de dados em cache (um exemplo seria o armazenamento de uma página web inteira).
  • CDummyCache: Componente disponível a partir da versão 1.0.5. Esse componente não realiza um caching de fato, pode ser considerado um cache falso. Possui o objetivo de simplificar o desenvolvimento de código que necessita trabalhar com dados em cache. Por exemplo, quando iniciamos o desenvolvimento de uma nova aplicação que precisa trabalhar com cache ou ainda quando o servidor atual não possui suporte a cache, podemos utilizar esse componente. Nos dois casos podemos utilizar o Yii::app()->cache->get($key)para recuperar dados do cache.

Todos os componentes citados são derivados da classe CCache, portanto você pode alterar o tipo de cache sem modificar o código que eles utilizam.

Existem vários níveis de caching, sendo o nível mais alto utilizado para armazenar dados que ocupam pouco espaço como, por exemplo, uma variável. Esse nível é comumente chamado de data caching. O nível seguinte é utilizado para armazenar partes de páginas, e o último nível possibilita o armazenamento de uma página inteira.

Data Caching

O caching de dados possibilita o armazenamento e a recuperação de variáveis PHP em cache. Para isso utilizamos a classe CCache que fornece os métodos get() e set().

Se quisermos, por exemplo, armazenar a variável $login em cache, devemos escolher um ID único e utilizar o método set() para armazená-lo, como mostra a Listagem 1.

Listagem 1. Exemplo de Data Caching.

Yii::app()->cache->set($id, $login);

Dessa forma, a variável $login deverá ficar em cache para sempre, no entanto, existem regras de caching que podem eliminá-lo da memória. A regra mais comum é quando o espaço reservado para o caching acaba e os dados começam a ser removidos de forma sequencial, dos mais antigos aos mais recentes. Para evitar que o espaço reservado para o caching acabe podemos, ao executar o método set(), especificar o tempo de expiração. Dessa forma, quando se cumprir o tempo passado por parâmetro, o dado será descartado da memória. Veja um exemplo na Listagem 2.

Listagem 2. Data Caching com parâmetro de duração.

// Mantém o valor no cache por 120 segundos ou seja, 2 minutos
  Yii::app()->cache->set($id, $login, 120);

Se porventura necessitarmos utilizar a variável $login em outro momento, utilizamos então o método get() informando o ID da variável desejada. O método get() retornará um booleano false caso a variável não esteja mais em cachê. Dessa forma será necessário armazená-la novamente, como na Listagem 3.

Listagem 3. Utilizando get() para recuperar variável.


  $value=Yii::app()->cache->get($id);
  if($value===false)
  {
              // armazena a variável $login novamente, pois, seu valor não foi encontrado
              Yii::app()->cache->set($id,$login);
  }

Um cuidado que deve ser tomado é a atribuição de IDs únicos, pois como o processo é manual, o desenvolvedor poderá passar IDs iguais, o que acarretará em um conflito no método get(). No entanto, vale lembrar que os IDs só deverão ser diferentes dentro da mesma aplicação e o componente de cache consegue diferenciar IDs de aplicações diferentes.

O MemCache e o APC são alguns exemplos de cache que suportam a recuperação em lote de vários valores ao mesmo tempo, reduzindo assim a sobrecarga desse processo. Utilizamos o método mget(), disponível a partir da versão 1.0.8, que explora essa funcionalidade. Esse método irá buscar o suporte nativo no sistema de cache e caso esse não exista, então ele irá simulá-lo.

Quando a variável não for mais necessária, pode-se utilizar o método delete() para apagá-la da cache. Para apagar toda a cache utilizamos o método flush(). Ele apaga não somente o da aplicação atual, mas também o de outras aplicações.

Trabalhando com ArrayAccess

O ArrayAccess é uma implementação da classe CCache, que possibilita a utilização de um vetor. Veja na Listagem 4 um exemplo de sua implementação.

Listagem 4. Utilizando cache com vetores.


  $cache=Yii::app()->cache;
  $cache['var1']=$value1; // equivalente a: $cache->set('var1',$value1);
  $value2=$cache['var2']; // equivalente a: $value2=$cache->get('var2');

Dependências do Cache

Existem alguns fatores que podem fazer com que os dados em cache devam ser descartados. Imaginemos o seguinte cenário: estamos trabalhando com cache para armazenar o login de um usuário a partir da variável $login; no entanto, o usuário resolve alterar os seus dados cadastrados no sistema e dentre outros, altera o seu login. Nesse momento a informação armazenada na variável em cache anteriormente tornar-se obsolets e necessita de atualização. Nesse cenário, o procedimento correto é a invalidação da variável em cache e a passagem novamente da variável $login para a memória em cache, isso é o que chamamos de dependência do cache, Para representarmos uma dependência instanciamos a CCacheDependency ou uma de suas classes derivadas. Então utilizamos o método set() para passar essa instância juntamente com os dados que devam ser armazenados. Veja como isso funciona na Listagem 5.

Listagem 5. Utilizando o CFIleCacheDependency para chegar as dependências do cache.


  // login irá expirar em 120 segundos
  // ele também deverá se tornar inválido se o conteúdo de 'ArquivoLogin' for alterado
  Yii::app()->cache->set($id, $login, 120, new CFileCacheDependency ('ArquivoLogin'));

Supomos que o sistema armazena os dados de login do usuário em um arquivo chamado 'ArquivoLogin'. Dessa forma, se esse arquivo for alterado, o valor de $login, alocado em cache, deverá ser anulado. Se utilizarmos o método get(), a dependência será verificada e, caso o arquivo tenha sido alterado, retornará um false, o que indicará que a informação precisa ser armazenada novamente.

Veja a seguir uma lista resumida com as dependências do cache:

  • CFileCacheDependency: a dependência é alterada caso a hora de última modificação do arquivo tenha sido alterada.
  • CDirectoryCacheDependency: a dependência é alterada se qualquer um dos arquivos ou subdiretórios do diretório informado sofrer alterações.
  • CDbCacheDependency: a dependência é alterada se o resultado da consulta informada sofre alterações.
  • CGlobalStateCacheDependency: a dependência é alterada se o valor do estado global informado for alterado. Um estado global é uma variável que é persistente entre múltiplas requisições e sessões de uma aplicação. Ele é definido através do método CApplication::setGlobalState().
  • CChainedCacheDependency: a dependência é alterada se qualquer uma das dependências na cadeia informada sofrer alteração.
  • CExpressionDependency: a dependência é alterada se o resultado da expressão PHP informada for alterado. Essa classe está disponível a partir da versão 1.0.4.

Caching de Fragmentos

Entendemos por caching de fragmentos o ato de armazenamento em cache de partes de uma página. Esse tipo de caching se torna muito útil quando trabalhamos com relatórios e ou resumos gerados pelo sistema, que requer tempo de processamento. Por exemplo, se uma determinada página exibe um relatório de crescimento da empresa no semestre, podemos armazenar a tabela com esses dados em cache, acelerando assim o tempo necessário para exibição da tela na próxima requisição.

Para a utilização do caching de fragmentos devemos utilizar os seguintes métodos na visão de um controle: CController::beginCache() e Ccontroller::endCache(), onde eles respondem respectivamente, pelo início e o fim do conteúdo que deve ser armazenado em cache. Da mesma forma do caching de dados, devemos passar um ID para identificar o fragmento da página armazenado. Veja um exemplo na Listagem 6.

Listagem 6. Exemplo de caching de fragmento.


  …código HTML anterior ao fragmento que você deseja armazenar...
  <?php if($this->beginCache($id)) { ?>
  …fragmento que deve ser armazenado...
  <?php $this->endCache(); } ?>
  ...código HTML posterior ao fragmento armazenado...

A listagem acima funciona da seguinte forma: o método beginCache() irá procurar se já existe algum fragmento armazenado com o ID passado por parâmetro - se o valor de retorno for false quer dizer que já existe algum fragmento armazenado com aquele ID, então o sistema não poderá adicionar um novo fragmento com aquele ID para não perder a redundância de dados. Então ao invés de armazenar novos dados em cache, o método beginCache() irá imprimir os dados já armazenados com aquele ID, caso contrário, o conteúdo dentro do if será executado e então todas as informações resultantes do código até o método endCache() serão armazenadas.

Duração

A opção mais utilizada no caching, sem sombra de dúvidas, é a duration, que indica por quanto tempo o conteúdo deve ser mantido em cache. Seu funcionamento é parecido com o parâmetro de tempo de duração do método CCache::set(). Na Listagem 7 armazenamos em cache um trecho de uma página web por duas horas.

Listagem 7. Caching de fragmento com parâmetro de duração.


  <html>
  <body>
   
  …continuação do código HTML anterior ao fragmento que você deseja armazenar...
   
  <?php if($this->beginCache($id, array('duration'=>7200))) { ?>
   
  …fragmento que deve ser armazenado...
   
  <?php $this->endCache(); } ?>
   
  ...continuação do código HTML posterior ao fragmento armazenado...
   
  </body>
  </html>

Se não passarmos como parâmetro a duração, por default, a duração será 60, indicando que o conteúdo em cache deve ser descartado depois de 60 segundos.

Dependência

Como já vimos no caching de dados, da mesma forma o caching de fragmentos pode ter dependências. Vejamos o seguinte cenário: um sistema de e-commerce possui uma ferramenta que gera para seus funcionários um relatório das vendas no mês, para acelerar a resposta à requisição do formulário, o sistema utiliza caching de fragmento nessa parte do código. No entanto, entre o momento que o fragmento do relatório foi armazenado e a próxima requisição foram feitas novas compras, então os dados necessitam ser atualizados na memória, para isso deve-se utilizar a opção dependency, que pode ser um objeto implementando a interface [IcacheDependency] para solicitar o descarte do fragmento antigo e seu novo armazenamento. Na Listagem 8 podemos ver esse processo de forma mais clara.

Listagem 8. Exemplo de dependência em caching de fragmentos.


  …continuação do código HTML anterior ao fragmento que você deseja armazenar...
  <?php if($this->beginCache($id, array('dependency'=>array(
  'class'=>'system.caching.dependencies.CDbCacheDependency',
  'sql'=>'SELECT MAX(lastModified) FROM Post')))) { ?>
   
  …fragmento que deve ser armazenado...
   
  <?php $this->endCache(); } ?>
   
  ...continuação do código HTML posterior ao fragmento armazenado...
   
  </body>
  </html>

A listagem acima específica, a partir de uma solicitação SQL ao Banco de Dados, que o fragmento depende da alteração do valor da coluna lastModified (última modificação).

Variação

Pensemos outro cenário: um determinado aplicativo de uma clinica médica possui duas possibilidades de login: o login para secretários(as) da clinica, que são responsáveis por organizar a agenda dos médicos, além de marcar e desmarcar consultas; e o login como médico, que proporcionará uma interface de visão da agenda do médico para o dia, tal como a possibilidade de solicitação de exames e etc.. Dessa forma, existe uma variação na interface mostrada para o usuário ao se logar. Assim, o conteúdo em cache irá sofrer variações de acordo com alguns parâmetros, nesse caso seria interessante que a cópia em cache varie de acordo com os IDs dos usuários.

O método CoutputCache fornece esse recurso, fazendo assim que não caia sobre o desenvolvedor a responsabilidade da variação desses IDs. Veja um resumo:

  • varyByRoute: mudando seu valor para true o conteúdo em érrfcache irá variar de acordo com a rota. Dessa forma, cada combinação de controle/ação terá seu conteúdo armazenado em cache separadamente.
  • varyBySelection: mudando seu valor para true podemos fazer com que o conteúdo em cache varie de acordo com os IDs da sessão. Dessa forma, cada sessão de usuário pode ter conteúdos diferentes e todos servidos através do cache.
  • varyByParam: ao atribuir um vetor de nomes a essa opção, podemos fazer com que o conteúdo do cache varie de acordo com valores passados através de GET. Por exemplo, se uma página exibe o conteúdo de um post, de acordo com a variável GET id podemos definir varyByParam como array('id'), assim podemos armazenar em cache o conteúdo de cada post. Sem esse tipo de variação poderíamos apenas armazenar um único post.
  • varyByExpression: ao atribuir uma expressão PHP a essa opção, podemos fazer com que o conteúdo do cache varie de acordo com o resultado dessa expressão. Essa opção está disponível a partir da versão 1.0.4.

Tipos de Requisição

Outra possibilidade muito útil ao trabalharmos com caching de fragmentos é o fato de podermos controlar os tipos de requisições que poderão ser armazenadas em cache. Por exemplo, um determinado sistema exibe um formulário, mas não queremos que esse formulário seja armazenado todas as vezes que é requisitado, mas somente na primeira vez (via GET). Nas exibições seguintes (via POST), o formulário não deve ser armazenado, pois o mesmo pode conter os dados informados pelos usuários. Para isso utilizamos a opção requestTypes, como mostra a Listagem 9.

Listagem 9. Caching controlado por tipo de tipo de requisição.


  <html>
  <body>
   
  …continuação do código HTML anterior ao fragmento que você deseja armazenar ...
   
  <?php if($this->beginCache($id, array('requestTypes'=>array('GET')))) { ?>
   
  …fragmento que deve ser armazenado ...
   
  <?php $this->endCache(); } ?>
   
  ...continuação do código HTML posterior ao fragmento armazenado...
   
  </body>
  </html>

Caching Aninhados

Assim como em uma linguagem de programação podemos ter comandos de condição aninhados, ou seja, um comando dentro do outro. Por exemplo, um if que depende de outro, como na Listagem 10.

Listagem 10. Estrutura de controle aninhada.


  $var1 = 2;
  $var2 = 15;
  $var3 = 30;
   
  if($var1 <= 10){
              if(($var1*$var2) == $var3){
                          echo “Seja um assinante MVP!”;
              } eles {
                          echo “Você perdeu uma grande oportunidade”;
              }
  } else {
              echo “Isso é um exemplo de comandos aninhados”;
  }

Assim como no exemplo acima, o caching de fragmentos pode ser aninhado. Ou seja, um fragmento em cache pode estar dentro de outro fragmento, também em cache. Por exemplo, os comentários estão em cache, junto do conteúdo do post que também está em cache. Veja na Listagem 11 um exemplo de caching de fragmentos aninhado.

Listagem 11. Exemplo de caching de fragmentos aninhado.


  <html>
  <body>
   
  …continuação do código HTML anterior ao fragmento que você deseja armazenar ...
   
  <?php if($this->beginCache($id1)) { ?>
   
  …fragmento externo que deve ser armazenado...
   
  <?php if($this->beginCache($id2)) { ?>
   
  …fragmento interno que deve ser armazenado...
   
  <?php $this->endCache(); } ?>
   
  … fragmento interno que deve ser armazenado ...
   
  <?php $this->endCache(); } ?>
   
  … fragmento interno que deve ser armazenado …
  </body>
  </html>

Ao utilizarmos caches aninhados podemos utilizar diferentes opções. Podemos passar parâmetros diferentes para os caches internos e externos, mudando, por exemplo, o tempo de duração de ambos.

Caching de Páginas

Já vimos que é possível armazenar em cache apenas uma variável, e parte de uma página. Além dos dois casos anteriores podemos também armazenar todo o conteúdo de uma página. A essa técnica chamamos de caching de página, que pode ser considerado um caso especial de caching de fragmentos. Geralmente o conteúdo de uma página é gerado aplicando-se um layout a uma visão, dessa forma o caching de página não irá funcionar apenas utilizando os métodos beginCache() e endCache() no layout. Isso porque o layout é aplicado dentro do método CController::render() depois que a visão é gerada. Então para armazenarmos a página inteira, devemos pular a execução da ação que gera o conteúdo da página. Para isso, utilizamos o COutputCache como um filtro. A Listagem 12 mostra como configuramos o caching de páginas.

Listagem 12. Configuração do caching de páginas.


  public function filters()
  {
              return array(
                          array(
                                     'COutputCache',
                                     'duration'=>100,
                                     'varyByParam'=>array('id'),
                          ),
              );
  }

Conteúdo Dinâmico

Ao utilizarmos caching de fragmentos e caching de páginas nos deparamos com o fato de que muitas vezes todo o conteúdo de uma página é estático, exceto em algumas pequenas partes. Um exemplo é quando estamos logados em algum sistema e solicitamos a página de ajuda, que sempre virá com o mesmo conteúdo, no entanto, para cada usuário apresentará o seu nome no topo.

Para resolver isso poderíamos dividir a página em vários fragmentos e armazena-los individualmente, mas isso tornaria nossa visão mais complexa. A forma correta é a utilização do recurso de conteúdo dinâmico fornecido pela classe CController.

Conteúdo dinâmico é todo fragmento que não deve ser armazenado, mesmo que o conteúdo do qual ele faz parte seja armazenado em cache. Para deixar um conteúdo dinâmico para sempre, ele deve ser gerado todas às vezes, mesmo quando o conteúdo é provido pelo cache. Para isso precisamos garantir que o conteúdo seja gerado por algum método ou função.

O método CController::renderDynamic() nos fornece a função de inserir o conteúdo dinâmico no

local desejado. Veja a Listagem 13.

Listagem 13. Exemplo de utilização de cache com conteúdo dinâmico.


  ...outro conteúdo HTML...
  <?php if($this->beginCache($id)) { ?>
  ...fragmento que deve ser armazenado em cache...
  <?php $this->renderDynamic($callback); ?>
  ...fragmento que deve ser armazenado em cache...
  <?php $this->endCache(); } ?>
  ...outro conteúdo HTML...

Na listagem acima $callback é um callback válido em PHP. Ele pode ser do tipo string com o nome de um método na classe do controle ou uma função global. Ele também pode ser um vetor indicando um método na classe. Qualquer parâmetro adicional para o método renderDynamic() será passado para o callback que deve retornar o conteúdo dinâmico em vez de exibi-lo.

Finalizamos assim esse artigo sobre a utilização de caching, objetivando o aumento no desempenho de aplicações webs desenvolvidas com o Framework Yii. A partir de agora poderemos turbinar nossas aplicações e obtermos resultados mais expressivos. Espero que tenham gostado.

Até a próxima!

Referências

http://www.yiiframework.com/tutorials/

http://www.yiiframework.com/doc/api/