Fork me on GitHub
/ Sweet home / Blog / Archives / Un cache disque pour doctrine /

Un cache disque pour doctrine

Last update: 2011-03-02 02:14:37

A moins que je ne sois passé à coté de quelque chose, Doctrine n'est pas fourni en standard avec un driver disque pour son cache. On peut comprendre, dans la mesure où un cache APC ou Memcached est forcément plus rapide. Pour autant on n'a pas forcément accès à ces caches sur tous les hébergements. Doctrine permet également la mise en cache dans une base de donnée (SQLite par exemple) mais là encore les extensions nécessaires ne sont pas forcément chargées. Voilà de quoi y remédier en utilisant un cache disque. D'abord une classe pour mettre en cache sur le disque:
class md_Cache
{
    private $cacheDir;

    public function __construct($cacheDir, $checkDir = true)
    {
        $slash = substr($cacheDir,-1);
        if($slash != PATH_SEPARATOR)
        {
            $cacheDir .= PATH_SEPARATOR;
        }
        if($checkDir)
        {
            if(!is_dir($cacheDir))
            {
                if(!mkdir($cacheDir, 0755, true))
                {
                    throw new Exception('directory "'
                                        . $cacheDir
                                        . '" does ot exist and could not be created.');
                }
            }
        }
        $this->cacheDir = $cacheDir;
    }

    public function fetch($key)
    {
        $paths = $this->mkPaths($key);
        if(!is_file($paths['meta']))
        {
            return false;
        }
        $metas = unserialize(file_get_contents($paths['meta']));
        if($metas['expires'] < time() and $metas['expires'] != 0)
        {
            unlink($paths['meta']);
            unlink($paths['cache']);
            return false;
        }
        return unserialize(file_get_contents($paths['cache']));
    }

    public function store($key, $value, $ttl = 0)
    {
        $paths = $this->mkPaths($key);
        if($ttl == 0)
        {
            $expires = 0;
        }
        else
        {
            $expires = time() + (int)$ttl;
        }
        file_put_contents($paths['cache'], serialize($value));
        file_put_contents($paths['meta'], serialize(array(
            'key'     => $key,
            'expires' => $expires,
            'file'    => basename($paths['cache'])
        )));
        return $value;
    }

    public function delete($key)
    {
        $paths = $this->mkPaths($key);
        if(unlink($paths['meta']) and unlink($paths['cache']))
        {
            return true;
        }
        return false;
    }

    private function mkPaths($key)
    {
        $hash = md5($key);
        return array(
            'meta'  => $this->cacheDir . $hash . '.meta',
            'cache' => $this->cacheDir . $hash . '.cache'
        );
    }

    public static function cacheInfos()
    {
        $metaFiles = glob($this->cacheDir . '*.meta');
        $metas = array();
        foreach($metaFiles as $metaFile)
        {
            $metas[] = unserialize(file_get_contents($metaFile));
        }
        return $metas;
    }

    public static function clear($removeDirectory = true)
    {
        $infos = $this->cacheInfos();
        foreach($infos as $info)
        {
            $this->delete($info['key']);
        }
        if($removeDirectory)
        {
            return rmdir($this->cacheDir);
        }
        return true;
    }
}
Ensuite le driver pour Doctrine:
class md_Cache_Doctrine_Disk extends Doctrine_Cache_Driver
{
    private $mdCache;

    public function __construct($options = array())
    {
        if(!isset($options['cacheDir']))
        {
            throw new Exception('option cacheDir must be set.');
        }
        if(!isset($options['checkDir']))
        {
            $options['checkDir'] = true;
        }
        $this->mdCache = new md_Cache($options['cacheDir'], $options['checkDir']);
        parent::__construct($options);
    }


    public function fetch($id, $testCacheValidity = true)
    {
        return $this->mdCache->fetch($id);
    }


    public function contains($id)
    {
        return $this->mdCache->fetch($id) === false ? false : true;
    }

    public function save($id, $data, $lifeTime = 0)
    {
        return (bool) $this->mdCache->store($id, $data, $lifeTime);
    }


    public function delete($id)
    {
        return $this->mdCache->delete($id);
    }
}
On peut ensuite ajouter le cache par défaut à Doctrine au moment du bootstrap:
Doctrine_Manager::getInstance()
                    ->setAttribute(
                        Doctrine::ATTR_RESULT_CACHE,
                        new md_Cache_Doctrine_Disk
                            (
                                array('cacheDir' => CACHE_DOCTRINE)
                            )
                        );
Et finalement utiliser le cache dans les requêtes:
Doctrine_Query::create()
            ->from('User u')
            ->useResultCache(true)
            ->setResultCacheLifeSpan(3600)
            ->fetchArray();

<< PIMP: PHP Image Manipulation Program ?
Doctrine Event Listener >>
 

Comment this