3 namespace Drupal\Core\TempStore;
5 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
6 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
7 use Drupal\Core\Lock\LockBackendInterface;
8 use Drupal\Core\Session\AccountProxyInterface;
9 use Symfony\Component\HttpFoundation\RequestStack;
12 * Stores and retrieves temporary data for a given owner.
14 * A PrivateTempStore can be used to make temporary, non-cache data available
15 * across requests. The data for the PrivateTempStore is stored in one
16 * key/value collection. PrivateTempStore data expires automatically after a
19 * The PrivateTempStore is different from a cache, because the data in it is not
20 * yet saved permanently and so it cannot be rebuilt. Typically, the
21 * PrivateTempStore might be used to store work in progress that is later saved
22 * permanently elsewhere, e.g. autosave data, multistep forms, or in-progress
23 * changes to complex configuration that are not ready to be saved.
25 * The PrivateTempStore differs from the SharedTempStore in that all keys are
26 * ensured to be unique for a particular user and users can never share data. If
27 * you want to be able to share data between users or use it for locking, use
28 * \Drupal\Core\TempStore\SharedTempStore.
30 class PrivateTempStore {
31 use DependencySerializationTrait;
34 * The key/value storage object used for this data.
36 * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
41 * The lock object used for this data.
43 * @var \Drupal\Core\Lock\LockBackendInterface
45 protected $lockBackend;
50 * @var \Drupal\Core\Session\AccountProxyInterface
52 protected $currentUser;
57 * @var \Symfony\Component\HttpFoundation\RequestStack
59 protected $requestStack;
62 * The time to live for items in seconds.
64 * By default, data is stored for one week (604800 seconds) before expiring.
71 * Constructs a new object for accessing data from a key/value store.
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
77 * @param \Drupal\Core\Lock\LockBackendInterface $lock_backend
78 * The lock object used for this data.
79 * @param \Drupal\Core\Session\AccountProxyInterface $current_user
80 * The current user account.
81 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
84 * The time to live for items, in seconds.
86 public function __construct(KeyValueStoreExpirableInterface $storage, LockBackendInterface $lock_backend, AccountProxyInterface $current_user, RequestStack $request_stack, $expire = 604800) {
87 $this->storage = $storage;
88 $this->lockBackend = $lock_backend;
89 $this->currentUser = $current_user;
90 $this->requestStack = $request_stack;
91 $this->expire = $expire;
95 * Retrieves a value from this PrivateTempStore for a given key.
98 * The key of the data to retrieve.
101 * The data associated with the key, or NULL if the key does not exist.
103 public function get($key) {
104 $key = $this->createkey($key);
105 if (($object = $this->storage->get($key)) && ($object->owner == $this->getOwner())) {
106 return $object->data;
111 * Stores a particular key/value pair in this PrivateTempStore.
114 * The key of the data to store.
115 * @param mixed $value
118 * @throws \Drupal\Core\TempStore\TempStoreException
119 * Thrown when a lock for the backend storage could not be acquired.
121 public function set($key, $value) {
122 // Ensure that an anonymous user has a session created for them, as
123 // otherwise subsequent page loads will not be able to retrieve their
125 if ($this->currentUser->isAnonymous()) {
126 // @todo when https://www.drupal.org/node/2865991 is resolved, use force
127 // start session API rather than setting an arbitrary value directly.
128 $this->startSession();
130 ->getCurrentRequest()
132 ->set('core.tempstore.private', TRUE);
135 $key = $this->createkey($key);
136 if (!$this->lockBackend->acquire($key)) {
137 $this->lockBackend->wait($key);
138 if (!$this->lockBackend->acquire($key)) {
139 throw new TempStoreException("Couldn't acquire lock to update item '$key' in '{$this->storage->getCollectionName()}' temporary storage.");
144 'owner' => $this->getOwner(),
146 'updated' => (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME'),
148 $this->storage->setWithExpire($key, $value, $this->expire);
149 $this->lockBackend->release($key);
153 * Returns the metadata associated with a particular key/value pair.
156 * The key of the data to store.
159 * An object with the owner and updated time if the key has a value, or
162 public function getMetadata($key) {
163 $key = $this->createkey($key);
164 // Fetch the key/value pair and its metadata.
165 $object = $this->storage->get($key);
167 // Don't keep the data itself in memory.
168 unset($object->data);
174 * Deletes data from the store for a given key and releases the lock on it.
177 * The key of the data to delete.
180 * TRUE if the object was deleted or does not exist, FALSE if it exists but
181 * is not owned by $this->owner.
183 * @throws \Drupal\Core\TempStore\TempStoreException
184 * Thrown when a lock for the backend storage could not be acquired.
186 public function delete($key) {
187 $key = $this->createkey($key);
188 if (!$object = $this->storage->get($key)) {
191 elseif ($object->owner != $this->getOwner()) {
194 if (!$this->lockBackend->acquire($key)) {
195 $this->lockBackend->wait($key);
196 if (!$this->lockBackend->acquire($key)) {
197 throw new TempStoreException("Couldn't acquire lock to delete item '$key' from '{$this->storage->getCollectionName()}' temporary storage.");
200 $this->storage->delete($key);
201 $this->lockBackend->release($key);
206 * Ensures that the key is unique for a user.
212 * The unique key for the user.
214 protected function createkey($key) {
215 return $this->getOwner() . ':' . $key;
219 * Gets the current owner based on the current user or the session ID.
224 protected function getOwner() {
225 $owner = $this->currentUser->id();
226 if ($this->currentUser->isAnonymous()) {
227 $this->startSession();
228 $owner = $this->requestStack->getCurrentRequest()->getSession()->getId();
234 * Start session because it is required for a private temp store.
236 * Ensures that an anonymous user has a session created for them, as
237 * otherwise subsequent page loads will not be able to retrieve their
240 * @todo when https://www.drupal.org/node/2865991 is resolved, use force
243 protected function startSession() {
244 $has_session = $this->requestStack
245 ->getCurrentRequest()
248 /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
249 $session = \Drupal::service('session');
250 $this->requestStack->getCurrentRequest()->setSession($session);