6
6
namespace Magento \Config \App \Config \Type ;
7
7
8
8
use Magento \Framework \App \Config \ConfigTypeInterface ;
9
- use Magento \Framework \DataObject ;
9
+ use Magento \Framework \App \ObjectManager ;
10
+ use Magento \Config \App \Config \Type \System \Reader ;
10
11
11
12
/**
12
- * Class process source, cache them and retrieve value by path
13
+ * System configuration type
13
14
*/
14
15
class System implements ConfigTypeInterface
15
16
{
@@ -23,9 +24,9 @@ class System implements ConfigTypeInterface
23
24
private $ source ;
24
25
25
26
/**
26
- * @var DataObject
27
+ * @var array
27
28
*/
28
- private $ data ;
29
+ private $ data = [] ;
29
30
30
31
/**
31
32
* @var \Magento\Framework\App\Config\Spi\PostProcessorInterface
@@ -65,14 +66,18 @@ class System implements ConfigTypeInterface
65
66
private $ configType ;
66
67
67
68
/**
68
- * Key name for flag which displays whether configuration is cached or not.
69
+ * @var Reader
70
+ */
71
+ private $ reader ;
72
+
73
+ /**
74
+ * List of scopes that were retrieved from configuration storage
69
75
*
70
- * Once configuration is cached additional flag pushed to cache storage
71
- * to be able check cache existence without data load.
76
+ * Is used to make sure that we don't try to load non-existing configuration scopes.
72
77
*
73
- * @var string
78
+ * @var array
74
79
*/
75
- private $ cacheExistenceKey ;
80
+ private $ availableDataScopes = null ;
76
81
77
82
/**
78
83
* @param \Magento\Framework\App\Config\ConfigSourceInterface $source
@@ -83,6 +88,7 @@ class System implements ConfigTypeInterface
83
88
* @param \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor
84
89
* @param int $cachingNestedLevel
85
90
* @param string $configType
91
+ * @param Reader $reader
86
92
*/
87
93
public function __construct (
88
94
\Magento \Framework \App \Config \ConfigSourceInterface $ source ,
@@ -92,7 +98,8 @@ public function __construct(
92
98
\Magento \Framework \Serialize \SerializerInterface $ serializer ,
93
99
\Magento \Framework \App \Config \Spi \PreProcessorInterface $ preProcessor ,
94
100
$ cachingNestedLevel = 1 ,
95
- $ configType = self ::CONFIG_TYPE
101
+ $ configType = self ::CONFIG_TYPE ,
102
+ Reader $ reader = null
96
103
) {
97
104
$ this ->source = $ source ;
98
105
$ this ->postProcessor = $ postProcessor ;
@@ -102,150 +109,174 @@ public function __construct(
102
109
$ this ->fallback = $ fallback ;
103
110
$ this ->serializer = $ serializer ;
104
111
$ this ->configType = $ configType ;
105
- $ this ->cacheExistenceKey = $ this -> configType . ' _CACHE_EXISTS ' ;
112
+ $ this ->reader = $ reader ?: ObjectManager:: getInstance ()-> get (Reader::class) ;
106
113
}
107
114
108
115
/**
116
+ * System configuration is separated by scopes (default, websites, stores). Configuration of a scope is inherited
117
+ * from its parent scope (store inherits website).
118
+ *
119
+ * Because there can be many scopes on single instance of application, the configuration data can be pretty large,
120
+ * so it does not make sense to load all of it on every application request. That is why we cache configuration
121
+ * data by scope and only load configuration scope when a value from that scope is requested.
122
+ *
123
+ * Possible path values:
124
+ * '' - will return whole system configuration (default scope + all other scopes)
125
+ * 'default' - will return all default scope configuration values
126
+ * '{scopeType}' - will return data from all scopes of a specified {scopeType} (websites, stores)
127
+ * '{scopeType}/{scopeCode}' - will return data for all values of the scope specified by {scopeCode} and scope type
128
+ * '{scopeType}/{scopeCode}/some/config/variable' - will return value of the config variable in the specified scope
129
+ *
109
130
* @inheritdoc
110
131
*/
111
132
public function get ($ path = '' )
112
133
{
113
- if ($ path === null ) {
114
- $ path = '' ;
134
+ if ($ path === '' ) {
135
+ $ this ->data = array_replace_recursive ($ this ->loadAllData (), $ this ->data );
136
+ return $ this ->data ;
115
137
}
116
- if ($ this ->isConfigRead ($ path )) {
117
- return $ this ->data ->getData ($ path );
138
+ $ pathParts = explode ('/ ' , $ path );
139
+ if (count ($ pathParts ) === 1 && $ pathParts [0 ] !== 'default ' ) {
140
+ if (!isset ($ this ->data [$ pathParts [0 ]])) {
141
+ $ data = $ this ->reader ->read ();
142
+ $ this ->data = array_replace_recursive ($ data , $ this ->data );
143
+ }
144
+ return $ this ->data [$ pathParts [0 ]];
118
145
}
119
-
120
- if (!empty ($ path ) && $ this ->isCacheExists ()) {
121
- return $ this ->readFromCache ($ path );
146
+ $ scopeType = array_shift ($ pathParts );
147
+ if ($ scopeType === 'default ' ) {
148
+ if (!isset ($ this ->data [$ scopeType ])) {
149
+ $ this ->data = array_replace_recursive ($ this ->loadDefaultScopeData ($ scopeType ), $ this ->data );
150
+ }
151
+ return $ this ->getDataByPathParts ($ this ->data [$ scopeType ], $ pathParts );
122
152
}
123
-
124
- $ config = $ this ->loadConfig ();
125
- $ this ->cacheConfig ($ config );
126
- $ this ->data = new DataObject ($ config );
127
- return $ this ->data ->getData ($ path );
128
- }
129
-
130
- /**
131
- * Check whether configuration is cached
132
- *
133
- * In case configuration cache exists method 'load' returns
134
- * value equal to $this->cacheExistenceKey
135
- *
136
- * @return bool
137
- */
138
- private function isCacheExists ()
139
- {
140
- return $ this ->cache ->load ($ this ->cacheExistenceKey ) !== false ;
153
+ $ scopeId = array_shift ($ pathParts );
154
+ if (!isset ($ this ->data [$ scopeType ][$ scopeId ])) {
155
+ $ this ->data = array_replace_recursive ($ this ->loadScopeData ($ scopeType , $ scopeId ), $ this ->data );
156
+ }
157
+ return isset ($ this ->data [$ scopeType ][$ scopeId ])
158
+ ? $ this ->getDataByPathParts ($ this ->data [$ scopeType ][$ scopeId ], $ pathParts )
159
+ : null ;
141
160
}
142
161
143
162
/**
144
- * Explode path by '/'(forward slash) separator
145
- *
146
- * In case $path string contains forward slash symbol(/) the $path is exploded and parts array is returned
147
- * In other case empty array is returned
163
+ * Load configuration data for all scopes
148
164
*
149
- * @param string $path
150
165
* @return array
151
166
*/
152
- private function getPathParts ( $ path )
167
+ private function loadAllData ( )
153
168
{
154
- $ pathParts = [];
155
- if (strpos ($ path , '/ ' ) !== false ) {
156
- $ pathParts = explode ('/ ' , $ path );
169
+ $ cachedData = $ this ->cache ->load ($ this ->configType );
170
+ if ($ cachedData === false ) {
171
+ $ data = $ this ->reader ->read ();
172
+ } else {
173
+ $ data = $ this ->serializer ->unserialize ($ cachedData );
157
174
}
158
- return $ pathParts ;
175
+ return $ data ;
159
176
}
160
177
161
178
/**
162
- * Check whether requested configuration data is read to memory
163
- *
164
- * Because of configuration is cached partially each part can be loaded separately
165
- * Method performs check if corresponding system configuration part is already loaded to memory
166
- * and value can be retrieved directly without cache look up
179
+ * Load configuration data for default scope
167
180
*
168
- *
169
- * @param string $path
170
- * @return bool
181
+ * @param string $scopeType
182
+ * @return array
171
183
*/
172
- private function isConfigRead ( $ path )
184
+ private function loadDefaultScopeData ( $ scopeType )
173
185
{
174
- $ pathParts = $ this ->getPathParts ($ path );
175
- return !empty ($ pathParts ) && isset ($ this ->data [$ pathParts [0 ]][$ pathParts [1 ]]);
186
+ $ cachedData = $ this ->cache ->load ($ this ->configType . '_ ' . $ scopeType );
187
+ if ($ cachedData === false ) {
188
+ $ data = $ this ->reader ->read ();
189
+ $ this ->cacheData ($ data );
190
+ } else {
191
+ $ data = [$ scopeType => $ this ->serializer ->unserialize ($ cachedData )];
192
+ }
193
+ return $ data ;
176
194
}
177
195
178
196
/**
179
- * Load configuration from all the sources
180
- *
181
- * System configuration is loaded in 3 steps performing consecutive calls to
182
- * Pre Processor, Fallback Processor, Post Processor accordingly
197
+ * Load configuration data for a specified scope
183
198
*
199
+ * @param string $scopeType
200
+ * @param string $scopeId
184
201
* @return array
185
202
*/
186
- private function loadConfig ( )
203
+ private function loadScopeData ( $ scopeType , $ scopeId )
187
204
{
188
- $ data = $ this ->preProcessor ->process ($ this ->source ->get ());
189
- $ this ->data = new DataObject ($ data );
190
- $ data = $ this ->fallback ->process ($ data );
191
- $ this ->data = new DataObject ($ data );
192
-
193
- return $ this ->postProcessor ->process ($ data );
205
+ $ cachedData = $ this ->cache ->load ($ this ->configType . '_ ' . $ scopeType . '_ ' . $ scopeId );
206
+ if ($ cachedData === false ) {
207
+ if ($ this ->availableDataScopes === null ) {
208
+ $ cachedScopeData = $ this ->cache ->load ($ this ->configType . '_scopes ' );
209
+ if ($ cachedScopeData !== false ) {
210
+ $ this ->availableDataScopes = $ this ->serializer ->unserialize ($ cachedScopeData );
211
+ }
212
+ }
213
+ if (is_array ($ this ->availableDataScopes ) && !isset ($ this ->availableDataScopes [$ scopeType ][$ scopeId ])) {
214
+ return [$ scopeType => [$ scopeId => []]];
215
+ }
216
+ $ data = $ this ->reader ->read ();
217
+ $ this ->cacheData ($ data );
218
+ } else {
219
+ $ data = [$ scopeType => [$ scopeId => $ this ->serializer ->unserialize ($ cachedData )]];
220
+ }
221
+ return $ data ;
194
222
}
195
223
196
224
/**
197
- *
198
- * Load configuration and caching it by parts.
199
- *
200
- * To be cached configuration is loaded first.
201
- * Then it is cached by parts to minimize memory usage on load.
202
- * Additional flag cached as well to give possibility check cache existence without data load.
225
+ * Cache configuration data.
226
+ * Caches data per scope to avoid reading data for all scopes on every request
203
227
*
204
228
* @param array $data
205
229
* @return void
206
230
*/
207
- private function cacheConfig ( $ data )
231
+ private function cacheData ( array $ data )
208
232
{
209
- foreach ($ data as $ scope => $ scopeData ) {
210
- foreach ($ scopeData as $ key => $ config ) {
233
+ $ this ->cache ->save (
234
+ $ this ->serializer ->serialize ($ data ),
235
+ $ this ->configType ,
236
+ [self ::CACHE_TAG ]
237
+ );
238
+ $ this ->cache ->save (
239
+ $ this ->serializer ->serialize ($ data ['default ' ]),
240
+ $ this ->configType . '_default ' ,
241
+ [self ::CACHE_TAG ]
242
+ );
243
+ $ scopes = [];
244
+ foreach (['websites ' , 'stores ' ] as $ curScopeType ) {
245
+ foreach ($ data [$ curScopeType ] as $ curScopeId => $ curScopeData ) {
246
+ $ scopes [$ curScopeType ][$ curScopeId ] = 1 ;
211
247
$ this ->cache ->save (
212
- $ this ->serializer ->serialize ($ config ),
213
- $ this ->configType . '_ ' . $ scope . $ key ,
248
+ $ this ->serializer ->serialize ($ curScopeData ),
249
+ $ this ->configType . '_ ' . $ curScopeType . ' _ ' . $ curScopeId ,
214
250
[self ::CACHE_TAG ]
215
251
);
216
252
}
217
253
}
218
- $ this ->cache ->save ($ this ->cacheExistenceKey , $ this ->cacheExistenceKey , [self ::CACHE_TAG ]);
254
+ $ this ->cache ->save (
255
+ $ this ->serializer ->serialize ($ scopes ),
256
+ $ this ->configType . "_scopes " ,
257
+ [self ::CACHE_TAG ]
258
+ );
219
259
}
220
260
221
261
/**
222
- * Read cached configuration
262
+ * Walk nested hash map by keys from $pathParts
223
263
*
224
- * Read section of system configuration corresponding to requested $path from cache
225
- * Configuration stored to internal property right after load to prevent additional
226
- * requests to cache storage
227
- *
228
- * @param string $path
264
+ * @param array $data to walk in
265
+ * @param array $pathParts keys path
229
266
* @return mixed
230
267
*/
231
- private function readFromCache ( $ path )
268
+ private function getDataByPathParts ( $ data , $ pathParts )
232
269
{
233
- if ($ this ->data === null ) {
234
- $ this ->data = new DataObject ();
235
- }
236
-
237
- $ result = null ;
238
- $ pathParts = $ this ->getPathParts ($ path );
239
- if (!empty ($ pathParts )) {
240
- $ result = $ this ->cache ->load ($ this ->configType . '_ ' . $ pathParts [0 ] . $ pathParts [1 ]);
241
- if ($ result !== false ) {
242
- $ readData = $ this ->data ->getData ();
243
- $ readData [$ pathParts [0 ]][$ pathParts [1 ]] = $ this ->serializer ->unserialize ($ result );
244
- $ this ->data ->setData ($ readData );
270
+ foreach ($ pathParts as $ key ) {
271
+ if ((array )$ data === $ data && isset ($ data [$ key ])) {
272
+ $ data = $ data [$ key ];
273
+ } elseif ($ data instanceof \Magento \Framework \DataObject) {
274
+ $ data = $ data ->getDataByKey ($ key );
275
+ } else {
276
+ return null ;
245
277
}
246
278
}
247
-
248
- return $ this ->data ->getData ($ path );
279
+ return $ data ;
249
280
}
250
281
251
282
/**
@@ -259,7 +290,7 @@ private function readFromCache($path)
259
290
*/
260
291
public function clean ()
261
292
{
262
- $ this ->data = null ;
293
+ $ this ->data = [] ;
263
294
$ this ->cache ->clean (\Zend_Cache::CLEANING_MODE_MATCHING_TAG , [self ::CACHE_TAG ]);
264
295
}
265
296
}
0 commit comments