SharedTempStore.php 8.8 KB

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