3 namespace Drupal\memcache;
5 use Drupal\Component\Assertion\Inspector;
6 use Drupal\Core\Cache\CacheBackendInterface;
7 use Drupal\Core\Cache\CacheTagsChecksumInterface;
8 use Drupal\memcache\Invalidator\TimestampInvalidatorInterface;
11 * Defines a Memcache cache backend.
13 class MemcacheBackend implements CacheBackendInterface {
16 * The cache bin to use.
23 * The (micro)time the bin was last deleted.
27 protected $lastBinDeletionTime;
30 * The memcache wrapper object.
32 * @var \Drupal\memcache\DrupalMemcacheInterface
37 * The cache tags checksum provider.
39 * @var \Drupal\Core\Cache\CacheTagsChecksumInterface|\Drupal\Core\Cache\CacheTagsInvalidatorInterface
41 protected $checksumProvider;
44 * The timestamp invalidation provider.
46 * @var \Drupal\memcache\Invalidator\TimestampInvalidatorInterface
48 protected $timestampInvalidator;
51 * Constructs a MemcacheBackend object.
55 * @param \Drupal\memcache\DrupalMemcacheInterface $memcache
56 * The memcache object.
57 * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
58 * The cache tags checksum service.
59 * @param \Drupal\memcache\Invalidator\TimestampInvalidatorInterface $timestamp_invalidator
60 * The timestamp invalidation provider.
62 public function __construct($bin, DrupalMemcacheInterface $memcache, CacheTagsChecksumInterface $checksum_provider, TimestampInvalidatorInterface $timestamp_invalidator) {
64 $this->memcache = $memcache;
65 $this->checksumProvider = $checksum_provider;
66 $this->timestampInvalidator = $timestamp_invalidator;
68 $this->ensureBinDeletionTimeIsSet();
74 public function get($cid, $allow_invalid = FALSE) {
76 $cache = $this->getMultiple($cids, $allow_invalid);
83 public function getMultiple(&$cids, $allow_invalid = FALSE) {
84 $cache = $this->memcache->getMulti($cids);
87 foreach ($cache as $result) {
88 if (!$this->timeIsGreaterThanBinDeletionTime($result->created)) {
92 if ($this->valid($result->cid, $result) || $allow_invalid) {
93 // Add it to the fetched items to diff later.
94 $fetched[$result->cid] = $result;
98 // Remove items from the referenced $cids array that we are returning,
99 // per comment in Drupal\Core\Cache\CacheBackendInterface::getMultiple().
100 $cids = array_diff($cids, array_keys($fetched));
106 * Determines if the cache item is valid.
108 * This also alters the valid property of the cache item itself.
112 * @param \stdClass $cache
116 * TRUE if valid, FALSE otherwise.
118 protected function valid($cid, \stdClass $cache) {
119 $cache->valid = TRUE;
121 // Items that have expired are invalid.
122 if ($cache->expire != CacheBackendInterface::CACHE_PERMANENT && $cache->expire <= REQUEST_TIME) {
123 $cache->valid = FALSE;
126 // Check if invalidateTags() has been called with any of the items's tags.
127 if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
128 $cache->valid = FALSE;
131 return $cache->valid;
137 public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = []) {
138 assert(Inspector::assertAllStrings($tags));
140 $tags[] = "memcache:$this->bin";
141 $tags = array_unique($tags);
142 // Sort the cache tags so that they are stored consistently.
145 // Create new cache object.
146 $cache = new \stdClass();
148 $cache->data = $data;
149 $cache->created = round(microtime(TRUE), 3);
150 $cache->expire = $expire;
151 $cache->tags = $tags;
152 $cache->checksum = $this->checksumProvider->getCurrentChecksum($tags);
154 // Cache all items permanently. We handle expiration in our own logic.
155 return $this->memcache->set($cid, $cache);
161 public function setMultiple(array $items) {
162 foreach ($items as $cid => $item) {
164 'expire' => CacheBackendInterface::CACHE_PERMANENT,
168 $this->set($cid, $item['data'], $item['expire'], $item['tags']);
175 public function delete($cid) {
176 $this->memcache->delete($cid);
182 public function deleteMultiple(array $cids) {
183 foreach ($cids as $cid) {
184 $this->memcache->delete($cid);
191 public function deleteAll() {
192 $this->lastBinDeletionTime = $this->timestampInvalidator->invalidateTimestamp($this->bin);
198 public function invalidate($cid) {
199 $this->invalidateMultiple([$cid]);
203 * Marks cache items as invalid.
205 * Invalid items may be returned in later calls to get(), if the
206 * $allow_invalid argument is TRUE.
209 * An array of cache IDs to invalidate.
211 * @see Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
212 * @see Drupal\Core\Cache\CacheBackendInterface::invalidate()
213 * @see Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
214 * @see Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
216 public function invalidateMultiple(array $cids) {
217 foreach ($cids as $cid) {
218 if ($item = $this->get($cid)) {
219 $item->expire = REQUEST_TIME - 1;
220 $this->memcache->set($cid, $item);
228 public function invalidateAll() {
229 $this->invalidateTags(["memcache:$this->bin"]);
235 public function invalidateTags(array $tags) {
236 $this->checksumProvider->invalidateTags($tags);
242 public function removeBin() {
243 $this->lastBinDeletionTime = $this->timestampInvalidator->invalidateTimestamp($this->bin);
249 public function garbageCollection() {
250 // Memcache will invalidate items; That items memory allocation is then
251 // freed up and reused. So nothing needs to be deleted/cleaned up here.
257 public function isEmpty() {
258 // We do not know so err on the safe side? Not sure if we can know this?
263 * Determines if a (micro)time is greater than the last bin deletion time.
265 * @param float $item_microtime
266 * A given (micro)time.
271 * TRUE if the (micro)time is greater than the last bin deletion time, FALSE
274 protected function timeIsGreaterThanBinDeletionTime($item_microtime) {
275 $last_bin_deletion = $this->getBinLastDeletionTime();
277 // If there is time, assume FALSE as there is no previous deletion time
279 if (!$last_bin_deletion) {
283 return $item_microtime > $last_bin_deletion;
287 * Gets the last invalidation time for the bin.
292 * The last invalidation timestamp of the tag.
294 protected function getBinLastDeletionTime() {
295 if (!isset($this->lastBinDeletionTime)) {
296 $this->lastBinDeletionTime = $this->timestampInvalidator->getLastInvalidationTimestamp($this->bin);
299 return $this->lastBinDeletionTime;
303 * Ensures a last bin deletion time has been set.
307 protected function ensureBinDeletionTimeIsSet() {
308 if (!$this->getBinLastDeletionTime()) {
309 $this->lastBinDeletionTime = $this->timestampInvalidator->invalidateTimestamp($this->bin);