friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Cache;
4
5use React\Promise;
6use React\Promise\PromiseInterface;
7
8class ArrayCache implements CacheInterface
9{
10 private $limit;
11 private $data = array();
12 private $expires = array();
13 private $supportsHighResolution;
14
15 /**
16 * The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
17 *
18 * ```php
19 * $cache = new ArrayCache();
20 *
21 * $cache->set('foo', 'bar');
22 * ```
23 *
24 * Its constructor accepts an optional `?int $limit` parameter to limit the
25 * maximum number of entries to store in the LRU cache. If you add more
26 * entries to this instance, it will automatically take care of removing
27 * the one that was least recently used (LRU).
28 *
29 * For example, this snippet will overwrite the first value and only store
30 * the last two entries:
31 *
32 * ```php
33 * $cache = new ArrayCache(2);
34 *
35 * $cache->set('foo', '1');
36 * $cache->set('bar', '2');
37 * $cache->set('baz', '3');
38 * ```
39 *
40 * This cache implementation is known to rely on wall-clock time to schedule
41 * future cache expiration times when using any version before PHP 7.3,
42 * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
43 * While this does not affect many common use cases, this is an important
44 * distinction for programs that rely on a high time precision or on systems
45 * that are subject to discontinuous time adjustments (time jumps).
46 * This means that if you store a cache item with a TTL of 30s on PHP < 7.3
47 * and then adjust your system time forward by 20s, the cache item may
48 * expire in 10s. See also [`set()`](#set) for more details.
49 *
50 * @param int|null $limit maximum number of entries to store in the LRU cache
51 */
52 public function __construct($limit = null)
53 {
54 $this->limit = $limit;
55
56 // prefer high-resolution timer, available as of PHP 7.3+
57 $this->supportsHighResolution = \function_exists('hrtime');
58 }
59
60 public function get($key, $default = null)
61 {
62 // delete key if it is already expired => below will detect this as a cache miss
63 if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
64 unset($this->data[$key], $this->expires[$key]);
65 }
66
67 if (!\array_key_exists($key, $this->data)) {
68 return Promise\resolve($default);
69 }
70
71 // remove and append to end of array to keep track of LRU info
72 $value = $this->data[$key];
73 unset($this->data[$key]);
74 $this->data[$key] = $value;
75
76 return Promise\resolve($value);
77 }
78
79 public function set($key, $value, $ttl = null)
80 {
81 // unset before setting to ensure this entry will be added to end of array (LRU info)
82 unset($this->data[$key]);
83 $this->data[$key] = $value;
84
85 // sort expiration times if TTL is given (first will expire first)
86 unset($this->expires[$key]);
87 if ($ttl !== null) {
88 $this->expires[$key] = $this->now() + $ttl;
89 \asort($this->expires);
90 }
91
92 // ensure size limit is not exceeded or remove first entry from array
93 if ($this->limit !== null && \count($this->data) > $this->limit) {
94 // first try to check if there's any expired entry
95 // expiration times are sorted, so we can simply look at the first one
96 \reset($this->expires);
97 $key = \key($this->expires);
98
99 // check to see if the first in the list of expiring keys is already expired
100 // if the first key is not expired, we have to overwrite by using LRU info
101 if ($key === null || $this->now() - $this->expires[$key] < 0) {
102 \reset($this->data);
103 $key = \key($this->data);
104 }
105 unset($this->data[$key], $this->expires[$key]);
106 }
107
108 return Promise\resolve(true);
109 }
110
111 public function delete($key)
112 {
113 unset($this->data[$key], $this->expires[$key]);
114
115 return Promise\resolve(true);
116 }
117
118 public function getMultiple(array $keys, $default = null)
119 {
120 $values = array();
121
122 foreach ($keys as $key) {
123 $values[$key] = $this->get($key, $default);
124 }
125
126 return Promise\all($values);
127 }
128
129 public function setMultiple(array $values, $ttl = null)
130 {
131 foreach ($values as $key => $value) {
132 $this->set($key, $value, $ttl);
133 }
134
135 return Promise\resolve(true);
136 }
137
138 public function deleteMultiple(array $keys)
139 {
140 foreach ($keys as $key) {
141 unset($this->data[$key], $this->expires[$key]);
142 }
143
144 return Promise\resolve(true);
145 }
146
147 public function clear()
148 {
149 $this->data = array();
150 $this->expires = array();
151
152 return Promise\resolve(true);
153 }
154
155 public function has($key)
156 {
157 // delete key if it is already expired
158 if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
159 unset($this->data[$key], $this->expires[$key]);
160 }
161
162 if (!\array_key_exists($key, $this->data)) {
163 return Promise\resolve(false);
164 }
165
166 // remove and append to end of array to keep track of LRU info
167 $value = $this->data[$key];
168 unset($this->data[$key]);
169 $this->data[$key] = $value;
170
171 return Promise\resolve(true);
172 }
173
174 /**
175 * @return float
176 */
177 private function now()
178 {
179 return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
180 }
181}