ThemeRegistry.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace Drupal\Core\Utility;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Cache\CacheBackendInterface;
  5. use Drupal\Core\Cache\CacheCollector;
  6. use Drupal\Core\DestructableInterface;
  7. use Drupal\Core\Lock\LockBackendInterface;
  8. /**
  9. * Builds the run-time theme registry.
  10. *
  11. * A cache collector to allow the theme registry to be accessed as a
  12. * complete registry, while internally caching only the parts of the registry
  13. * that are actually in use on the site. On cache misses the complete
  14. * theme registry is loaded and used to update the run-time cache.
  15. */
  16. class ThemeRegistry extends CacheCollector implements DestructableInterface {
  17. /**
  18. * Whether the partial registry can be persisted to the cache.
  19. *
  20. * This is only allowed if all modules and the request method is GET. _theme()
  21. * should be very rarely called on POST requests and this avoids polluting
  22. * the runtime cache.
  23. */
  24. protected $persistable;
  25. /**
  26. * The complete theme registry array.
  27. */
  28. protected $completeRegistry;
  29. /**
  30. * Constructs a ThemeRegistry object.
  31. *
  32. * @param string $cid
  33. * The cid for the array being cached.
  34. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  35. * The cache backend.
  36. * @param \Drupal\Core\Lock\LockBackendInterface $lock
  37. * The lock backend.
  38. * @param array $tags
  39. * (optional) The tags to specify for the cache item.
  40. * @param bool $modules_loaded
  41. * Whether all modules have already been loaded.
  42. */
  43. public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, $tags = [], $modules_loaded = FALSE) {
  44. $this->cid = $cid;
  45. $this->cache = $cache;
  46. $this->lock = $lock;
  47. $this->tags = $tags;
  48. $this->persistable = $modules_loaded && \Drupal::hasRequest() && \Drupal::request()->isMethod('GET');
  49. // @todo: Implement lazyload.
  50. $this->cacheLoaded = TRUE;
  51. if ($this->persistable && $cached = $this->cache->get($this->cid)) {
  52. $this->storage = $cached->data;
  53. }
  54. else {
  55. // If there is no runtime cache stored, fetch the full theme registry,
  56. // but then initialize each value to NULL. This allows offsetExists()
  57. // to function correctly on non-registered theme hooks without triggering
  58. // a call to resolveCacheMiss().
  59. $this->storage = $this->initializeRegistry();
  60. foreach (array_keys($this->storage) as $key) {
  61. $this->persist($key);
  62. }
  63. // RegistryTest::testRaceCondition() ensures that the cache entry is
  64. // written on the initial construction of the theme registry.
  65. $this->updateCache();
  66. }
  67. }
  68. /**
  69. * Initializes the full theme registry.
  70. *
  71. * @return
  72. * An array with the keys of the full theme registry, but the values
  73. * initialized to NULL.
  74. */
  75. public function initializeRegistry() {
  76. // @todo DIC this.
  77. $this->completeRegistry = \Drupal::service('theme.registry')->get();
  78. return array_fill_keys(array_keys($this->completeRegistry), NULL);
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. public function has($key) {
  84. // Since the theme registry allows for theme hooks to be requested that
  85. // are not registered, just check the existence of the key in the registry.
  86. // Use array_key_exists() here since a NULL value indicates that the theme
  87. // hook exists but has not yet been requested.
  88. return isset($this->storage[$key]) || array_key_exists($key, $this->storage);
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. public function get($key) {
  94. // If the offset is set but empty, it is a registered theme hook that has
  95. // not yet been requested. Offsets that do not exist at all were not
  96. // registered in hook_theme().
  97. if (isset($this->storage[$key])) {
  98. return $this->storage[$key];
  99. }
  100. elseif (array_key_exists($key, $this->storage)) {
  101. return $this->resolveCacheMiss($key);
  102. }
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function resolveCacheMiss($key) {
  108. if (!isset($this->completeRegistry)) {
  109. $this->completeRegistry = \Drupal::service('theme.registry')->get();
  110. }
  111. $this->storage[$key] = $this->completeRegistry[$key];
  112. if ($this->persistable) {
  113. $this->persist($key);
  114. }
  115. return $this->storage[$key];
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. protected function updateCache($lock = TRUE) {
  121. if (!$this->persistable) {
  122. return;
  123. }
  124. // @todo: Is the custom implementation necessary?
  125. $data = [];
  126. foreach ($this->keysToPersist as $offset => $persist) {
  127. if ($persist) {
  128. $data[$offset] = $this->storage[$offset];
  129. }
  130. }
  131. if (empty($data)) {
  132. return;
  133. }
  134. $lock_name = $this->cid . ':' . __CLASS__;
  135. if (!$lock || $this->lock->acquire($lock_name)) {
  136. if ($cached = $this->cache->get($this->cid)) {
  137. // Use array merge instead of union so that filled in values in $data
  138. // overwrite empty values in the current cache.
  139. $data = array_merge($cached->data, $data);
  140. }
  141. else {
  142. $registry = $this->initializeRegistry();
  143. $data = array_merge($registry, $data);
  144. }
  145. $this->cache->set($this->cid, $data, Cache::PERMANENT, $this->tags);
  146. if ($lock) {
  147. $this->lock->release($lock_name);
  148. }
  149. }
  150. }
  151. }