Completed
Push — master ( 08c5aa...5b6f10 )
by Basil
02:11
created

Config::callback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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
     * Run a callable functions for the defined env when toArray() is called.
183
     *
184
     * @param callable $fn The function to run, the first argument of the closure is the {{luya\Config}} object.
185
     * @return ConfigDefinition
186
     * @since 1.0.23
187
     */
188
    public function callback(callable $fn)
189
    {
190
        return $this->addDefinition(New ConfigDefinition(ConfigDefinition::GROUP_CALLABLE, false, $fn));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
191
    }
192
193
    /**
194
     * Register a component
195
     *
196
     * @param string $id The id of the component
197
     * @param string|array $config The configuration for the given module. If a string is given this will be taken as `class` property.
198
     * @param string $runtime The runtime for the component: all, web or console
199
     * @return ConfigDefinition
200
     */
201
    public function component($id, $config, $runtime = self::RUNTIME_ALL)
202
    {
203
        return $this->addDefinition(new ConfigDefinition(ConfigDefinition::GROUP_COMPONENTS, $id, $config))->runtime($runtime);
204
    }
205
206
    /**
207
     * Register a web 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 webComponent($id, $config)
214
    {
215
        return $this->component($id, $config, self::RUNTIME_WEB);
216
    }
217
218
    /**
219
     * Register a console runtime component.
220
     *
221
     * @param string $id The id of the component
222
     * @param string|array $config The configuration for the given module. If a string is given this will be taken as `class` property.
223
     * @return ConfigDefinition
224
     */
225
    public function consoleComponent($id, $config)
226
    {
227
        return $this->component($id, $config, self::RUNTIME_CONSOLE);
228
    }
229
230
    private $_definitions = [];
231
232
    /**
233
     * Add a definition into the definitions bag.
234
     *
235
     * @param ConfigDefinition $definition
236
     * @return ConfigDefinition
237
     */
238
    private function addDefinition(ConfigDefinition $definition)
239
    {
240
        if ($this->_env !== null) {
241
            $definition->env($this->_env);
242
        }
243
        
244
        $this->_definitions[] = $definition;
245
246
        return $definition;
247
    }
248
249
    private $_isCliRuntime;
250
251
    /**
252
     * Whether runtime is cli or not
253
     *
254
     * @return boolean
255
     */
256
    public function isCliRuntime()
257
    {
258
        if ($this->_isCliRuntime === null) {
259
            $this->_isCliRuntime = strtolower(php_sapi_name()) === 'cli';
260
        }
261
262
        return $this->_isCliRuntime;
263
    }
264
265
    /**
266
     * Setter method for runtime.
267
     *
268
     * > This method is mainly used for unit testing.
269
     *
270
     * @param boolean $value
271
     */
272
    public function setCliRuntime($value)
273
    {
274
        $this->_isCliRuntime = $value;
275
    }
276
277
    /**
278
     * Export the given configuration as array for certain envs.
279
     *
280
     * @param array $envs A list of environments to export. if nothing is given all enviroments will be returned.
281
     * @return array The configuration array
282
     */
283
    public function toArray(array $envs = [])
284
    {
285
        $config = [];
286
        $envs = array_merge($envs, [self::ENV_ALL]);
287
        foreach ($this->_definitions as $definition) { /** @var ConfigDefinition $definition */
288
            // validate if current export env is in the list of envs
289
            if (!$definition->validateEnvs($envs)) {
290
                continue;
291
            }
292
            // validate runtime circumstances
293
            if ($definition->validateRuntime(self::RUNTIME_ALL)) {
294
                $this->appendConfig($config, $definition);
295
            } elseif ($this->isCliRuntime() && $definition->validateRuntime(self::RUNTIME_CONSOLE)) {
296
                $this->appendConfig($config, $definition);
297
            } elseif (!$this->isCliRuntime() && $definition->validateRuntime(self::RUNTIME_WEB)) {
298
                $this->appendConfig($config, $definition);
299
            }
300
        }
301
302
        return $config;
303
    }
304
305
    /**
306
     * Append a given definition int othe config
307
     *
308
     * @param array $config
309
     * @param ConfigDefinition $definition
310
     */
311
    private function appendConfig(&$config, ConfigDefinition $definition)
312
    {
313
        switch ($definition->getGroup()) {
314
            case ConfigDefinition::GROUP_APPLICATIONS:
315
                foreach ($definition->getConfig() as $k => $v) {
0 ignored issues
show
Bug introduced by
The expression $definition->getConfig() of type object|array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
316
                    $config[$k] = $v;
317
                }
318
                break;
319
            case ConfigDefinition::GROUP_COMPONENTS:
320
                $this->handleKeyBaseMerge($config, $definition, 'components');
321
                break;
322
323
            case ConfigDefinition::GROUP_MODULES:
324
                $this->handleKeyBaseMerge($config, $definition, 'modules');
325
                break;
326
327
            case ConfigDefinition::GROUP_BOOTSTRAPS:
328
                if (!array_key_exists('bootstrap', $config)) {
329
                    $config['bootstrap'] = [];
330
                }
331
                foreach ($definition->getConfig() as $v) {
0 ignored issues
show
Bug introduced by
The expression $definition->getConfig() of type object|array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
332
                    $config['bootstrap'][] = $v;
333
                }
334
                break;
335
336
            case ConfigDefinition::GROUP_CALLABLE:
337
                call_user_func($definition->getConfig(), $this);
338
            break;
339
        }
340
    }
341
342
    /**
343
     * Add a array key based component definition.
344
     *
345
     * @param array $config
346
     * @param ConfigDefinition $definition
347
     * @param string $section
348
     */
349
    private function handleKeyBaseMerge(&$config, ConfigDefinition $definition, $section)
350
    {
351
        // ass missing section key
352
        if (!array_key_exists($section, $config)) {
353
            $config[$section] = [];
354
        }
355
356
        // if key exists, merge otherwise create key
357
        if (isset($config[$section][$definition->getKey()])) {
358
            $config[$section][$definition->getKey()] = ArrayHelper::merge($config[$section][$definition->getKey()], $definition->getConfig());
359
        } else {
360
            $config[$section][$definition->getKey()] = $definition->getConfig();
361
        }
362
    }
363
}
364