1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Yii 2 config loader |
4
|
|
|
* |
5
|
|
|
* @see https://github.com/sergeymakinen/yii2-config |
6
|
|
|
* @copyright Copyright (c) 2016-2017 Sergey Makinen (https://makinen.ru) |
7
|
|
|
* @license https://github.com/sergeymakinen/yii2-config/blob/master/LICENSE The MIT License |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace sergeymakinen\yii\config; |
11
|
|
|
|
12
|
|
|
use sergeymakinen\yii\phpfilecache\Cache as PhpFileCache; |
13
|
|
|
use sergeymakinen\yii\phpfilecache\ValueWithBootstrap; |
14
|
|
|
use yii\base\BaseObject; |
15
|
|
|
use yii\base\InvalidConfigException; |
16
|
|
|
use yii\caching\Cache; |
17
|
|
|
use yii\di\Instance; |
18
|
|
|
use yii\helpers\ArrayHelper; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Config loader. |
22
|
|
|
* |
23
|
|
|
* @property bool $isCached whether the config is cached. This property is read-only. |
24
|
|
|
*/ |
25
|
|
|
class Config extends BaseObject |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* @var string full path to a base directory to look for configs. |
29
|
|
|
* You may use a path alias here. |
30
|
|
|
*/ |
31
|
|
|
public $configDir; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var bool whether to enable caching. |
35
|
|
|
* The complete configuration will be analyzed and converted |
36
|
|
|
* to a single PHP file which will be cared by a OPcode cacher so it will load almost immediately. |
37
|
|
|
* @since 2.0 |
38
|
|
|
*/ |
39
|
|
|
public $enableCache = false; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var int number of seconds that a cached config can remain valid in a cache. |
43
|
|
|
* Use `0` to never expire. |
44
|
|
|
* @since 2.0 |
45
|
|
|
*/ |
46
|
|
|
public $cacheDuration = 3600; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var Cache|string|array the [[Cache]] object or the application component ID of the [[Cache]] object. |
50
|
|
|
* It can also be an array that is used to create a [[Cache]] instance. |
51
|
|
|
* @since 2.0 |
52
|
|
|
*/ |
53
|
|
|
public $cache = [ |
54
|
|
|
'class' => 'sergeymakinen\yii\phpfilecache\Cache', |
55
|
|
|
'cachePath' => '@yii/../../../runtime/cache', |
56
|
|
|
]; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var bool|null whether to inject the cache config/instance into the main config. |
60
|
|
|
* By default the cache config will only be included if there are `id` & `basePath` keys |
61
|
|
|
* in the main config and no `cacheConfig` key in the `components` array. |
62
|
|
|
* @see $cache |
63
|
|
|
* @since 2.0 |
64
|
|
|
*/ |
65
|
|
|
public $includeCacheConfig; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var string|null tier name (e. g. `console`, `web`, `backend`, `frontend`). |
69
|
|
|
*/ |
70
|
|
|
public $tier; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @var string|null environment name (e. g. `dev`, `test`, `prod`). |
74
|
|
|
*/ |
75
|
|
|
public $env = YII_ENV; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @var string[] array of pathes relative to [[configDir]]. |
79
|
|
|
* [[Config]] will look for configs in each directory in the order they are defined. |
80
|
|
|
* You can use the following substitutions: |
81
|
|
|
* |
82
|
|
|
* | Name | Description |
83
|
|
|
* | --- | --- |
84
|
|
|
* | `{env}` | Config environment name ([[env]]). |
85
|
|
|
* | `{tier}` | Config tier name ([[tier]]). |
86
|
|
|
*/ |
87
|
|
|
public $dirs = ['']; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @var array|string[]|Loader[] config file configurations. |
91
|
|
|
* Array of: |
92
|
|
|
* |
93
|
|
|
* - [[Loader]] instances |
94
|
|
|
* - array that is used to create [[Loader]] instances |
95
|
|
|
* - shortcuts |
96
|
|
|
*/ |
97
|
|
|
public $files = []; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @var string[] registered config file loaders per extension. |
101
|
|
|
*/ |
102
|
|
|
public $loaders = []; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @var Storage the internal config instance. |
106
|
|
|
*/ |
107
|
|
|
protected $storage; |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Loads and returns the [[Config]] instance from the configuration file. |
111
|
|
|
* @param string $path the configuration file path. |
112
|
|
|
* You may use a path alias here. Also the file may have any extension which is loadable by [[Config]] by default. |
113
|
|
|
* @param array $config name-value pairs that will be used to initialize the object properties. |
114
|
|
|
* @return static [[Config]] instance. |
115
|
|
|
* @throws InvalidConfigException |
116
|
|
|
*/ |
117
|
3 |
|
public static function fromFile($path, array $config = []) |
118
|
|
|
{ |
119
|
3 |
|
$fileConfig = (new static([ |
120
|
3 |
|
'includeCacheConfig' => false, |
121
|
3 |
|
'configDir' => dirname($path), |
122
|
3 |
|
'tier' => $path, |
123
|
|
|
'files' => [ |
124
|
|
|
[ |
125
|
3 |
|
'enableLocal' => false, |
126
|
3 |
|
'path' => basename($path), |
127
|
3 |
|
], |
128
|
3 |
|
], |
129
|
3 |
|
]))->load(); |
130
|
3 |
|
return new static(ArrayHelper::merge($fileConfig, $config)); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @inheritDoc |
135
|
|
|
* @throws InvalidConfigException |
136
|
|
|
*/ |
137
|
66 |
|
public function init() |
138
|
|
|
{ |
139
|
66 |
|
parent::init(); |
140
|
66 |
|
if ($this->enableCache) { |
141
|
10 |
|
$this->cache = Instance::ensure($this->cache, Cache::className()); |
|
|
|
|
142
|
10 |
|
} |
143
|
66 |
|
if (empty($this->configDir)) { |
144
|
2 |
|
throw new InvalidConfigException('The "configDir" property must be set.'); |
145
|
|
|
} |
146
|
|
|
|
147
|
65 |
|
$this->configDir = \Yii::getAlias($this->configDir); |
148
|
65 |
|
$this->storage = new Storage(); |
149
|
65 |
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Returns whether the config is cached. |
153
|
|
|
* @return bool whether the config is cached. |
154
|
|
|
*/ |
155
|
3 |
|
public function getIsCached() |
156
|
|
|
{ |
157
|
3 |
|
return $this->cache->exists($this->calculateCacheKey()); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Compiles the config and writes it to the cache. |
162
|
|
|
* @return bool whether caching was successful. |
163
|
|
|
*/ |
164
|
19 |
|
public function cache() |
165
|
|
|
{ |
166
|
19 |
|
$this->compile(); |
167
|
10 |
|
if ($this->cache instanceof PhpFileCache) { |
168
|
8 |
|
$value = new ValueWithBootstrap($this->storage->config, implode("\n\n", $this->storage->bootstrap)); |
169
|
8 |
|
} else { |
170
|
|
|
$value = [ |
171
|
4 |
|
$this->storage->config, |
172
|
4 |
|
implode("\n\n", $this->storage->bootstrap), |
173
|
4 |
|
]; |
174
|
|
|
} |
175
|
10 |
|
return $this->cache->set($this->calculateCacheKey(), $value, $this->cacheDuration); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Removes the cached config. |
180
|
|
|
* @return bool whether flushing was successful. |
181
|
|
|
*/ |
182
|
10 |
|
public function flushCache() |
183
|
|
|
{ |
184
|
10 |
|
return $this->cache->delete($this->calculateCacheKey()); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Loads the config from/ignoring the cache and returns it. |
189
|
|
|
* @return array the config. |
190
|
|
|
*/ |
191
|
36 |
|
public function load() |
192
|
|
|
{ |
193
|
36 |
|
if ($this->enableCache) { |
194
|
3 |
|
$config = $this->loadCached(); |
195
|
3 |
|
if ($config === false) { |
196
|
3 |
|
$config = $this->loadFresh(); |
197
|
3 |
|
$this->cache(); |
198
|
3 |
|
} |
199
|
3 |
|
} else { |
200
|
33 |
|
$config = $this->loadFresh(); |
201
|
|
|
} |
202
|
31 |
|
return $this->includeCacheConfig($config); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Loads the config from the cache. |
207
|
|
|
* @return array|false the config or `false` if loading failed. |
208
|
|
|
*/ |
209
|
3 |
|
protected function loadCached() |
210
|
|
|
{ |
211
|
3 |
|
$token = "Loading config from cache: '{$this->tier}'"; |
212
|
3 |
|
\Yii::beginProfile($token, __METHOD__); |
213
|
3 |
|
$this->storage->reset(); |
214
|
3 |
|
$cacheValue = $this->cache->get($this->calculateCacheKey()); |
215
|
3 |
|
if ($cacheValue === false) { |
216
|
3 |
|
\Yii::endProfile($token, __METHOD__); |
217
|
3 |
|
return false; |
218
|
|
|
} |
219
|
|
|
|
220
|
3 |
|
if ($this->cache instanceof PhpFileCache) { |
221
|
1 |
|
$this->storage->config = $cacheValue; |
222
|
1 |
|
} else { |
223
|
2 |
|
$this->storage->config = $cacheValue[0]; |
224
|
2 |
|
eval($cacheValue[1]); |
225
|
|
|
} |
226
|
3 |
|
\Yii::endProfile($token, __METHOD__); |
227
|
3 |
|
return $this->storage->config; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Loads the config ignoring the cache. |
232
|
|
|
* @return array the config. |
233
|
|
|
*/ |
234
|
36 |
|
protected function loadFresh() |
235
|
|
|
{ |
236
|
36 |
|
$token = "Loading config: '{$this->tier}'"; |
237
|
36 |
|
\Yii::beginProfile($token, __METHOD__); |
238
|
36 |
|
$this->storage->reset(); |
239
|
36 |
|
foreach ($this->resolveLoaders() as $file) { |
240
|
33 |
|
$file->load(); |
241
|
31 |
|
} |
242
|
31 |
|
\Yii::endProfile($token, __METHOD__); |
243
|
31 |
|
return $this->storage->config; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Resolves file configurations into [[Loader]] instances. |
248
|
|
|
* @return Loader[] [[Loader]] instances. |
249
|
|
|
* @throws InvalidConfigException |
250
|
|
|
*/ |
251
|
64 |
|
protected function resolveLoaders() |
252
|
|
|
{ |
253
|
64 |
|
$availableLoaders = array_merge($this->defaultLoaders(), $this->loaders); |
254
|
64 |
|
$loaders = []; |
255
|
64 |
|
foreach ($this->files as $key => $file) { |
256
|
64 |
|
if (is_string($file)) { |
257
|
12 |
|
$file = $this->resolveShortcut($key, $file); |
258
|
12 |
|
} |
259
|
64 |
|
if (is_array($file)) { |
260
|
64 |
|
if (isset($file['class'])) { |
261
|
47 |
|
$class = $file['class']; |
262
|
47 |
|
unset($file['class']); |
263
|
47 |
|
} else { |
264
|
64 |
|
if (!isset($file['path'])) { |
265
|
1 |
|
throw new InvalidConfigException('The "path" property must be set.'); |
266
|
|
|
} |
267
|
|
|
|
268
|
63 |
|
$extension = pathinfo($file['path'], PATHINFO_EXTENSION); |
269
|
63 |
|
if (!isset($availableLoaders[$extension])) { |
270
|
1 |
|
throw new InvalidConfigException("No loader available for the '$extension' extension."); |
271
|
|
|
} |
272
|
|
|
|
273
|
62 |
|
$class = $availableLoaders[$extension]; |
274
|
|
|
} |
275
|
62 |
|
$file = new $class($this, $this->storage, $file); |
276
|
62 |
|
} |
277
|
62 |
|
if ($file instanceof Loader) { |
278
|
62 |
|
$loaders[] = $file; |
279
|
62 |
|
} else { |
280
|
1 |
|
throw new InvalidConfigException('The "files" property must be an array of Loader objects, or configuration arrays, or strings.'); |
281
|
|
|
} |
282
|
62 |
|
} |
283
|
61 |
|
return $loaders; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Maps a shortcut to an array for [[resolveLoaders()]]. |
288
|
|
|
* @param mixed $key the array entry key. |
289
|
|
|
* @param string $value the array entry value. |
290
|
|
|
* @return array a configuration array. |
291
|
|
|
*/ |
292
|
12 |
|
protected function resolveShortcut($key, $value) |
293
|
|
|
{ |
294
|
12 |
|
$file = ['path' => $value]; |
295
|
12 |
|
if (is_string($key)) { |
296
|
11 |
|
$config = explode('@', $key, 2); |
297
|
11 |
|
if ($config[0] !== '') { |
298
|
9 |
|
$tierEnvConfig = explode(':', $config[0], 2); |
299
|
9 |
|
if (isset($tierEnvConfig[1])) { |
300
|
6 |
|
if ($tierEnvConfig[0] !== '') { |
301
|
4 |
|
$file['tier'] = $tierEnvConfig[0]; |
302
|
4 |
|
} |
303
|
6 |
|
if ($tierEnvConfig[1] !== '') { |
304
|
3 |
|
$file['env'] = $tierEnvConfig[1]; |
305
|
3 |
|
} |
306
|
6 |
|
} else { |
307
|
3 |
|
$file['env'] = $config[0]; |
308
|
|
|
} |
309
|
9 |
|
} |
310
|
11 |
|
if (isset($config[1]) && $config[1] !== '') { |
311
|
4 |
|
$file['key'] = $config[1]; |
312
|
4 |
|
} |
313
|
11 |
|
} |
314
|
12 |
|
return $file; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Compiles all specified files according to their configurations. |
319
|
|
|
*/ |
320
|
19 |
|
protected function compile() |
321
|
|
|
{ |
322
|
19 |
|
$token = "Compiling config: '{$this->tier}'"; |
323
|
19 |
|
\Yii::beginProfile($token, __METHOD__); |
324
|
19 |
|
$this->storage->reset(); |
325
|
19 |
|
foreach ($this->resolveLoaders() as $file) { |
326
|
19 |
|
$file->compile(); |
327
|
19 |
|
} |
328
|
10 |
|
\Yii::endProfile($token, __METHOD__); |
329
|
10 |
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Returns an array of default loaders available. |
333
|
|
|
* @return string[] default loaders. |
334
|
|
|
* @since 2.0 |
335
|
|
|
*/ |
336
|
64 |
|
protected function defaultLoaders() |
337
|
|
|
{ |
338
|
|
|
return [ |
339
|
64 |
|
'ini' => 'sergeymakinen\yii\config\IniLoader', |
340
|
64 |
|
'json' => 'sergeymakinen\yii\config\JsonLoader', |
341
|
64 |
|
'php' => 'sergeymakinen\yii\config\PhpArrayLoader', |
342
|
64 |
|
'yaml' => 'sergeymakinen\yii\config\YamlLoader', |
343
|
64 |
|
'yml' => 'sergeymakinen\yii\config\YamlLoader', |
344
|
64 |
|
]; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Calculates and returns a key based on some [[Config]] parameters. |
349
|
|
|
* @return array the cache key. |
350
|
|
|
* @since 2.0 |
351
|
|
|
*/ |
352
|
11 |
|
protected function calculateCacheKey() |
353
|
|
|
{ |
354
|
|
|
return [ |
355
|
11 |
|
$this->tier, |
356
|
11 |
|
$this->env, |
357
|
11 |
|
md5($this->configDir), |
358
|
11 |
|
]; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Merges the config with the cache config and returns it. |
363
|
|
|
* @param array $config |
364
|
|
|
* @return array |
365
|
|
|
*/ |
366
|
31 |
|
private function includeCacheConfig(array $config) |
367
|
|
|
{ |
368
|
|
|
if ( |
369
|
31 |
|
$this->includeCacheConfig === true |
370
|
31 |
|
|| ( |
371
|
31 |
|
$this->includeCacheConfig === null |
372
|
31 |
|
&& isset($config['id'], $config['basePath']) |
373
|
31 |
|
&& !isset($config['components']['configCache']) |
374
|
|
|
) |
375
|
31 |
|
) { |
376
|
1 |
|
$config['components']['configCache'] = $this->cache; |
377
|
1 |
|
} |
378
|
31 |
|
return $config; |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.