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