Completed
Pull Request — master (#597)
by Greg
03:01
created

Config::getWithFallback()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 4
1
<?php
2
namespace Robo\Config;
3
4
use Dflydev\DotAccessData\Data;
5
6
class Config
7
{
8
    const PROGRESS_BAR_AUTO_DISPLAY_INTERVAL = 'progress-delay';
9
    const DEFAULT_PROGRESS_DELAY = 2;
10
    const SIMULATE = 'simulate';
11
    const INTERACTIVE = 'interactive';
12
    const DECORATED = 'decorated';
13
14
    /**
15
     * @var Data
16
     */
17
    protected $config;
18
19
    /**
20
     * @var array
21
     */
22
    protected $defaults;
23
24
    /**
25
     * Create a new configuration object, and initialize it with
26
     * the provided nested array containing configuration data.
27
     */
28
    public function __construct(array $data = null)
29
    {
30
        $this->config = new Data($data);
31
        $this->defaults = $this->getGlobalOptionDefaultValues();
32
    }
33
34
    /**
35
     * Determine if a non-default config value exists.
36
     */
37
    public function has($key)
38
    {
39
        return ($this->config->has($key));
40
    }
41
42
    /**
43
     * Fetch a configuration value
44
     *
45
     * @param string $key Which config item to look up
46
     * @param string|null $defaultOverride Override usual default value with a different default. Deprecated; provide defaults to the config processor instead.
47
     *
48
     * @return mixed
49
     */
50
    public function get($key, $defaultOverride = null)
51
    {
52
        if ($this->has($key)) {
53
            return $this->config->get($key);
54
        }
55
        return $this->getDefault($key, $defaultOverride);
56
    }
57
58
    /**
59
     * Fetch an option value from a given key, or, if that specific key does
60
     * not contain a value, then consult various fallback options until a
61
     * value is found.
62
     *
63
     * Given the following inputs:
64
     *   - $prefix  = "command."
65
     *   - $group   = "foo.bar.baz"
66
     *   - $postfix = ".options."
67
     * This method will then consider, in order:
68
     *   - command.foo.bar.baz.options
69
     *   - command.foo.bar.options
70
     *   - command.foo.options
71
     * If any of these contain an option for "$key", then return its value.
72
     */
73
    public function getWithFallback($key, $group, $prefix = '', $postfix = '.')
74
    {
75
        $configKey = "{$prefix}{$group}${postfix}{$key}";
76
        if ($this->has($configKey)) {
77
            return $this->get($configKey);
78
        }
79
        if ($this->hasDefault($configKey)) {
80
            return $this->getDefault($configKey);
81
        }
82
        $moreGeneralGroupname = preg_replace('#\.[^.]*$#', '', $group);
83
        if ($moreGeneralGroupname != $group) {
84
            return $this->getWithFallback($key, $moreGeneralGroupname, $prefix, $postfix);
85
        }
86
        return null;
87
    }
88
89
    /**
90
     * Works like 'getWithFallback', but merges results from all applicable
91
     * groups. Settings from most specific group take precedence.
92
     */
93
    public function getWithMerge($key, $group, $prefix = '', $postfix = '.')
94
    {
95
        $configKey = "{$prefix}{$group}${postfix}{$key}";
96
        $result = [];
97
        if ($this->has($configKey)) {
98
            $result = $this->get($configKey);
99
        }
100
        elseif ($this->hasDefault($configKey)) {
101
            $result = $this->getDefault($configKey);
102
        }
103
        if (!is_array($result)) {
104
            throw new \UnexpectedValueException($configKey . ' must be a list of settings to apply.');
105
        }
106
        $moreGeneralGroupname = preg_replace('#\.[^.]*$#', '', $group);
107
        if ($moreGeneralGroupname != $group) {
108
            $result += $this->getWithMerge($key, $moreGeneralGroupname, $prefix, $postfix);
109
        }
110
        return $result;
111
    }
112
113
    /**
114
     * Set a config value
115
     *
116
     * @param string $key
117
     * @param mixed $value
118
     *
119
     * @return $this
120
     */
121
    public function set($key, $value)
122
    {
123
        $this->config->set($key, $value);
124
        return $this;
125
    }
126
127
    /**
128
     * Import configuration from the provided nexted array, replacing whatever
129
     * was here previously. No processing is done on the provided data.
130
     *
131
     * @param array $data
132
     * @return Config
133
     */
134
    public function import($data)
135
    {
136
        $this->config = new Data($data);
137
        if (!empty($data)) {
138
            $this->config->import($data, true);
139
        }
140
        return $this;
141
    }
142
143
    /**
144
     * Export all configuration as a nested array.
145
     */
146
    public function export()
147
    {
148
        return $this->config->export();
149
    }
150
151
    /**
152
     * Given an object that contains configuration methods, inject any
153
     * configuration found in the configuration file.
154
     *
155
     * The proper use for this method is to call setter methods of the
156
     * provided object. Using configuration to call methods that do work
157
     * is an abuse of this mechanism.
158
     *
159
     * TODO: We could use reflection to test to see if the return type
160
     * of the provided object is a reference to the object itself. All
161
     * setter methods should do this. This test is insufficient to guarentee
162
     * that the method is valid, but it would catch almost every misuse.
163
     */
164
    public function applyConfiguration($object, $configurationKey, $group = '', $prefix = '', $postfix = '')
165
    {
166
        if (!empty($group) && empty($postfix)) {
167
            $postfix = '.';
168
        }
169
        $settings = $this->getWithMerge($configurationKey, $group, $prefix, $postfix);
170
        foreach ($settings as $setterMethod => $args) {
171
            // TODO: Should it be possible to make $args a nested array
172
            // to make this code call the setter method multiple times?
173
            $fn = [$object, $setterMethod];
174
            if (is_callable($fn)) {
175
                call_user_func_array($fn, (array)$args);
176
            }
177
        }
178
    }
179
180
    /**
181
     * Return an associative array containing all of the global configuration
182
     * options and their default values.
183
     *
184
     * @return array
185
     */
186
    public function getGlobalOptionDefaultValues()
187
    {
188
        $globalOptions =
189
        [
190
            self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL => self::DEFAULT_PROGRESS_DELAY,
191
            self::SIMULATE => false,
192
        ];
193
194
        return $globalOptions;
195
    }
196
197
    /**
198
     * Return the default value for a given configuration item.
199
     *
200
     * @param string $key
201
     * @param mixed $defaultOverride
0 ignored issues
show
Bug introduced by
There is no parameter named $defaultOverride. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
202
     *
203
     * @return mixed
204
     */
205
    public function hasDefault($key)
206
    {
207
        return isset($this->defaults[$key]);
208
    }
209
210
    /**
211
     * Return the default value for a given configuration item.
212
     *
213
     * @param string $key
214
     * @param mixed $defaultOverride
215
     *
216
     * @return mixed
217
     */
218
    public function getDefault($key, $defaultOverride = null)
219
    {
220
        return $this->hasDefault($key) ? $this->defaults[$key] : $defaultOverride;
221
    }
222
223
    /**
224
     * Set the default value for a configuration setting. This allows us to
225
     * set defaults either before or after more specific configuration values
226
     * are loaded. Keeping defaults separate from current settings also
227
     * allows us to determine when a setting has been overridden.
228
     *
229
     * @param string $key
230
     * @param string $value
231
     */
232
    public function setDefault($key, $value)
233
    {
234
        $this->defaults[$key] = $value;
235
        return $this;
236
    }
237
238
    /**
239
     * @return bool
240
     */
241
    public function isSimulated()
242
    {
243
        return $this->get(self::SIMULATE);
244
    }
245
246
    /**
247
     * @param bool $simulated
248
     *
249
     * @return $this
250
     */
251
    public function setSimulated($simulated = true)
252
    {
253
        return $this->set(self::SIMULATE, $simulated);
254
    }
255
256
    /**
257
     * @return bool
258
     */
259
    public function isInteractive()
260
    {
261
        return $this->get(self::INTERACTIVE);
262
    }
263
264
    /**
265
     * @param bool $simulated
0 ignored issues
show
Bug introduced by
There is no parameter named $simulated. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
266
     *
267
     * @return $this
268
     */
269
    public function setInteractive($interactive = true)
270
    {
271
        return $this->set(self::INTERACTIVE, $interactive);
272
    }
273
274
    /**
275
     * @return bool
276
     */
277
    public function isDecorated()
278
    {
279
        return $this->get(self::DECORATED);
280
    }
281
282
    /**
283
     * @param bool $decorated
284
     *
285
     * @return $this
286
     */
287
    public function setDecorated($decorated = true)
288
    {
289
        return $this->set(self::DECORATED, $decorated);
290
    }
291
292
    /**
293
     * @param int $interval
294
     *
295
     * @return $this
296
     */
297
    public function setProgressBarAutoDisplayInterval($interval)
298
    {
299
        return $this->set(self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval);
300
    }
301
}
302