Completed
Push — master ( c317cc...f3046f )
by Basil
02:34
created

Config::env()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace luya;
4
5
use luya\helpers\ArrayHelper;
6
7
/**
8
 * Configuration array Helper.
9
 *
10
 * The {{luya\Config}} allows you to create the configuration for different hosts and difference between web and console config.
11
 *
12
 * ```php
13
 * $config = new Config('myapp', dirname(__DIR__), [
14
 *     'siteTitle' => 'My LUYA Project',
15
 *     'defaultRoute' => 'cms',
16
 *     // other application level configurations
17
 * ]);
18
 *
19
 * // define global components which works either for console or web runtime
20
 *
21
 * $config->component('mail', [
22
 *     'host' => 'xyz',
23
 *     'from' => '[email protected]',
24
 * ]);
25
 *
26
 * $config->component('db', [
27
 *     'class' => 'yii\db\Connection',
28
 *     'dsn' => 'mysql:host=localhost;dbname=prod_db',
29
 *     'username' => 'foo',
30
 *     'password' => 'bar',
31
 * ]);
32
 *
33
 * // define components which are only for web or console runtime:
34
 *
35
 * $config->webComponent('request', [
36
 *     'cookieValidationKey' => 'xyz',
37
 * ]);
38
 *
39
 * // which is equals to, but the above is better to read and structure in the config file
40
 *
41
 * $config->component('request', [
42
 *     'cookieValidationKey' => 'xyz',
43
 * ])->webRuntime();
44
 *
45
 * // adding modules
46
 *
47
 * $config->module('admin', [
48
 *     'class' => 'luya\admin\Module',
49
 *     'secureLogin' => true,
50
 * ]);
51
 *
52
 * $config->module('cms', 'luya\cms\frontend\Module'); // which is equals to $config->module('cms', ['class' => 'luya\cms\frontend\Module']);
53
 *
54
 * // export and generate the config for a given enviroment or environment independent.
55
 *
56
 * return $config->toArray(); // returns the config not taking care of enviroment variables like prod, env
57
 *
58
 * return $config->toArray(Config::ENV_PROD);
59
 * ```
60
 *
61
 * Switching between envs can be usefull if certain configurations should only apply on a certain environment. Therefore you can add `env()` behind componenets, applications and modules.
62
 *
63
 * ```php
64
 * $config->component('db', [
65
 *     'class' => 'yii\db\Connection',
66
 *     'dsn' => 'mysql:host=localhost;dbname=prod_db',
67
 *     'username' => 'foo',
68
 *     'password' => 'bar',
69
 * ])->env(Config::ENV_LOCAL);
70
 *
71
 * $config->component('db', [
72
 *     'class' => 'yii\db\Connection',
73
 *     'dsn' => 'mysql:host=localhost;dbname=prod_db',
74
 *     'username' => 'foo',
75
 *     'password' => 'bar',
76
 * ])->env(Config::ENV_DEV);
77
 *
78
 * $config->component('db', [
79
 *     'class' => 'yii\db\Connection',
80
 *     'dsn' => 'mysql:host=localhost;dbname=prod_db',
81
 *     'username' => 'foo',
82
 *     'password' => 'bar',
83
 * ])->env(Config::ENV_PROD);
84
 *
85
 * return $config->toArray(Config::ENV_PROD); // would only return the prod env db component
86
 * ```
87
 *
88
 * @author Basil Suter <[email protected]>
89
 * @since 1.0.21
90
 */
