RedisCache.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. namespace Doctrine\Common\Cache;
  3. use Redis;
  4. use function array_combine;
  5. use function array_diff_key;
  6. use function array_fill_keys;
  7. use function array_filter;
  8. use function array_keys;
  9. use function count;
  10. use function defined;
  11. use function extension_loaded;
  12. use function is_bool;
  13. /**
  14. * Redis cache provider.
  15. *
  16. * @link www.doctrine-project.org
  17. */
  18. class RedisCache extends CacheProvider
  19. {
  20. /** @var Redis|null */
  21. private $redis;
  22. /**
  23. * Sets the redis instance to use.
  24. *
  25. * @return void
  26. */
  27. public function setRedis(Redis $redis)
  28. {
  29. $redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue());
  30. $this->redis = $redis;
  31. }
  32. /**
  33. * Gets the redis instance used by the cache.
  34. *
  35. * @return Redis|null
  36. */
  37. public function getRedis()
  38. {
  39. return $this->redis;
  40. }
  41. /**
  42. * {@inheritdoc}
  43. */
  44. protected function doFetch($id)
  45. {
  46. return $this->redis->get($id);
  47. }
  48. /**
  49. * {@inheritdoc}
  50. */
  51. protected function doFetchMultiple(array $keys)
  52. {
  53. $fetchedItems = array_combine($keys, $this->redis->mget($keys));
  54. // Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data.
  55. $keysToFilter = array_keys(array_filter($fetchedItems, static function ($item) : bool {
  56. return $item === false;
  57. }));
  58. if ($keysToFilter) {
  59. $multi = $this->redis->multi(Redis::PIPELINE);
  60. foreach ($keysToFilter as $key) {
  61. $multi->exists($key);
  62. }
  63. $existItems = array_filter($multi->exec());
  64. $missedItemKeys = array_diff_key($keysToFilter, $existItems);
  65. $fetchedItems = array_diff_key($fetchedItems, array_fill_keys($missedItemKeys, true));
  66. }
  67. return $fetchedItems;
  68. }
  69. /**
  70. * {@inheritdoc}
  71. */
  72. protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
  73. {
  74. if ($lifetime) {
  75. // Keys have lifetime, use SETEX for each of them
  76. $multi = $this->redis->multi(Redis::PIPELINE);
  77. foreach ($keysAndValues as $key => $value) {
  78. $multi->setex($key, $lifetime, $value);
  79. }
  80. $succeeded = array_filter($multi->exec());
  81. return count($succeeded) == count($keysAndValues);
  82. }
  83. // No lifetime, use MSET
  84. return (bool) $this->redis->mset($keysAndValues);
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. protected function doContains($id)
  90. {
  91. $exists = $this->redis->exists($id);
  92. if (is_bool($exists)) {
  93. return $exists;
  94. }
  95. return $exists > 0;
  96. }
  97. /**
  98. * {@inheritdoc}
  99. */
  100. protected function doSave($id, $data, $lifeTime = 0)
  101. {
  102. if ($lifeTime > 0) {
  103. return $this->redis->setex($id, $lifeTime, $data);
  104. }
  105. return $this->redis->set($id, $data);
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. protected function doDelete($id)
  111. {
  112. return $this->redis->del($id) >= 0;
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. protected function doDeleteMultiple(array $keys)
  118. {
  119. return $this->redis->del($keys) >= 0;
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. protected function doFlush()
  125. {
  126. return $this->redis->flushDB();
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. protected function doGetStats()
  132. {
  133. $info = $this->redis->info();
  134. return [
  135. Cache::STATS_HITS => $info['keyspace_hits'],
  136. Cache::STATS_MISSES => $info['keyspace_misses'],
  137. Cache::STATS_UPTIME => $info['uptime_in_seconds'],
  138. Cache::STATS_MEMORY_USAGE => $info['used_memory'],
  139. Cache::STATS_MEMORY_AVAILABLE => false,
  140. ];
  141. }
  142. /**
  143. * Returns the serializer constant to use. If Redis is compiled with
  144. * igbinary support, that is used. Otherwise the default PHP serializer is
  145. * used.
  146. *
  147. * @return int One of the Redis::SERIALIZER_* constants
  148. */
  149. protected function getSerializerValue()
  150. {
  151. if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) {
  152. return Redis::SERIALIZER_IGBINARY;
  153. }
  154. return Redis::SERIALIZER_PHP;
  155. }
  156. }