Alguns ElePHPants na praia

Criando um sistema de cache no PHP

“Cache” é uma forma de armazenar um valor para um consulta futura mais rápida. Com o cache conseguimos otimizar o carregamento dos sites e de suas informações.

Suponhamos que você tenha um site que faça uma consulta em um tabela do banco de dados que possua 3.000.000 registros e essa consulta demore mais de 30 segundos (acredite, isso acontece). Com o cache você pode reduzir esse tempo em alguns segundos.

Cachear uma informação significa salvá-la em algum lugar (seja em um arquivo ou diretamente na memória RAM do servidor) para depois poder consultar essa informação sem ter que obtê-la da forma mais demorada (no exemplo a cima, com a consulta ao banco de dados).

Vamos criar aqui uma classe que servirá para armazenar qualquer tipo de texto, variável, número inteiro, resultado SQL e etc.

Para começar, começamos criando uma classe vazia:

<?php

/**
 * Sistema de cache
 *
 * @author Thiago Belem <contato@thiagobelem.net>
 * @link http://blog.thiagobelem.net/
 */
class Cache {

}

?>

Agora vamos adicionar alguns atributos que serão usados pelo sistema de cache:

	/**
	 * Tempo padrão de cache
	 *
	 * @var string
	 */
	private static $time = '5 minutes';

	/**
	 * Local onde o cache será salvo
	 *
	 * Definido pelo construtor
	 *
	 * @var string
	 */
	private $folder;

O atributo $time define por quanto tempo as informações ficarão salvas no cache, tempo esse que poderá ser mudado para cada valor salvo (veremos mais a diante).

Agora vamos criar um método chamado setFolder() que servirá para definir o local onde os arquivos de cache serão salvos:

	/**
	 * Define onde os arquivos de cache serão salvos
	 *
	 * Irá verificar se a pasta existe e pode ser escrita, caso contrário
	 * uma mensagem de erro será exibida
	 *
	 * @param string $folder Local para salvar os arquivos de cache (opcional)
	 *
	 * @return void
	 */
	protected function setFolder($folder) {
		// Se a pasta existir, for uma pasta e puder ser escrita
		if (file_exists($folder) && is_dir($folder) && is_writable($folder)) {
			$this->folder = $folder;
		} else {
			trigger_error('Não foi possível acessar a pasta de cache', E_USER_ERROR);
		}
	}

Esse método recebe o caminho (pasta) onde os arquivos serão criados e, após verificar se o caminho existe, é um diretório e pode ser manipulado, ele define um atributo com o caminho passado. Caso ele não consiga localizar a pasta ou não seja possível escrever nela, um erro será gerado.

Com esse método criado, podemos criar um construtor para essa classe com o seguinte código:

	/**
	 * Construtor
	 *
	 * Inicializa a classe e permite a definição de onde os arquivos
	 * serão salvos. Se o parâmetro $folder for ignorado o local dos
	 * arquivos temporários do sistema operacional será usado
	 *
	 * @uses Cache::setFolder() Para definir o local dos arquivos de cache
	 *
	 * @param string $folder Local para salvar os arquivos de cache (opcional)
	 *
	 * @return void
	 */
	public function __construct($folder = null) {
		$this->setFolder(!is_null($folder) ? $folder : sys_get_temp_dir());
	}

O construtor será chamado sempre que instanciarmos a classe Cache e, como você pode ver, ele recebe um parâmetro (opcional) onde podemos definir o local onde os arquivos serão criados… Se não passarmos nenhum parâmetro para ele o mesmo irá usar o local de arquivos temporários definido pelo seu sistema operacional.

Agora que já conseguimos definir o local onde os caches serão salvos, vamos criar o método que irá gerar o nome dos arquivos de cache:

	/**
	 * Gera o local do arquivo de cache baseado na chave passada
	 *
	 * @param string $key Uma chave para identificar o arquivo
	 *
	 * @return string Local do arquivo de cache
	 */
	protected function generateFileLocation($key) {
		return $this->folder . DIRECTORY_SEPARATOR . sha1($key) . '.tmp';
	}