91
class Config
92
{
93
    const ENV_ALL = 'all';
94
95
    const ENV_PROD = 'prod';
96
    
97
    const ENV_PREP = 'prep';
98
    
99
    const ENV_DEV = 'dev';
100
    
101
    const ENV_LOCAL = 'local';
102
103
    const RUNTIME_ALL = 0;
104
105
    const RUNTIME_CONSOLE = 1;
106
107
    const RUNTIME_WEB = 2;
108
    
109
    /**
110
     * Constructor
111
     *
112
     * @param string $id
113
     * @param string $basePath
114
     * @param array $applicationConfig
115
     */
116
    public function __construct($id, $basePath, array $applicationConfig = [])
117
    {
118
        $applicationConfig['id'] = $id;
119
        $applicationConfig['basePath'] = $basePath;
120
        $this->application($applicationConfig);
121
    }
122
    
123
    private $_env;
124
    
125
    /**
126
     * Assign the env to each component, module or application that defined inside the callback.
127
     *
128
     * Callback function has one parameter with the current {{luya\Config}} object.
129
     *
130
     * @param string $env The environment to assigne inside the callback.
131
     * @param callable $callback function(\luya\Config $config)
132
     * @return $this
133
     */
134
    public function env($env, callable $callback)
135
    {
136
        $this->_env = $env;
137
        
138
        try {
139
            call_user_func($callback, $this);
140
        } finally {
141
            $this->_env = null;
142
        }
143
        
144
        return $this;
145
    }
146
147
    /**
148
     * register application level config
149
     *
150
     * @param array $config The array to configure
151
     * @return ConfigDefinition
152
     */
153
    public function application(array $config)
154
    {
155
        return $this->addDefinition(new ConfigDefinition(ConfigDefinition::GROUP_APPLICATIONS, md5(serialize($config)), $config));
156
    }
157
158
    /**
159
     * Register one or more bootstrap entries into the bootstrap section.
160
     *
161
     * @param array $config An array with bootstrap entries, its common to use the module name
162
     * @return ConfigDefinition
163
     */
164
    public function bootstrap(array $config)
165
    {
166
        return $this->addDefinition(new ConfigDefinition(ConfigDefinition::GROUP_BOOTSTRAPS, md5(serialize($config)), $config));
167
    }
168
169
    /**
170
     * Register a module.
171
     *
172
     * @param string $id The module identifier.
173
     * @param string|array $config The configuration for the given module. If a string is given this will be taken as `class` property.
174
     * @return ConfigDefinition
175
     */
176
    public function module($id, $config)
177
    {
178
        return $this->addDefinition(new ConfigDefinition(ConfigDefinition::GROUP_MODULES, $id, $config));
179
    }
180
181
    /**
182
     * Register a component
183
     *
184
     * @param string $id The id of the component
185
     * @param string|array $config The configuration for the given module. If a string is given this will be taken as `class` property.
186
     * @param string $runtime The runtime for the component: all, web or console
187
     * @return ConfigDefinition
188
     */
189
    public function component($id, $config, $runtime = self::RUNTIME_ALL)
190
    {
191
        return $this->addDefinition(new ConfigDefinition(ConfigDefinition::GROUP_COMPONENTS, $id, $config))->runtime($runtime);
192
    }
193
194
    /**
195
     * Register a web runtime component.
196
     *
197
     * @param string $id The id of the component
198
     * @param string|array $config The configuration for the given module. If a string is given this will be taken as `class` property.
199
     * @return ConfigDefinition
200
     */
201
    public function webComponent($id, $config)
202
    {
203
        return $this->component($id, $config, self::RUNTIME_WEB);
204
    }
205
206
    /**
207
     * Register a console runtime component.
208
     *
209
     * @param string $id The id of the component
210
     * @param string|array $config The configuration for the given module. If a string is given this will be taken as `class` property.
211
     * @return ConfigDefinition
212
     */
213
    public function consoleComponent($id, $config)
214
    {
215
        return $this->component($id, $config, self::RUNTIME_CONSOLE);
216
    }
217
218
    private $_definitions = [];
219
220
    /**
221
     * Add a defintion into the defintions bag.
222
     *
223
     * @param ConfigDefinition $definition
224
     * @return ConfigDefinition
225
     */
226
    private function addDefinition(ConfigDefinition $definition)
227
    {
228
        if ($this->_env !== null) {
229
            $definition->env($this->_env);
230
        }
231
        
232
        $this->_definitions[] = $definition;
233
234
        return $definition;
235
    }
236
237
    private $_isCliRuntime;
238
239
    /**
240
     * Whether runtime is cli or not
241
     *
242
     * @return boolean
243
     */
244
    public function isCliRuntime()
245
    {
246
        if ($this->_isCliRuntime === null) {
247
            $this->_isCliRuntime = strtolower(php_sapi_name()) === 'cli';
248
        }
249
250
        return $this->_isCliRuntime;
251
    }
252
253
    /**
254
     * Setter method for runtime.
255
     *
256
     * > This method is mainly used for unit testing.
257
     *
258
     * @param boolean $value
259
     */
260
    public function setCliRuntime($value)
261
    {
262
        $this->_isCliRuntime = $value;
263
    }
264
265
    /**
266
     * Export the given configuration as array for certain envs.
267
     *
268
     * @param array $envs A list of environments to export. if nothing is given all enviroments will be returned.
269
     * @return array The configuration array
270
     */
271
    public function toArray(array $envs = [])
272
    {
273
        $config = [];
274
        $envs = array_merge($envs, [self::ENV_ALL]);
275
        foreach ($this->_definitions as $definition) { /** @var ConfigDefinition $definition */
276
            // validate if current export env is in the list of envs
277
            if (!$definition->validateEnvs($envs)) {
278
                continue;
279
            }
280
            // validate runtime circumstances
281
            if ($definition->validateRuntime(self::RUNTIME_ALL)) {
282
                $this->appendConfig($config, $definition);
283
            } elseif ($this->isCliRuntime() && $definition->validateRuntime(self::RUNTIME_CONSOLE)) {
284
                $this->appendConfig($config, $definition);
285
            } elseif (!$this->isCliRuntime() && $definition->validateRuntime(self::RUNTIME_WEB)) {
286
                $this->appendConfig($config, $definition);
287
            }
288
        }
289
290
        return $config;
291
    }
292
293
    /**
294
     * Append a given defintion int othe config
295
     *
296
     * @param array $config
297
     * @param ConfigDefinition $definition
298
     */
299
    private function appendConfig(&$config, ConfigDefinition $definition)
300
    {
301
        switch ($definition->getGroup()) {
302
            case ConfigDefinition::GROUP_APPLICATIONS:
303
                foreach ($definition->getConfig() as $k => $v) {
304
                    $config[$k] = $v;
305
                }
306
                break;
307
            case ConfigDefinition::GROUP_COMPONENTS:
308
                $this->handleKeyBaseMerge($config, $definition, 'components');
309
                break;
310
311
            case ConfigDefinition::GROUP_MODULES:
312
                $this->handleKeyBaseMerge($config, $definition, 'modules');
313
                break;
314
315
            case ConfigDefinition::GROUP_BOOTSTRAPS:
316
                if (!array_key_exists('bootstrap', $config)) {
317
                    $config['bootstrap'] = [];
318
                }
319
                foreach ($definition->getConfig() as $v) {
320
                    $config['bootstrap'][] = $v;
321
                }
322
        }
323
    }
324
325
    /**
326
     * Add a array key based component defintion.
327
     *
328
     * @param array $config
329
     * @param ConfigDefinition $definition
330
     * @param string $section
331
     */
332
    private function handleKeyBaseMerge(&$config, ConfigDefinition $definition, $section)
333
    {
334
        // ass missing section key
335
        if (!array_key_exists($section, $config)) {
336
            $config[$section] = [];
337
        }
338
339
        // if key exists, merge otherwise create key
340
        if (isset($config[$section][$definition->getKey()])) {
341
            $config[$section][$definition->getKey()] = ArrayHelper::merge($config[$section][$definition->getKey()], $definition->getConfig());
342
        } else {
343
            $config[$section][$definition->getKey()] = $definition->getConfig();
344
        }
345
    }
346
}
347