ThemeRegistry.php 5.1 KB

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