E o método que irá criar o arquivo de cache propriamente dito:

	/**
	 * Cria um arquivo de cache
	 *
	 * @uses Cache::generateFileLocation() para gerar o local do arquivo de cache
	 *
	 * @param string $key Uma chave para identificar o arquivo
	 * @param string $content Conteúdo do arquivo de cache
	 *
	 * @return boolean Se o arquivo foi criado
	 */
	protected function createCacheFile($key, $content) {
		// Gera o nome do arquivo
		$filename = $this->generateFileLocation($key);

		// Cria o arquivo com o conteúdo
		return file_put_contents($filename, $content)
			OR trigger_error('Não foi possível criar o arquivo de cache', E_USER_ERROR);
	}

O nosso sistema está quase pronto.. Já podemos criar arquivos de cache na pasta de cache, precisamos então criar dois métodos: um para salvar um valor no cache (seja ele uma string, número, resultado SQL e etc.) e outro pra ler esse valor do cache.

Primeiro o método que salva um valor no cache:

	/**
	 * Salva um valor no cache
	 *
	 * @uses Cache::createCacheFile() para criar o arquivo com o cache
	 *
	 * @param string $key Uma chave para identificar o valor cacheado
	 * @param mixed $content Conteúdo/variável a ser salvo(a) no cache
	 * @param string $time Quanto tempo até o cache expirar (opcional)
	 *
	 * @return boolean Se o cache foi salvo
	 */
	public function save($key, $content, $time = null) {
		$time = strtotime(!is_null($time) ? $time : self::$time);

		$content = serialize(array(
			'expires' => $time,
			'content' => $content));

		return $this->createCacheFile($key, $content);
	}

E agora o método para ler esse valor do cache:

	/**
	 * Salva um valor do cache
	 *
	 * @uses Cache::generateFileLocation() para gerar o local do arquivo de cache
	 *
	 * @param string $key Uma chave para identificar o valor cacheado
	 *
	 * @return mixed Se o cache foi encontrado retorna o seu valor, caso contrário retorna NULL
	 */
	public function read($key) {
		$filename = $this->generateFileLocation($key);
		if (file_exists($filename) && is_readable($filename)) {
			$cache = unserialize(file_get_contents($filename));
			if ($cache['expires'] > time()) {
				return $cache['content'];
			} else {
				unlink($filename);
			}
		}
		return null;
	}

Se você reparar, esse último método irá excluir o arquivo de cache caso ele tenha expirado.

Usando o sistema de cache

Veja um exemplo de uso do sistema de cache onde primeiro verificamos se há um valor armazenado no cache e, se não houver, geramos o valor novamente e salvamos ele no cache para futuras verificações:

// Verifica se a frase já está no cache
$cache = new Cache();
$frase = $cache->read('frase-dia');
// Se não houver frase no cache ou já tiver expirado
if (!$frase) {
	// Cria uma nova frase e salva-a no cache por 30s
	$frase = 'CALMA! O sábio jamais se aborrece ('. date('H:i:s') .')';
	$cache->save('frase-dia', $frase, '30 seconds');
}
echo "<p>{$frase}</p>";

Veja o código-fonte completo da classe: http://pastebin.com/p4m0CpwH

Um grande abraço e até a próxima! :)

