matthiasmullie / scrapbook

PHP cache library, with adapters for e.g. Memcached, Redis, Couchbase, APC(u), SQL and additional capabilities (e.g. transactions, stampede protection) built on top.
https://www.scrapbook.cash
MIT License
315 stars 27 forks source link

Compression #39

Closed darkalchemy closed 5 years ago

darkalchemy commented 5 years ago

Is there an easy way to implement data compression before storing?

I am thinking I can create new methods that would compress before and decompress after. But, I am looking for a simpler method.

Thanks

matthiasmullie commented 5 years ago

Hey @darkalchemy

One of the things I really like about the Scrapbook architecture, is that you can just create another class that wraps over the rest, like how most of the features are currently implemented.

You could simply create another class that will relay all function calls to the actual storage, but (de)compresses first. Something like this (untested, might contain syntax errors):

class Compressor implements \MatthiasMullie\Scrapbook\KeyValueStore
{
    /**
     * @var KeyValueStore
     */
    protected $cache;

    /**
     * @param KeyValueStore $cache
     */
    public function __construct(KeyValueStore $cache)
    {
        $this->cache = $cache;
    }

    /**
     * {@inheritdoc}
     */
    public function get($key, &$token = null)
    {
        $value = $this->cache->get($key, $token);
        return $this->decompress($value);
    }

    /**
     * {@inheritdoc}
     */
    public function getMulti(array $keys, array &$tokens = null)
    {
        $values = $this->cache->getMulti($keys, $tokens);
        return array_map(function($value) {
            return $this->decompress($value);
        }, $values);
    }

    /**
     * {@inheritdoc}
     */
    public function set($key, $value, $expire = 0)
    {
        $value = $this->compress($value);
        return $this->cache->set($key, $value, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function setMulti(array $items, $expire = 0)
    {
        $items = array_map(function($value) {
            return $this->compress($value);
        }, $items);
        return $this->cache->setMulti($items, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function delete($key)
    {
        return $this->cache->delete($key);
    }

    /**
     * {@inheritdoc}
     */
    public function deleteMulti(array $keys)
    {
        return $this->cache->deleteMulti($keys);
    }

    /**
     * {@inheritdoc}
     */
    public function add($key, $value, $expire = 0)
    {
        $value = $this->compress($value);
        return $this->cache->add($key, $value, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function replace($key, $value, $expire = 0)
    {
        $value = $this->compress($value);
        return $this->cache->replace($key, $value, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function cas($token, $key, $value, $expire = 0)
    {
        $value = $this->compress($value);
        return $this->cache->cas($token, $key, $value, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function increment($key, $offset = 1, $initial = 0, $expire = 0)
    {
        // @todo might have to go through uncompressed?
        return $this->cache->increment($key, $offset, $initial, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
    {
        // @todo might have to go through uncompressed?
        return $this->cache->decrement($key, $offset, $initial, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function touch($key, $expire)
    {
        return $this->cache->touch($key, $expire ?: $this->defaultExpire);
    }

    /**
     * {@inheritdoc}
     */
    public function flush()
    {
        return $this->cache->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function getCollection($name)
    {
        return $this->cache->getCollection($name);
    }

    /**
     * @param string $value
     * @return string
     */
    protected function compress($value)
    {
        // @todo gzencode expects string input
        // might be ok for your use, or you might have to force $value to string,
        // or implement a completely different compression
        return gzencode($value, $level, FORCE_GZIP);
    }

    /**
     * @param string $value
     * @return string
     */
    protected function decompress($value)
    {
        // @todo same note about strings
        return gzdecode($value);
    }
}

You would then use it like this:

// create \Memcached object pointing to your Memcached server
$client = new \Memcached();
$client->addServer('localhost', 11211);
// create Scrapbook cache object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);
// add compression layer on top
$cache = new Compressor($cache);
darkalchemy commented 5 years ago

Thanks!