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