26 thoughts on “Criando um sistema de cache no PHP

  1. Fernando

    Olá Thiago. Primeiramente, parabéns pelo tutorial. Bem legal e simples. Porém, gostaria de pedir uma ajuda: Eu defini a variável folder assim: private $folder = ‘/cache/’;
    Sendo que “cache” é uma pasta que criei na mesma pasta onde ficam os arquivos do site. Só que não funciona. Eu percebo que ele cria o cache, mas não nessa pasta. O que está errado?

    Ah, tentei também ‘cache’, ‘/cache’, ‘cache/’ mas nada funcionou. Obrigado.

  2. Helison santos

    Teste seu exemplo, muito bom, mais no meu caso só esta funcionado qdo o tempo expira, como ficaria essa parte para verificação do conteúdo if ($cache['expires'] > time()) {…}

    Desde agradeço…

  3. Helison santos

    Gostei muito do post, li e apliquei os codigos tentando entender o que cada linha faz, não função rea() ele verifica o tempo e se esse for falso ele exclui o arquivo.
    Como ficaria a função read() para verificar se o conteúdo é igual ou não,
    Abraço.

  4. Helison santos

    Ola ótimo post,
    Vi o exemplo de armazenar uma query, mais se eu fosse fazer um loop while etc como ficaria?
    Desde já agradeço

  5. Marcelo Fabiano

    Ótimo tutorial Thiago !!!!
    queria só fazer uma pergunta eu tenho um arquivo XML e não consigo guardar ele em Cache utilizando esta classe…
    Mensagem de Erro:
    SimpleXMLElement ‘não é permitido

  6. Pingback: Power Php - Dicas Atuais sobre o mundo php!

  7. Irineu M Junior

    Thiago… fiz as seguintes alterações na sua classe…
    Troquei o protected por public ja que não conseguia setar a pasta.

    - protected function setFolder($folder) {
    + public function setFolder($folder) {

    E ao invés de mostrar o erro de “Pasta não encontrada” eu a crio.
    -trigger_error(‘Não foi possível acessar a pasta de cache’, E_USER_ERROR);
    +mkdir($folder, 0755, TRUE);
    +$this->folder = $folder;

  8. Celso Junior

    Ótimo script, mas tenho uma dúvida:
    Como faço para implementar essa sistema em uma consulta MySQL? Me mostre um exemplo bem simples que dispensa a execução de query toda vez que a página é carregada.

    Já tentei de diversas formas e não consegui. :)

  9. Lucas Pacheco

    cara, exelento o tutorial!(Y)
    http://csscreator.com/divitis dê uma olhada neste link, dá pra escrever alguma coisa sobre isso;;;
    sei que aqui a voltado para desenvolvimento, mas o artigo é bem interessante, e vale a pena ser explorado… eu mesmo percebi que eu fazia isto…

  10. Leandro

    Mas Thiago ver se você concorda, o cara vai e faz uma consulta onde verificar o numero de registros em uma tabela SQL, poderia então fazer com que isso seja mais util pra conteudo que vive mudando, ao invez cachear pelo tempo que seria realmente bom para sites/blogs.

  11. Leandroprietorj

    rapaz, OTIMO sistema
    programo em ASP a alguns anos e sempre tentava aplicar o uso de paginas como XML, mas essa aplicação de CACHE é muito melhor – ate onde vi…
    estou engatinhando no PHP e migrando um portal de grande porte de ASP para PHP; não conheco bem as funções, mas estou me virando como posso

    so não entendi muito bem como funciona…
    posso cachear uma noticia por exemplo – $frase = $cache->read(‘noticia_id12′);
    nessa linha ele verifica se há um cache para noticia_id12???
    caso não haja entra no if…
    if (!$frase){
    conn_BD()
    mysql – select…
    $frase = ‘meu bloco de texto montado’;
    $cache->save(‘noticia_id12′, $frase, ’30 seconds’);
    }

    é assim que ele funciona???

    aguardo essa ajudinha, pois tenho outras ideias para a aplicação desse CACHE
    muito obrigado por compartilhar :)

  12. Thiago Belem

    A questão é que se ele verificar se o conteúdo do cache está igual ao
    “novo”, isso significa que você gerou o conteúdo novo, ignorando o propósito
    do cache que é NÃO GERAR o conteúdo cada vez que o mesmo for necessário e
    sim usar o conteúdo que está no cache enquanto ele não expirar.

  13. Ricardoendres

    Entendi. Você me indicaria alguma classe em que o cache levaria em conta tbm o conteudo? ou alguma adaptação no a que ele fique assim

    Desde já agradeço

  14. Ricardoendres

    Sim, mas passado o tempo estipulado, no caso do exemplo 30 segundos, ele substitui/atualiza o cache, mesmo sendo um arquivo igual?

  15. Ricardoendres

    Outra coisa que estava pensando…
    Como poderia fazer uma comparação se o cache existente é igual ao novo. Pois se é igual não tem necessidade atualizar o cache…

    se cache atual for igual ao cache antigo, nao atualizar, caso contrario e se o prazo expirou, atualize

    Ha esta possibilidade?

  16. Ricardoendres

    Com esta classe de cache, eu poderia cachear partes sim e partes nao, de um código html?
    Se sim, qual o procedimento? Se nao, qual classe vc me indicaria?

    Desde ja agradeço

  17. Irineu Martins Junior

    Nossa… eu tenho um pouco de medo quando se fala em cachear html.
    Sempre criamos cache das imagens, porque vira e mexe elas são necessarias em tamanhos diferentes.
    Vou estudar um pouco mais sobre esta classe…
    Valeu Thiago

Comments are closed.