@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 * @task memcache Managing Memcache
5 */
6final class PhutilMemcacheKeyValueCache extends PhutilKeyValueCache {
7
8 private $servers = array();
9 private $connections = array();
10
11
12/* -( Key-Value Cache Implementation )------------------------------------- */
13
14
15 public function isAvailable() {
16 return function_exists('memcache_pconnect');
17 }
18
19 public function getKeys(array $keys) {
20 $buckets = $this->bucketKeys($keys);
21 $results = array();
22
23 foreach ($buckets as $bucket => $bucket_keys) {
24 $conn = $this->getConnection($bucket);
25 $result = $conn->get($bucket_keys);
26 if (!$result) {
27 // If the call fails, treat it as a miss on all keys.
28 $result = array();
29 }
30
31 $results += $result;
32 }
33
34 return $results;
35 }
36
37 public function setKeys(array $keys, $ttl = null) {
38 $buckets = $this->bucketKeys(array_keys($keys));
39
40 // Memcache interprets TTLs as:
41 //
42 // - Seconds from now, for values from 1 to 2592000 (30 days).
43 // - Epoch timestamp, for values larger than 2592000.
44 //
45 // We support only relative TTLs, so convert excessively large relative
46 // TTLs into epoch TTLs.
47 if ($ttl > 2592000) {
48 $effective_ttl = time() + $ttl;
49 } else {
50 $effective_ttl = $ttl;
51 }
52
53 foreach ($buckets as $bucket => $bucket_keys) {
54 $conn = $this->getConnection($bucket);
55
56 foreach ($bucket_keys as $key) {
57 $conn->set($key, $keys[$key], 0, $effective_ttl);
58 }
59 }
60
61 return $this;
62 }
63
64 public function deleteKeys(array $keys) {
65 $buckets = $this->bucketKeys($keys);
66
67 foreach ($buckets as $bucket => $bucket_keys) {
68 $conn = $this->getConnection($bucket);
69 foreach ($bucket_keys as $key) {
70 $conn->delete($key);
71 }
72 }
73
74 return $this;
75 }
76
77 public function destroyCache() {
78 foreach ($this->servers as $key => $spec) {
79 $this->getConnection($key)->flush();
80 }
81 return $this;
82 }
83
84
85/* -( Managing Memcache )-------------------------------------------------- */
86
87
88 /**
89 * Set available memcache servers. For example:
90 *
91 * $cache->setServers(
92 * array(
93 * array(
94 * 'host' => '10.0.0.20',
95 * 'port' => 11211,
96 * ),
97 * array(
98 * 'host' => '10.0.0.21',
99 * 'port' => 11211,
100 * ),
101 * ));
102 *
103 * @param array $servers List of server specifications.
104 * @return $this
105 * @task memcache
106 */
107 public function setServers(array $servers) {
108 $this->servers = array_values($servers);
109 return $this;
110 }
111
112 private function bucketKeys(array $keys) {
113 $buckets = array();
114 $n = count($this->servers);
115
116 if (!$n) {
117 throw new PhutilInvalidStateException('setServers');
118 }
119
120 foreach ($keys as $key) {
121 $bucket = (int)((crc32($key) & 0x7FFFFFFF) % $n);
122 $buckets[$bucket][] = $key;
123 }
124
125 return $buckets;
126 }
127
128
129 /**
130 * @phutil-external-symbol function memcache_pconnect
131 */
132 private function getConnection($server) {
133 if (empty($this->connections[$server])) {
134 $spec = $this->servers[$server];
135 $host = $spec['host'];
136 $port = $spec['port'];
137
138 $conn = memcache_pconnect($host, $spec['port']);
139
140 if (!$conn) {
141 throw new Exception(
142 pht(
143 'Unable to connect to memcache server (%s:%d)!',
144 $host,
145 $port));
146 }
147
148 $this->connections[$server] = $conn;
149 }
150 return $this->connections[$server];
151 }
152
153}