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