Memcached.php 4.72 KB
Newer Older
Ketan's avatar
Ketan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Framework\Cache\Backend;

/**
 * Memcached cache model
 */
class Memcached extends \Zend_Cache_Backend_Memcached implements \Zend_Cache_Backend_ExtendedInterface
{
    /**
     * Maximum chunk of data that could be saved in one memcache cell (1 MiB)
     */
    const DEFAULT_SLAB_SIZE = 1048576;

    /**
     * Used to tell chunked data from ordinary
     */
    const CODE_WORD = '{splitted}';

    /**
     * Constructor
     *
     * @param array $options @see \Zend_Cache_Backend_Memcached::__construct()
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function __construct(array $options = [])
    {
        parent::__construct($options);

        if (!isset($options['slab_size']) || !is_numeric($options['slab_size'])) {
            if (isset($options['slab_size'])) {
                throw new \Magento\Framework\Exception\LocalizedException(
                    new \Magento\Framework\Phrase(
                        "Invalid value for the node <slab_size>. Expected to be positive integer."
                    )
                );
            }

            $this->_options['slab_size'] = self::DEFAULT_SLAB_SIZE;
        } else {
            $this->_options['slab_size'] = $options['slab_size'];
        }
    }

    /**
     * Returns ID of a specific chunk on the basis of data's ID
     *
     * @param string $id    Main data's ID
     * @param int $index Particular chunk number to return ID for
     * @return string
     */
    protected function _getChunkId($id, $index)
    {
        return "{$id}[{$index}]";
    }

    /**
     * Remove saved chunks in case something gone wrong (e.g. some chunk from the chain can not be found)
     *
     * @param string $id     ID of data's info cell
     * @param int $chunks Number of chunks to remove (basically, the number after '{splitted}|')
     * @return null
     */
    protected function _cleanTheMess($id, $chunks)
    {
        for ($i = 0; $i < $chunks; $i++) {
            $this->remove($this->_getChunkId($id, $i));
        }

        $this->remove($id);
    }

    /**
     * Save data to memcached, split it into chunks if data size is bigger than memcached slab size.
     *
     * @param string $data             @see \Zend_Cache_Backend_Memcached::save()
     * @param string $id               @see \Zend_Cache_Backend_Memcached::save()
     * @param string[] $tags           @see \Zend_Cache_Backend_Memcached::save()
     * @param bool $specificLifetime   @see \Zend_Cache_Backend_Memcached::save()
     * @return bool
     */
    public function save($data, $id, $tags = [], $specificLifetime = false)
    {
        if (is_string($data) && strlen($data) > $this->_options['slab_size']) {
            $dataChunks = str_split($data, $this->_options['slab_size']);

            for ($i = 0, $count = count($dataChunks); $i < $count; $i++) {
                $chunkId = $this->_getChunkId($id, $i);

                if (!parent::save($dataChunks[$i], $chunkId, $tags, $specificLifetime)) {
                    $this->_cleanTheMess($id, $i + 1);
                    return false;
                }
            }

            $data = self::CODE_WORD . '|' . $i;
        }

        return parent::save($data, $id, $tags, $specificLifetime);
    }

    /**
     * Load data from memcached, glue from several chunks if it was splitted upon save.
     *
     * @param string $id                     @see \Zend_Cache_Backend_Memcached::load()
     * @param bool $doNotTestCacheValidity @see \Zend_Cache_Backend_Memcached::load()
     * @return bool|false|string
     */
    public function load($id, $doNotTestCacheValidity = false)
    {
        $data = parent::load($id, $doNotTestCacheValidity);

        if (is_string($data) && substr($data, 0, strlen(self::CODE_WORD)) == self::CODE_WORD) {
            // Seems we've got chunked data

            $arr = explode('|', $data);
            $chunks = isset($arr[1]) ? $arr[1] : false;
            $chunkData = [];

            if ($chunks && is_numeric($chunks)) {
                for ($i = 0; $i < $chunks; $i++) {
                    $chunk = parent::load($this->_getChunkId($id, $i), $doNotTestCacheValidity);

                    if (false === $chunk) {
                        // Some chunk in chain was not found, we can not glue-up the data:
                        // clean the mess and return nothing

                        $this->_cleanTheMess($id, $chunks);
                        return false;
                    }

                    $chunkData[] = $chunk;
                }

                return implode('', $chunkData);
            }
        }

        // Data has not been splitted to chunks on save
        return $data;
    }
}