f43ede2a6375c93ce5a5d1e5a37095f2832f3b4e
[yaffs-website] / web / core / lib / Drupal / Core / Config / CachedStorage.php
1 <?php
2
3 namespace Drupal\Core\Config;
4
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
7
8 /**
9  * Defines the cached storage.
10  *
11  * The class gets another storage and a cache backend injected. It reads from
12  * the cache and delegates the read to the storage on a cache miss. It also
13  * handles cache invalidation.
14  */
15 class CachedStorage implements StorageInterface, StorageCacheInterface {
16   use DependencySerializationTrait;
17
18   /**
19    * The configuration storage to be cached.
20    *
21    * @var \Drupal\Core\Config\StorageInterface
22    */
23   protected $storage;
24
25   /**
26    * The instantiated Cache backend.
27    *
28    * @var \Drupal\Core\Cache\CacheBackendInterface
29    */
30   protected $cache;
31
32   /**
33    * List of listAll() prefixes with their results.
34    *
35    * @var array
36    */
37   protected $findByPrefixCache = [];
38
39   /**
40    * Constructs a new CachedStorage.
41    *
42    * @param \Drupal\Core\Config\StorageInterface $storage
43    *   A configuration storage to be cached.
44    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
45    *   A cache backend used to store configuration.
46    */
47   public function __construct(StorageInterface $storage, CacheBackendInterface $cache) {
48     $this->storage = $storage;
49     $this->cache = $cache;
50   }
51
52   /**
53    * {@inheritdoc}
54    */
55   public function exists($name) {
56     // The cache would read in the entire data (instead of only checking whether
57     // any data exists), and on a potential cache miss, an additional storage
58     // lookup would have to happen, so check the storage directly.
59     return $this->storage->exists($name);
60   }
61
62   /**
63    * {@inheritdoc}
64    */
65   public function read($name) {
66     $cache_key = $this->getCacheKey($name);
67     if ($cache = $this->cache->get($cache_key)) {
68       // The cache contains either the cached configuration data or FALSE
69       // if the configuration file does not exist.
70       return $cache->data;
71     }
72     // Read from the storage on a cache miss and cache the data. Also cache
73     // information about missing configuration objects.
74     $data = $this->storage->read($name);
75     $this->cache->set($cache_key, $data);
76     return $data;
77   }
78
79   /**
80    * {@inheritdoc}
81    */
82   public function readMultiple(array $names) {
83     $data_to_return = [];
84
85     $cache_keys_map = $this->getCacheKeys($names);
86     $cache_keys = array_values($cache_keys_map);
87     $cached_list = $this->cache->getMultiple($cache_keys);
88
89     if (!empty($cache_keys)) {
90       // $cache_keys_map contains the full $name => $cache_key map, while
91       // $cache_keys contains just the $cache_key values that weren't found in
92       // the cache.
93       // @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple()
94       $names_to_get = array_keys(array_intersect($cache_keys_map, $cache_keys));
95       $list = $this->storage->readMultiple($names_to_get);
96       // Cache configuration objects that were loaded from the storage, cache
97       // missing configuration objects as an explicit FALSE.
98       $items = [];
99       foreach ($names_to_get as $name) {
100         $data = isset($list[$name]) ? $list[$name] : FALSE;
101         $data_to_return[$name] = $data;
102         $items[$cache_keys_map[$name]] = ['data' => $data];
103       }
104
105       $this->cache->setMultiple($items);
106     }
107
108     // Add the configuration objects from the cache to the list.
109     $cache_keys_inverse_map = array_flip($cache_keys_map);
110     foreach ($cached_list as $cache_key => $cache) {
111       $name = $cache_keys_inverse_map[$cache_key];
112       $data_to_return[$name] = $cache->data;
113     }
114
115     // Ensure that only existing configuration objects are returned, filter out
116     // cached information about missing objects.
117     return array_filter($data_to_return);
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public function write($name, array $data) {
124     if ($this->storage->write($name, $data)) {
125       // While not all written data is read back, setting the cache instead of
126       // just deleting it avoids cache rebuild stampedes.
127       $this->cache->set($this->getCacheKey($name), $data);
128       $this->findByPrefixCache = [];
129       return TRUE;
130     }
131     return FALSE;
132   }
133
134   /**
135    * {@inheritdoc}
136    */
137   public function delete($name) {
138     // If the cache was the first to be deleted, another process might start
139     // rebuilding the cache before the storage is gone.
140     if ($this->storage->delete($name)) {
141       $this->cache->delete($this->getCacheKey($name));
142       $this->findByPrefixCache = [];
143       return TRUE;
144     }
145     return FALSE;
146   }
147
148   /**
149    * {@inheritdoc}
150    */
151   public function rename($name, $new_name) {
152     // If the cache was the first to be deleted, another process might start
153     // rebuilding the cache before the storage is renamed.
154     if ($this->storage->rename($name, $new_name)) {
155       $this->cache->delete($this->getCacheKey($name));
156       $this->cache->delete($this->getCacheKey($new_name));
157       $this->findByPrefixCache = [];
158       return TRUE;
159     }
160     return FALSE;
161   }
162
163   /**
164    * {@inheritdoc}
165    */
166   public function encode($data) {
167     return $this->storage->encode($data);
168   }
169
170   /**
171    * {@inheritdoc}
172    */
173   public function decode($raw) {
174     return $this->storage->decode($raw);
175   }
176
177   /**
178    * {@inheritdoc}
179    */
180   public function listAll($prefix = '') {
181     // Do not cache when a prefix is not provided.
182     if ($prefix) {
183       return $this->findByPrefix($prefix);
184     }
185     return $this->storage->listAll();
186   }
187
188   /**
189    * Finds configuration object names starting with a given prefix.
190    *
191    * Given the following configuration objects:
192    * - node.type.article
193    * - node.type.page
194    *
195    * Passing the prefix 'node.type.' will return an array containing the above
196    * names.
197    *
198    * @param string $prefix
199    *   The prefix to search for
200    *
201    * @return array
202    *   An array containing matching configuration object names.
203    */
204   protected function findByPrefix($prefix) {
205     $cache_key = $this->getCacheKey($prefix);
206     if (!isset($this->findByPrefixCache[$cache_key])) {
207       $this->findByPrefixCache[$cache_key] = $this->storage->listAll($prefix);
208     }
209     return $this->findByPrefixCache[$cache_key];
210   }
211
212   /**
213    * {@inheritdoc}
214    */
215   public function deleteAll($prefix = '') {
216     // If the cache was the first to be deleted, another process might start
217     // rebuilding the cache before the storage is renamed.
218     $names = $this->storage->listAll($prefix);
219     if ($this->storage->deleteAll($prefix)) {
220       $this->cache->deleteMultiple($this->getCacheKeys($names));
221       return TRUE;
222     }
223     return FALSE;
224   }
225
226   /**
227    * Clears the static list cache.
228    */
229   public function resetListCache() {
230     $this->findByPrefixCache = [];
231   }
232
233   /**
234    * {@inheritdoc}
235    */
236   public function createCollection($collection) {
237     return new static(
238       $this->storage->createCollection($collection),
239       $this->cache
240     );
241   }
242
243   /**
244    * {@inheritdoc}
245    */
246   public function getAllCollectionNames() {
247     return $this->storage->getAllCollectionNames();
248   }
249
250   /**
251    * {@inheritdoc}
252    */
253   public function getCollectionName() {
254     return $this->storage->getCollectionName();
255   }
256
257   /**
258    * Returns a cache key for a configuration name using the collection.
259    *
260    * @param string $name
261    *   The configuration name.
262    *
263    * @return string
264    *   The cache key for the configuration name.
265    */
266   protected function getCacheKey($name) {
267     return $this->getCollectionPrefix() . $name;
268   }
269
270   /**
271    * Returns a cache key map for an array of configuration names.
272    *
273    * @param array $names
274    *   The configuration names.
275    *
276    * @return array
277    *   An array of cache keys keyed by configuration names.
278    */
279   protected function getCacheKeys(array $names) {
280     $prefix = $this->getCollectionPrefix();
281     $cache_keys = array_map(function ($name) use ($prefix) {
282       return $prefix . $name;
283     }, $names);
284
285     return array_combine($names, $cache_keys);
286   }
287
288   /**
289    * Returns a cache ID prefix to use for the collection.
290    *
291    * @return string
292    *   The cache ID prefix.
293    */
294   protected function getCollectionPrefix() {
295     $collection = $this->storage->getCollectionName();
296     if ($collection == StorageInterface::DEFAULT_COLLECTION) {
297       return '';
298     }
299     return $collection . ':';
300   }
301
302 }