Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\user;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
|
Chris@0
|
6 use Drupal\Core\Lock\LockBackendInterface;
|
Chris@0
|
7 use Symfony\Component\HttpFoundation\RequestStack;
|
Chris@0
|
8
|
Chris@0
|
9 /**
|
Chris@0
|
10 * Stores and retrieves temporary data for a given owner.
|
Chris@0
|
11 *
|
Chris@0
|
12 * A SharedTempStore can be used to make temporary, non-cache data available
|
Chris@0
|
13 * across requests. The data for the SharedTempStore is stored in one key/value
|
Chris@0
|
14 * collection. SharedTempStore data expires automatically after a given
|
Chris@0
|
15 * timeframe.
|
Chris@0
|
16 *
|
Chris@0
|
17 * The SharedTempStore is different from a cache, because the data in it is not
|
Chris@0
|
18 * yet saved permanently and so it cannot be rebuilt. Typically, the
|
Chris@0
|
19 * SharedTempStore might be used to store work in progress that is later saved
|
Chris@0
|
20 * permanently elsewhere, e.g. autosave data, multistep forms, or in-progress
|
Chris@0
|
21 * changes to complex configuration that are not ready to be saved.
|
Chris@0
|
22 *
|
Chris@0
|
23 * Each SharedTempStore belongs to a particular owner (e.g. a user, session, or
|
Chris@0
|
24 * process). Multiple owners may use the same key/value collection, and the
|
Chris@0
|
25 * owner is stored along with the key/value pair.
|
Chris@0
|
26 *
|
Chris@0
|
27 * Every key is unique within the collection, so the SharedTempStore can check
|
Chris@0
|
28 * whether a particular key is already set by a different owner. This is
|
Chris@0
|
29 * useful for informing one owner that the data is already in use by another;
|
Chris@0
|
30 * for example, to let one user know that another user is in the process of
|
Chris@0
|
31 * editing certain data, or even to restrict other users from editing it at
|
Chris@0
|
32 * the same time. It is the responsibility of the implementation to decide
|
Chris@0
|
33 * when and whether one owner can use or update another owner's data.
|
Chris@0
|
34 *
|
Chris@0
|
35 * If you want to be able to ensure that the data belongs to the current user,
|
Chris@0
|
36 * use \Drupal\user\PrivateTempStore.
|
Chris@0
|
37 */
|
Chris@0
|
38 class SharedTempStore {
|
Chris@0
|
39
|
Chris@0
|
40 /**
|
Chris@0
|
41 * The key/value storage object used for this data.
|
Chris@0
|
42 *
|
Chris@0
|
43 * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
|
Chris@0
|
44 */
|
Chris@0
|
45 protected $storage;
|
Chris@0
|
46
|
Chris@0
|
47 /**
|
Chris@0
|
48 * The lock object used for this data.
|
Chris@0
|
49 *
|
Chris@0
|
50 * @var \Drupal\Core\Lock\LockBackendInterface
|
Chris@0
|
51 */
|
Chris@0
|
52 protected $lockBackend;
|
Chris@0
|
53
|
Chris@0
|
54 /**
|
Chris@0
|
55 * The request stack.
|
Chris@0
|
56 *
|
Chris@0
|
57 * @var \Symfony\Component\HttpFoundation\RequestStack
|
Chris@0
|
58 */
|
Chris@0
|
59 protected $requestStack;
|
Chris@0
|
60
|
Chris@0
|
61 /**
|
Chris@0
|
62 * The owner key to store along with the data (e.g. a user or session ID).
|
Chris@0
|
63 *
|
Chris@0
|
64 * @var mixed
|
Chris@0
|
65 */
|
Chris@0
|
66 protected $owner;
|
Chris@0
|
67
|
Chris@0
|
68 /**
|
Chris@0
|
69 * The time to live for items in seconds.
|
Chris@0
|
70 *
|
Chris@0
|
71 * By default, data is stored for one week (604800 seconds) before expiring.
|
Chris@0
|
72 *
|
Chris@0
|
73 * @var int
|
Chris@0
|
74 */
|
Chris@0
|
75 protected $expire;
|
Chris@0
|
76
|
Chris@0
|
77 /**
|
Chris@0
|
78 * Constructs a new object for accessing data from a key/value store.
|
Chris@0
|
79 *
|
Chris@0
|
80 * @param KeyValueStoreExpirableInterface $storage
|
Chris@0
|
81 * The key/value storage object used for this data. Each storage object
|
Chris@0
|
82 * represents a particular collection of data and will contain any number
|
Chris@0
|
83 * of key/value pairs.
|
Chris@0
|
84 * @param \Drupal\Core\Lock\LockBackendInterface $lock_backend
|
Chris@0
|
85 * The lock object used for this data.
|
Chris@0
|
86 * @param mixed $owner
|
Chris@0
|
87 * The owner key to store along with the data (e.g. a user or session ID).
|
Chris@0
|
88 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
Chris@0
|
89 * The request stack.
|
Chris@0
|
90 * @param int $expire
|
Chris@0
|
91 * The time to live for items, in seconds.
|
Chris@0
|
92 */
|
Chris@0
|
93 public function __construct(KeyValueStoreExpirableInterface $storage, LockBackendInterface $lock_backend, $owner, RequestStack $request_stack, $expire = 604800) {
|
Chris@0
|
94 $this->storage = $storage;
|
Chris@0
|
95 $this->lockBackend = $lock_backend;
|
Chris@0
|
96 $this->owner = $owner;
|
Chris@0
|
97 $this->requestStack = $request_stack;
|
Chris@0
|
98 $this->expire = $expire;
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 /**
|
Chris@0
|
102 * Retrieves a value from this SharedTempStore for a given key.
|
Chris@0
|
103 *
|
Chris@0
|
104 * @param string $key
|
Chris@0
|
105 * The key of the data to retrieve.
|
Chris@0
|
106 *
|
Chris@0
|
107 * @return mixed
|
Chris@0
|
108 * The data associated with the key, or NULL if the key does not exist.
|
Chris@0
|
109 */
|
Chris@0
|
110 public function get($key) {
|
Chris@0
|
111 if ($object = $this->storage->get($key)) {
|
Chris@0
|
112 return $object->data;
|
Chris@0
|
113 }
|
Chris@0
|
114 }
|
Chris@0
|
115
|
Chris@0
|
116 /**
|
Chris@0
|
117 * Retrieves a value from this SharedTempStore for a given key.
|
Chris@0
|
118 *
|
Chris@0
|
119 * Only returns the value if the value is owned by $this->owner.
|
Chris@0
|
120 *
|
Chris@0
|
121 * @param string $key
|
Chris@0
|
122 * The key of the data to retrieve.
|
Chris@0
|
123 *
|
Chris@0
|
124 * @return mixed
|
Chris@0
|
125 * The data associated with the key, or NULL if the key does not exist.
|
Chris@0
|
126 */
|
Chris@0
|
127 public function getIfOwner($key) {
|
Chris@0
|
128 if (($object = $this->storage->get($key)) && ($object->owner == $this->owner)) {
|
Chris@0
|
129 return $object->data;
|
Chris@0
|
130 }
|
Chris@0
|
131 }
|
Chris@0
|
132
|
Chris@0
|
133 /**
|
Chris@0
|
134 * Stores a particular key/value pair only if the key doesn't already exist.
|
Chris@0
|
135 *
|
Chris@0
|
136 * @param string $key
|
Chris@0
|
137 * The key of the data to check and store.
|
Chris@0
|
138 * @param mixed $value
|
Chris@0
|
139 * The data to store.
|
Chris@0
|
140 *
|
Chris@0
|
141 * @return bool
|
Chris@0
|
142 * TRUE if the data was set, or FALSE if it already existed.
|
Chris@0
|
143 */
|
Chris@0
|
144 public function setIfNotExists($key, $value) {
|
Chris@0
|
145 $value = (object) [
|
Chris@0
|
146 'owner' => $this->owner,
|
Chris@0
|
147 'data' => $value,
|
Chris@0
|
148 'updated' => (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME'),
|
Chris@0
|
149 ];
|
Chris@0
|
150 return $this->storage->setWithExpireIfNotExists($key, $value, $this->expire);
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 /**
|
Chris@0
|
154 * Stores a particular key/value pair in this SharedTempStore.
|
Chris@0
|
155 *
|
Chris@0
|
156 * Only stores the given key/value pair if it does not exist yet or is owned
|
Chris@0
|
157 * by $this->owner.
|
Chris@0
|
158 *
|
Chris@0
|
159 * @param string $key
|
Chris@0
|
160 * The key of the data to store.
|
Chris@0
|
161 * @param mixed $value
|
Chris@0
|
162 * The data to store.
|
Chris@0
|
163 *
|
Chris@0
|
164 * @return bool
|
Chris@0
|
165 * TRUE if the data was set, or FALSE if it already exists and is not owned
|
Chris@0
|
166 * by $this->user.
|
Chris@0
|
167 *
|
Chris@0
|
168 * @throws \Drupal\user\TempStoreException
|
Chris@0
|
169 * Thrown when a lock for the backend storage could not be acquired.
|
Chris@0
|
170 */
|
Chris@0
|
171 public function setIfOwner($key, $value) {
|
Chris@0
|
172 if ($this->setIfNotExists($key, $value)) {
|
Chris@0
|
173 return TRUE;
|
Chris@0
|
174 }
|
Chris@0
|
175
|
Chris@0
|
176 if (($object = $this->storage->get($key)) && ($object->owner == $this->owner)) {
|
Chris@0
|
177 $this->set($key, $value);
|
Chris@0
|
178 return TRUE;
|
Chris@0
|
179 }
|
Chris@0
|
180
|
Chris@0
|
181 return FALSE;
|
Chris@0
|
182 }
|
Chris@0
|
183
|
Chris@0
|
184 /**
|
Chris@0
|
185 * Stores a particular key/value pair in this SharedTempStore.
|
Chris@0
|
186 *
|
Chris@0
|
187 * @param string $key
|
Chris@0
|
188 * The key of the data to store.
|
Chris@0
|
189 * @param mixed $value
|
Chris@0
|
190 * The data to store.
|
Chris@0
|
191 *
|
Chris@0
|
192 * @throws \Drupal\user\TempStoreException
|
Chris@0
|
193 * Thrown when a lock for the backend storage could not be acquired.
|
Chris@0
|
194 */
|
Chris@0
|
195 public function set($key, $value) {
|
Chris@0
|
196 if (!$this->lockBackend->acquire($key)) {
|
Chris@0
|
197 $this->lockBackend->wait($key);
|
Chris@0
|
198 if (!$this->lockBackend->acquire($key)) {
|
Chris@0
|
199 throw new TempStoreException("Couldn't acquire lock to update item '$key' in '{$this->storage->getCollectionName()}' temporary storage.");
|
Chris@0
|
200 }
|
Chris@0
|
201 }
|
Chris@0
|
202
|
Chris@0
|
203 $value = (object) [
|
Chris@0
|
204 'owner' => $this->owner,
|
Chris@0
|
205 'data' => $value,
|
Chris@0
|
206 'updated' => (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME'),
|
Chris@0
|
207 ];
|
Chris@0
|
208 $this->storage->setWithExpire($key, $value, $this->expire);
|
Chris@0
|
209 $this->lockBackend->release($key);
|
Chris@0
|
210 }
|
Chris@0
|
211
|
Chris@0
|
212 /**
|
Chris@0
|
213 * Returns the metadata associated with a particular key/value pair.
|
Chris@0
|
214 *
|
Chris@0
|
215 * @param string $key
|
Chris@0
|
216 * The key of the data to store.
|
Chris@0
|
217 *
|
Chris@0
|
218 * @return mixed
|
Chris@0
|
219 * An object with the owner and updated time if the key has a value, or
|
Chris@0
|
220 * NULL otherwise.
|
Chris@0
|
221 */
|
Chris@0
|
222 public function getMetadata($key) {
|
Chris@0
|
223 // Fetch the key/value pair and its metadata.
|
Chris@0
|
224 $object = $this->storage->get($key);
|
Chris@0
|
225 if ($object) {
|
Chris@0
|
226 // Don't keep the data itself in memory.
|
Chris@0
|
227 unset($object->data);
|
Chris@0
|
228 return $object;
|
Chris@0
|
229 }
|
Chris@0
|
230 }
|
Chris@0
|
231
|
Chris@0
|
232 /**
|
Chris@0
|
233 * Deletes data from the store for a given key and releases the lock on it.
|
Chris@0
|
234 *
|
Chris@0
|
235 * @param string $key
|
Chris@0
|
236 * The key of the data to delete.
|
Chris@0
|
237 *
|
Chris@0
|
238 * @throws \Drupal\user\TempStoreException
|
Chris@0
|
239 * Thrown when a lock for the backend storage could not be acquired.
|
Chris@0
|
240 */
|
Chris@0
|
241 public function delete($key) {
|
Chris@0
|
242 if (!$this->lockBackend->acquire($key)) {
|
Chris@0
|
243 $this->lockBackend->wait($key);
|
Chris@0
|
244 if (!$this->lockBackend->acquire($key)) {
|
Chris@0
|
245 throw new TempStoreException("Couldn't acquire lock to delete item '$key' from {$this->storage->getCollectionName()} temporary storage.");
|
Chris@0
|
246 }
|
Chris@0
|
247 }
|
Chris@0
|
248 $this->storage->delete($key);
|
Chris@0
|
249 $this->lockBackend->release($key);
|
Chris@0
|
250 }
|
Chris@0
|
251
|
Chris@0
|
252 /**
|
Chris@0
|
253 * Deletes data from the store for a given key and releases the lock on it.
|
Chris@0
|
254 *
|
Chris@0
|
255 * Only delete the given key if it is owned by $this->owner.
|
Chris@0
|
256 *
|
Chris@0
|
257 * @param string $key
|
Chris@0
|
258 * The key of the data to delete.
|
Chris@0
|
259 *
|
Chris@0
|
260 * @return bool
|
Chris@0
|
261 * TRUE if the object was deleted or does not exist, FALSE if it exists but
|
Chris@0
|
262 * is not owned by $this->owner.
|
Chris@0
|
263 *
|
Chris@0
|
264 * @throws \Drupal\user\TempStoreException
|
Chris@0
|
265 * Thrown when a lock for the backend storage could not be acquired.
|
Chris@0
|
266 */
|
Chris@0
|
267 public function deleteIfOwner($key) {
|
Chris@0
|
268 if (!$object = $this->storage->get($key)) {
|
Chris@0
|
269 return TRUE;
|
Chris@0
|
270 }
|
Chris@0
|
271 elseif ($object->owner == $this->owner) {
|
Chris@0
|
272 $this->delete($key);
|
Chris@0
|
273 return TRUE;
|
Chris@0
|
274 }
|
Chris@0
|
275
|
Chris@0
|
276 return FALSE;
|
Chris@0
|
277 }
|
Chris@0
|
278
|
Chris@0
|
279 }
|