@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * Stacks multiple caches on top of each other, with readthrough semantics:
5 *
6 * - For reads, we try each cache in order until we find all the keys.
7 * - For writes, we set the keys in each cache.
8 *
9 * @task config Configuring the Stack
10 */
11final class PhutilKeyValueCacheStack extends PhutilKeyValueCache {
12
13
14 /**
15 * Forward list of caches in the stack (from the nearest cache to the farthest
16 * cache).
17 */
18 private $cachesForward;
19
20
21 /**
22 * Backward list of caches in the stack (from the farthest cache to the
23 * nearest cache).
24 */
25 private $cachesBackward;
26
27
28 /**
29 * TTL to use for any writes which are side effects of the next read
30 * operation.
31 */
32 private $nextTTL;
33
34
35/* -( Configuring the Stack )---------------------------------------------- */
36
37
38 /**
39 * Set the caches which comprise this stack.
40 *
41 * @param array<PhutilKeyValueCache> $caches Ordered list of key-value
42 * caches.
43 * @return $this
44 * @task config
45 */
46 public function setCaches(array $caches) {
47 assert_instances_of($caches, parent::class);
48 $this->cachesForward = $caches;
49 $this->cachesBackward = array_reverse($caches);
50
51 return $this;
52 }
53
54
55 /**
56 * Set the readthrough TTL for the next cache operation. The TTL applies to
57 * any keys set by the next call to @{method:getKey} or @{method:getKeys},
58 * and is reset after the call finishes.
59 *
60 * // If this causes any caches to fill, they'll fill with a 15-second TTL.
61 * $stack->setNextTTL(15)->getKey('porcupine');
62 *
63 * // TTL does not persist; this will use no TTL.
64 * $stack->getKey('hedgehog');
65 *
66 * @param int $ttl TTL in seconds.
67 * @return $this
68 *
69 * @task config
70 */
71 public function setNextTTL($ttl) {
72 $this->nextTTL = $ttl;
73 return $this;
74 }
75
76
77/* -( Key-Value Cache Implementation )------------------------------------- */
78
79
80 public function getKeys(array $keys) {
81
82 $remaining = array_fuse($keys);
83 $results = array();
84 $missed = array();
85
86 try {
87 foreach ($this->cachesForward as $cache) {
88 $result = $cache->getKeys($remaining);
89 $remaining = array_diff_key($remaining, $result);
90 $results += $result;
91 if (!$remaining) {
92 while ($cache = array_pop($missed)) {
93 // TODO: This sets too many results in the closer caches, although
94 // it probably isn't a big deal in most cases; normally we're just
95 // filling the request cache.
96 $cache->setKeys($result, $this->nextTTL);
97 }
98 break;
99 }
100 $missed[] = $cache;
101 }
102 $this->nextTTL = null;
103 } catch (Exception $ex) {
104 $this->nextTTL = null;
105 throw $ex;
106 }
107
108 return $results;
109 }
110
111
112 public function setKeys(array $keys, $ttl = null) {
113 foreach ($this->cachesBackward as $cache) {
114 $cache->setKeys($keys, $ttl);
115 }
116 return $this;
117 }
118
119
120 public function deleteKeys(array $keys) {
121 foreach ($this->cachesBackward as $cache) {
122 $cache->deleteKeys($keys);
123 }
124 return $this;
125 }
126
127
128 public function destroyCache() {
129 foreach ($this->cachesBackward as $cache) {
130 $cache->destroyCache();
131 }
132 return $this;
133 }
134
135}