|
10 | 10 | use Magento\Framework\App\Config\Spi\PostProcessorInterface;
|
11 | 11 | use Magento\Framework\App\Config\Spi\PreProcessorInterface;
|
12 | 12 | use Magento\Framework\Cache\FrontendInterface;
|
13 |
| -use Magento\Framework\DataObject; |
| 13 | +use Magento\Framework\App\ObjectManager; |
| 14 | +use Magento\Config\App\Config\Type\System\Reader; |
14 | 15 | use Magento\Store\Model\Config\Processor\Fallback;
|
15 | 16 |
|
16 | 17 | /**
|
17 |
| - * Class process source, cache them and retrieve value by path |
| 18 | + * System configuration type. |
18 | 19 | */
|
19 | 20 | class System implements ConfigTypeInterface
|
20 | 21 | {
|
| 22 | + /** |
| 23 | + * Cache tag. |
| 24 | + */ |
21 | 25 | const CACHE_TAG = 'config_scopes';
|
22 | 26 |
|
| 27 | + /** |
| 28 | + * Config type. |
| 29 | + */ |
23 | 30 | const CONFIG_TYPE = 'system';
|
24 | 31 |
|
25 | 32 | /**
|
| 33 | + * Config source. |
| 34 | + * |
26 | 35 | * @var ConfigSourceInterface
|
27 | 36 | */
|
28 | 37 | private $source;
|
29 | 38 |
|
30 | 39 | /**
|
31 |
| - * @var DataObject[] |
| 40 | + * Object data. |
| 41 | + * |
| 42 | + * @var array |
32 | 43 | */
|
33 |
| - private $data; |
| 44 | + private $data = []; |
34 | 45 |
|
35 | 46 | /**
|
| 47 | + * Postprocessor. |
| 48 | + * |
36 | 49 | * @var PostProcessorInterface
|
37 | 50 | */
|
38 | 51 | private $postProcessor;
|
39 | 52 |
|
40 | 53 | /**
|
| 54 | + * Preprocessor. |
| 55 | + * |
41 | 56 | * @var PreProcessorInterface
|
42 | 57 | */
|
43 | 58 | private $preProcessor;
|
44 | 59 |
|
45 | 60 | /**
|
| 61 | + * Cache. |
| 62 | + * |
46 | 63 | * @var FrontendInterface
|
47 | 64 | */
|
48 | 65 | private $cache;
|
49 | 66 |
|
50 | 67 | /**
|
| 68 | + * Caching nested level. |
| 69 | + * |
51 | 70 | * @var int
|
52 | 71 | */
|
53 | 72 | private $cachingNestedLevel;
|
54 | 73 |
|
55 | 74 | /**
|
| 75 | + * Fallback. |
| 76 | + * |
56 | 77 | * @var Fallback
|
57 | 78 | */
|
58 | 79 | private $fallback;
|
59 | 80 |
|
60 | 81 | /**
|
61 |
| - * Key name for flag which displays whether configuration is cached or not. |
62 |
| - * |
63 |
| - * Once configuration is cached additional flag pushed to cache storage |
64 |
| - * to be able check cache existence without data load. |
| 82 | + * The type of config. |
65 | 83 | *
|
66 | 84 | * @var string
|
67 | 85 | */
|
68 |
| - private $cacheExistenceKey = self::CONFIG_TYPE . '_CACHE_EXISTS'; |
| 86 | + private $configType; |
| 87 | + |
| 88 | + /** |
| 89 | + * Reader. |
| 90 | + * |
| 91 | + * @var Reader |
| 92 | + */ |
| 93 | + private $reader; |
| 94 | + |
| 95 | + /** |
| 96 | + * List of scopes that were retrieved from configuration storage. |
| 97 | + * |
| 98 | + * Is used to make sure that we don't try to load non-existing configuration scopes. |
| 99 | + * |
| 100 | + * @var array |
| 101 | + */ |
| 102 | + private $availableDataScopes = null; |
69 | 103 |
|
70 | 104 | /**
|
71 |
| - * System constructor. |
72 | 105 | * @param ConfigSourceInterface $source
|
73 | 106 | * @param PostProcessorInterface $postProcessor
|
74 | 107 | * @param Fallback $fallback
|
75 | 108 | * @param FrontendInterface $cache
|
76 | 109 | * @param PreProcessorInterface $preProcessor
|
77 | 110 | * @param int $cachingNestedLevel
|
| 111 | + * @param string $configType |
| 112 | + * @param Reader $reader |
78 | 113 | */
|
79 | 114 | public function __construct(
|
80 | 115 | ConfigSourceInterface $source,
|
81 | 116 | PostProcessorInterface $postProcessor,
|
82 | 117 | Fallback $fallback,
|
83 | 118 | FrontendInterface $cache,
|
84 | 119 | PreProcessorInterface $preProcessor,
|
85 |
| - $cachingNestedLevel = 1 |
| 120 | + $cachingNestedLevel = 1, |
| 121 | + $configType = self::CONFIG_TYPE, |
| 122 | + Reader $reader = null |
86 | 123 | ) {
|
87 | 124 | $this->source = $source;
|
88 | 125 | $this->postProcessor = $postProcessor;
|
89 | 126 | $this->preProcessor = $preProcessor;
|
90 | 127 | $this->cache = $cache;
|
91 | 128 | $this->cachingNestedLevel = $cachingNestedLevel;
|
92 | 129 | $this->fallback = $fallback;
|
| 130 | + $this->configType = $configType; |
| 131 | + $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class); |
93 | 132 | }
|
94 | 133 |
|
95 | 134 | /**
|
| 135 | + * System configuration is separated by scopes (default, websites, stores). Configuration of a scope is inherited |
| 136 | + * from its parent scope (store inherits website). |
| 137 | + * |
| 138 | + * Because there can be many scopes on single instance of application, the configuration data can be pretty large, |
| 139 | + * so it does not make sense to load all of it on every application request. That is why we cache configuration |
| 140 | + * data by scope and only load configuration scope when a value from that scope is requested. |
| 141 | + * |
| 142 | + * Possible path values: |
| 143 | + * '' - will return whole system configuration (default scope + all other scopes) |
| 144 | + * 'default' - will return all default scope configuration values |
| 145 | + * '{scopeType}' - will return data from all scopes of a specified {scopeType} (websites, stores) |
| 146 | + * '{scopeType}/{scopeCode}' - will return data for all values of the scope specified by {scopeCode} and scope type |
| 147 | + * '{scopeType}/{scopeCode}/some/config/variable' - will return value of the config variable in the specified scope |
| 148 | + * |
96 | 149 | * @inheritdoc
|
97 | 150 | */
|
98 | 151 | public function get($path = '')
|
99 | 152 | {
|
100 |
| - if ($path === null) { |
101 |
| - $path = ''; |
102 |
| - } |
| 153 | + if ($path === '') { |
| 154 | + $this->data = array_replace_recursive($this->loadAllData(), $this->data); |
103 | 155 |
|
104 |
| - if ($this->isConfigRead($path)) { |
105 |
| - return $this->data->getData($path); |
| 156 | + return $this->data; |
106 | 157 | }
|
| 158 | + $pathParts = explode('/', $path); |
| 159 | + if (count($pathParts) === 1 && $pathParts[0] !== 'default') { |
| 160 | + if (!isset($this->data[$pathParts[0]])) { |
| 161 | + $data = $this->reader->read(); |
| 162 | + $this->data = array_replace_recursive($data, $this->data); |
| 163 | + } |
107 | 164 |
|
108 |
| - if (!empty($path) && $this->isCacheExists()) { |
109 |
| - return $this->readFromCache($path); |
| 165 | + return $this->data[$pathParts[0]]; |
110 | 166 | }
|
| 167 | + $scopeType = array_shift($pathParts); |
| 168 | + if ($scopeType === 'default') { |
| 169 | + if (!isset($this->data[$scopeType])) { |
| 170 | + $this->data = array_replace_recursive($this->loadDefaultScopeData($scopeType), $this->data); |
| 171 | + } |
111 | 172 |
|
112 |
| - $config = $this->loadConfig(); |
113 |
| - $this->cacheConfig($config); |
114 |
| - $this->data = new DataObject($config); |
115 |
| - return $this->data->getData($path); |
116 |
| - } |
| 173 | + return $this->getDataByPathParts($this->data[$scopeType], $pathParts); |
| 174 | + } |
| 175 | + $scopeId = array_shift($pathParts); |
| 176 | + if (!isset($this->data[$scopeType][$scopeId])) { |
| 177 | + $this->data = array_replace_recursive($this->loadScopeData($scopeType, $scopeId), $this->data); |
| 178 | + } |
117 | 179 |
|
118 |
| - /** |
119 |
| - * Check whether configuration is cached |
120 |
| - * @return bool |
121 |
| - */ |
122 |
| - private function isCacheExists() |
123 |
| - { |
124 |
| - return $this->cache->load($this->cacheExistenceKey) !== false; |
| 180 | + return isset($this->data[$scopeType][$scopeId]) |
| 181 | + ? $this->getDataByPathParts($this->data[$scopeType][$scopeId], $pathParts) |
| 182 | + : null; |
125 | 183 | }
|
126 | 184 |
|
127 | 185 | /**
|
128 |
| - * Explode path by '/'(forward slash) separator |
| 186 | + * Load configuration data for all scopes. |
129 | 187 | *
|
130 |
| - * @param string $path |
131 | 188 | * @return array
|
132 | 189 | */
|
133 |
| - private function getPathParts($path) |
| 190 | + private function loadAllData() |
134 | 191 | {
|
135 |
| - $pathParts = []; |
136 |
| - if (strpos($path, '/') !== false) { |
137 |
| - $pathParts = explode('/', $path); |
| 192 | + $cachedData = $this->cache->load($this->configType); |
| 193 | + if ($cachedData === false) { |
| 194 | + $data = $this->reader->read(); |
| 195 | + } else { |
| 196 | + $data = unserialize($cachedData); |
138 | 197 | }
|
139 |
| - return $pathParts; |
| 198 | + |
| 199 | + return $data; |
140 | 200 | }
|
141 | 201 |
|
142 | 202 | /**
|
143 |
| - * Check whether requested configuration data is read to memory |
| 203 | + * Load configuration data for default scope. |
144 | 204 | *
|
145 |
| - * @param string $path |
146 |
| - * @return bool |
| 205 | + * @param string $scopeType |
| 206 | + * @return array |
147 | 207 | */
|
148 |
| - private function isConfigRead($path) |
| 208 | + private function loadDefaultScopeData($scopeType) |
149 | 209 | {
|
150 |
| - $pathParts = $this->getPathParts($path); |
151 |
| - return !empty($pathParts) && isset($this->data[$pathParts[0]][$pathParts[1]]); |
| 210 | + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); |
| 211 | + if ($cachedData === false) { |
| 212 | + $data = $this->reader->read(); |
| 213 | + $this->cacheData($data); |
| 214 | + } else { |
| 215 | + $data = [$scopeType => unserialize($cachedData)]; |
| 216 | + } |
| 217 | + |
| 218 | + return $data; |
152 | 219 | }
|
153 | 220 |
|
154 | 221 | /**
|
155 |
| - * Load configuration from all the sources |
| 222 | + * Load configuration data for a specified scope. |
156 | 223 | *
|
| 224 | + * @param string $scopeType |
| 225 | + * @param string $scopeId |
157 | 226 | * @return array
|
158 | 227 | */
|
159 |
| - private function loadConfig() |
| 228 | + private function loadScopeData($scopeType, $scopeId) |
160 | 229 | {
|
161 |
| - $data = $this->preProcessor->process($this->source->get()); |
162 |
| - $this->data = new DataObject($data); |
163 |
| - $data = $this->fallback->process($data); |
164 |
| - $this->data = new DataObject($data); |
| 230 | + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); |
| 231 | + if ($cachedData === false) { |
| 232 | + if ($this->availableDataScopes === null) { |
| 233 | + $cachedScopeData = $this->cache->load($this->configType . '_scopes'); |
| 234 | + if ($cachedScopeData !== false) { |
| 235 | + $this->availableDataScopes = unserialize($cachedScopeData); |
| 236 | + } |
| 237 | + } |
| 238 | + if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) { |
| 239 | + return [$scopeType => [$scopeId => []]]; |
| 240 | + } |
| 241 | + $data = $this->reader->read(); |
| 242 | + $this->cacheData($data); |
| 243 | + } else { |
| 244 | + $data = [$scopeType => [$scopeId => unserialize($cachedData)]]; |
| 245 | + } |
165 | 246 |
|
166 |
| - return $this->postProcessor->process($data); |
| 247 | + return $data; |
167 | 248 | }
|
168 | 249 |
|
169 | 250 | /**
|
170 |
| - * |
171 |
| - * Load configuration and caching it by parts. |
172 |
| - * |
173 |
| - * To be cached configuration is loaded first. |
174 |
| - * Then it is cached by parts to minimize memory usage on load. |
175 |
| - * Additional flag cached as well to give possibility check cache existence without data load. |
| 251 | + * Cache configuration data. |
| 252 | + * Caches data per scope to avoid reading data for all scopes on every request. |
176 | 253 | *
|
177 | 254 | * @param array $data
|
178 | 255 | * @return void
|
179 | 256 | */
|
180 |
| - private function cacheConfig($data) |
| 257 | + private function cacheData(array $data) |
181 | 258 | {
|
182 |
| - foreach ($data as $scope => $scopeData) { |
183 |
| - foreach ($scopeData as $key => $config) { |
| 259 | + $this->cache->save( |
| 260 | + serialize($data), |
| 261 | + $this->configType, |
| 262 | + [self::CACHE_TAG] |
| 263 | + ); |
| 264 | + $this->cache->save( |
| 265 | + serialize($data['default']), |
| 266 | + $this->configType . '_default', |
| 267 | + [self::CACHE_TAG] |
| 268 | + ); |
| 269 | + $scopes = []; |
| 270 | + foreach (['websites', 'stores'] as $curScopeType) { |
| 271 | + foreach ($data[$curScopeType] as $curScopeId => $curScopeData) { |
| 272 | + $scopes[$curScopeType][$curScopeId] = 1; |
184 | 273 | $this->cache->save(
|
185 |
| - serialize($config), |
186 |
| - self::CONFIG_TYPE . '_' . $scope . $key, |
| 274 | + serialize($curScopeData), |
| 275 | + $this->configType . '_' . $curScopeType . '_' . $curScopeId, |
187 | 276 | [self::CACHE_TAG]
|
188 | 277 | );
|
189 | 278 | }
|
190 | 279 | }
|
191 |
| - $this->cache->save('1', $this->cacheExistenceKey, [self::CACHE_TAG]); |
| 280 | + $this->cache->save( |
| 281 | + serialize($scopes), |
| 282 | + $this->configType . "_scopes", |
| 283 | + [self::CACHE_TAG] |
| 284 | + ); |
192 | 285 | }
|
193 | 286 |
|
194 | 287 | /**
|
195 |
| - * Read cached configuration |
| 288 | + * Walk nested hash map by keys from $pathParts. |
196 | 289 | *
|
197 |
| - * @param string $path |
| 290 | + * @param array $data to walk in |
| 291 | + * @param array $pathParts keys path |
198 | 292 | * @return mixed
|
199 | 293 | */
|
200 |
| - private function readFromCache($path) |
| 294 | + private function getDataByPathParts($data, $pathParts) |
201 | 295 | {
|
202 |
| - if ($this->data === null) { |
203 |
| - $this->data = new DataObject(); |
204 |
| - } |
205 |
| - |
206 |
| - $result = null; |
207 |
| - $pathParts = $this->getPathParts($path); |
208 |
| - if (!empty($pathParts)) { |
209 |
| - $result = $this->cache->load(self::CONFIG_TYPE . '_' . $pathParts[0] . $pathParts[1]); |
210 |
| - } |
211 |
| - |
212 |
| - if ($result !== false && $result !== null) { |
213 |
| - $readData = $this->data->getData(); |
214 |
| - $readData[$pathParts[0]][$pathParts[1]] = unserialize($result); |
215 |
| - $this->data = new DataObject($readData); |
| 296 | + foreach ($pathParts as $key) { |
| 297 | + if ((array)$data === $data && isset($data[$key])) { |
| 298 | + $data = $data[$key]; |
| 299 | + } elseif ($data instanceof \Magento\Framework\DataObject) { |
| 300 | + $data = $data->getDataByKey($key); |
| 301 | + } else { |
| 302 | + return null; |
| 303 | + } |
216 | 304 | }
|
217 | 305 |
|
218 |
| - return $this->data->getData($path); |
| 306 | + return $data; |
219 | 307 | }
|
220 | 308 |
|
221 | 309 | /**
|
222 |
| - * Clean cache and global variables cache |
| 310 | + * Clean cache and global variables cache. |
223 | 311 | *
|
224 | 312 | * @return void
|
225 | 313 | */
|
226 | 314 | public function clean()
|
227 | 315 | {
|
228 |
| - $this->data = null; |
| 316 | + $this->data = []; |
229 | 317 | $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
|
230 | 318 | }
|
231 | 319 | }
|
0 commit comments