CachedStorage.php 8.1 KB

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