Memcached.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Cache\Backend;
  7. /**
  8. * Memcached cache model
  9. */
  10. class Memcached extends \Zend_Cache_Backend_Memcached implements \Zend_Cache_Backend_ExtendedInterface
  11. {
  12. /**
  13. * Maximum chunk of data that could be saved in one memcache cell (1 MiB)
  14. */
  15. const DEFAULT_SLAB_SIZE = 1048576;
  16. /**
  17. * Used to tell chunked data from ordinary
  18. */
  19. const CODE_WORD = '{splitted}';
  20. /**
  21. * Constructor
  22. *
  23. * @param array $options @see \Zend_Cache_Backend_Memcached::__construct()
  24. * @throws \Magento\Framework\Exception\LocalizedException
  25. */
  26. public function __construct(array $options = [])
  27. {
  28. parent::__construct($options);
  29. if (!isset($options['slab_size']) || !is_numeric($options['slab_size'])) {
  30. if (isset($options['slab_size'])) {
  31. throw new \Magento\Framework\Exception\LocalizedException(
  32. new \Magento\Framework\Phrase(
  33. "Invalid value for the node <slab_size>. Expected to be positive integer."
  34. )
  35. );
  36. }
  37. $this->_options['slab_size'] = self::DEFAULT_SLAB_SIZE;
  38. } else {
  39. $this->_options['slab_size'] = $options['slab_size'];
  40. }
  41. }
  42. /**
  43. * Returns ID of a specific chunk on the basis of data's ID
  44. *
  45. * @param string $id Main data's ID
  46. * @param int $index Particular chunk number to return ID for
  47. * @return string
  48. */
  49. protected function _getChunkId($id, $index)
  50. {
  51. return "{$id}[{$index}]";
  52. }
  53. /**
  54. * Remove saved chunks in case something gone wrong (e.g. some chunk from the chain can not be found)
  55. *
  56. * @param string $id ID of data's info cell
  57. * @param int $chunks Number of chunks to remove (basically, the number after '{splitted}|')
  58. * @return null
  59. */
  60. protected function _cleanTheMess($id, $chunks)
  61. {
  62. for ($i = 0; $i < $chunks; $i++) {
  63. $this->remove($this->_getChunkId($id, $i));
  64. }
  65. $this->remove($id);
  66. }
  67. /**
  68. * Save data to memcached, split it into chunks if data size is bigger than memcached slab size.
  69. *
  70. * @param string $data @see \Zend_Cache_Backend_Memcached::save()
  71. * @param string $id @see \Zend_Cache_Backend_Memcached::save()
  72. * @param string[] $tags @see \Zend_Cache_Backend_Memcached::save()
  73. * @param bool $specificLifetime @see \Zend_Cache_Backend_Memcached::save()
  74. * @return bool
  75. */
  76. public function save($data, $id, $tags = [], $specificLifetime = false)
  77. {
  78. if (is_string($data) && strlen($data) > $this->_options['slab_size']) {
  79. $dataChunks = str_split($data, $this->_options['slab_size']);
  80. for ($i = 0, $count = count($dataChunks); $i < $count; $i++) {
  81. $chunkId = $this->_getChunkId($id, $i);
  82. if (!parent::save($dataChunks[$i], $chunkId, $tags, $specificLifetime)) {
  83. $this->_cleanTheMess($id, $i + 1);
  84. return false;
  85. }
  86. }
  87. $data = self::CODE_WORD . '|' . $i;
  88. }
  89. return parent::save($data, $id, $tags, $specificLifetime);
  90. }
  91. /**
  92. * Load data from memcached, glue from several chunks if it was splitted upon save.
  93. *
  94. * @param string $id @see \Zend_Cache_Backend_Memcached::load()
  95. * @param bool $doNotTestCacheValidity @see \Zend_Cache_Backend_Memcached::load()
  96. * @return bool|false|string
  97. */
  98. public function load($id, $doNotTestCacheValidity = false)
  99. {
  100. $data = parent::load($id, $doNotTestCacheValidity);
  101. if (is_string($data) && substr($data, 0, strlen(self::CODE_WORD)) == self::CODE_WORD) {
  102. // Seems we've got chunked data
  103. $arr = explode('|', $data);
  104. $chunks = isset($arr[1]) ? $arr[1] : false;
  105. $chunkData = [];
  106. if ($chunks && is_numeric($chunks)) {
  107. for ($i = 0; $i < $chunks; $i++) {
  108. $chunk = parent::load($this->_getChunkId($id, $i), $doNotTestCacheValidity);
  109. if (false === $chunk) {
  110. // Some chunk in chain was not found, we can not glue-up the data:
  111. // clean the mess and return nothing
  112. $this->_cleanTheMess($id, $chunks);
  113. return false;
  114. }
  115. $chunkData[] = $chunk;
  116. }
  117. return implode('', $chunkData);
  118. }
  119. }
  120. // Data has not been splitted to chunks on save
  121. return $data;
  122. }
  123. }