1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Generic Config contract implementation |
4
|
|
|
* |
5
|
|
|
* @package BrightNucleus\Config |
6
|
|
|
* @author Alain Schlesser <[email protected]> |
7
|
|
|
* @license GPL-2.0+ |
8
|
|
|
* @link http://www.brightnucleus.com/ |
9
|
|
|
* @copyright 2016 Alain Schlesser, Bright Nucleus |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace BrightNucleus\Config; |
13
|
|
|
|
14
|
|
|
use BrightNucleus\Config\ConfigSchemaInterface as Schema; |
15
|
|
|
use BrightNucleus\Config\ConfigValidatorInterface as Validator; |
16
|
|
|
use Exception; |
17
|
|
|
use InvalidArgumentException; |
18
|
|
|
use RuntimeException; |
19
|
|
|
use Symfony\Component\OptionsResolver\OptionsResolver; |
20
|
|
|
use UnexpectedValueException; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Class Config |
24
|
|
|
* |
25
|
|
|
* @since 0.1.0 |
26
|
|
|
* |
27
|
|
|
* @package BrightNucleus\Config |
28
|
|
|
* @author Alain Schlesser <[email protected]> |
29
|
|
|
*/ |
30
|
|
|
class Config extends AbstractConfig |
31
|
|
|
{ |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* The schema of the Config file. |
35
|
|
|
* |
36
|
|
|
* @var Schema |
37
|
|
|
*/ |
38
|
|
|
protected $schema; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The Validator class that gets asked to do the validation of the config. |
42
|
|
|
* |
43
|
|
|
* @since 0.1.0 |
44
|
|
|
* |
45
|
|
|
* @var Validator |
46
|
|
|
*/ |
47
|
|
|
protected $validator; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Instantiate the Config object. |
51
|
|
|
* |
52
|
|
|
* It accepts either an array with the configuration settings, or a |
53
|
|
|
* filename pointing to a PHP file it can include. |
54
|
|
|
* |
55
|
|
|
* @since 0.1.0 |
56
|
|
|
* @since 0.1.6 Accepts a delimiter to parse configuration keys. |
57
|
|
|
* |
58
|
|
|
* @param array|string $config Array with settings or filename for the |
59
|
|
|
* settings file. |
60
|
|
|
* @param Schema|null $schema Optional. Config that contains default |
61
|
|
|
* values that can get overwritten. |
62
|
|
|
* @param Validator|null $validator Optional. Validator class that does the |
63
|
|
|
* actual validation. |
64
|
|
|
* @param string[]|string|null $delimiter A string or array of strings that are used as delimiters to parse |
65
|
|
|
* configuration keys. Defaults to "\", "/" & ".". |
66
|
|
|
* @throws InvalidArgumentException If the config source is not a string or |
67
|
|
|
* array. |
68
|
|
|
* @throws RuntimeException If loading of the config source failed. |
69
|
|
|
* @throws UnexpectedValueException If the config file is not valid. |
70
|
|
|
*/ |
71
|
8 |
|
public function __construct( |
72
|
|
|
$config, |
73
|
|
|
Schema $schema = null, |
74
|
|
|
Validator $validator = null, |
75
|
|
|
$delimiter = null |
76
|
|
|
) { |
77
|
8 |
|
$this->schema = $schema; |
78
|
8 |
|
$this->validator = $validator; |
79
|
|
|
|
80
|
|
|
// Make sure $config is either a string or array. |
81
|
8 |
View Code Duplication |
if (! (is_string($config) || is_array($config))) { |
|
|
|
|
82
|
1 |
|
throw new InvalidArgumentException( |
83
|
|
|
sprintf( |
84
|
1 |
|
_('Invalid configuration source: %1$s'), |
85
|
1 |
|
print_r($config, true) |
86
|
|
|
) |
87
|
|
|
); |
88
|
|
|
} |
89
|
|
|
|
90
|
7 |
|
if (is_string($config)) { |
91
|
5 |
|
$config = $this->fetchArrayData($config); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// Run the $config through the OptionsResolver. |
95
|
5 |
|
\Assert\that($config)->isArray(); |
96
|
5 |
|
$config = $this->resolveOptions($config); |
97
|
|
|
|
98
|
|
|
// Instantiate the parent class. |
99
|
|
|
try { |
100
|
5 |
|
parent::__construct($config, $delimiter); |
101
|
|
|
} catch (Exception $exception) { |
102
|
|
|
throw new RuntimeException( |
103
|
|
|
sprintf( |
104
|
|
|
_('Could not instantiate the configuration through its parent. Reason: %1$s'), |
105
|
|
|
$exception->getMessage() |
106
|
|
|
) |
107
|
|
|
); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
// Finally, validate the resulting config. |
111
|
5 |
|
if (! $this->isValid()) { |
112
|
1 |
|
throw new UnexpectedValueException( |
113
|
|
|
sprintf( |
114
|
1 |
|
_('ConfigInterface file is not valid: %1$s'), |
115
|
1 |
|
print_r($config, true) |
116
|
|
|
) |
117
|
|
|
); |
118
|
|
|
} |
119
|
5 |
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Validate the Config file. |
123
|
|
|
* |
124
|
|
|
* @since 0.1.0 |
125
|
|
|
* |
126
|
|
|
* @return boolean |
127
|
|
|
*/ |
128
|
1 |
|
public function isValid() |
129
|
|
|
{ |
130
|
1 |
|
if ($this->validator) { |
131
|
1 |
|
return $this->validator->isValid($this); |
132
|
|
|
} |
133
|
|
|
|
134
|
1 |
|
return true; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Fetch array data from a string pointing to a file. |
139
|
|
|
* |
140
|
|
|
* @since 0.1.0 |
141
|
|
|
* |
142
|
|
|
* @param string $filename Filename for the settings file. |
143
|
|
|
* @return array Array with configuration settings. |
144
|
|
|
* @throws RuntimeException If the config source is a non-existing |
145
|
|
|
* file. |
146
|
|
|
* @throws RuntimeException If loading of the config source failed. |
147
|
|
|
*/ |
148
|
5 |
|
protected function fetchArrayData($filename) |
149
|
|
|
{ |
150
|
|
|
try { |
151
|
|
|
// Assert that $filename is a readable file. |
152
|
5 |
|
\Assert\that($filename) |
153
|
5 |
|
->notEmpty() |
154
|
5 |
|
->file() |
155
|
4 |
|
->readable(); |
156
|
|
|
|
157
|
|
|
// Try to load the file through PHP's include(). |
158
|
|
|
// Make sure we don't accidentally create output. |
159
|
4 |
|
ob_get_contents(); |
160
|
4 |
|
$config = include($filename); |
161
|
4 |
|
ob_clean(); |
162
|
|
|
|
163
|
|
|
// The included should return an array. |
164
|
4 |
|
\Assert\that($config)->isArray(); |
165
|
2 |
|
} catch (Exception $exception) { |
166
|
2 |
|
throw new RuntimeException( |
167
|
|
|
sprintf( |
168
|
2 |
|
_('Loading from configuration source %1$s failed. Reason: %2$s'), |
169
|
2 |
|
(string)$filename, |
170
|
2 |
|
(string)$exception->getMessage() |
171
|
|
|
) |
172
|
|
|
); |
173
|
|
|
} |
174
|
|
|
|
175
|
3 |
|
return $config; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Process the passed-in defaults and merge them with the new values, while |
180
|
|
|
* checking that all required options are set. |
181
|
|
|
* |
182
|
|
|
* @since 0.1.0 |
183
|
|
|
* |
184
|
|
|
* @param array $config Configuration settings to resolve. |
185
|
|
|
* @return array Resolved configuration settings. |
186
|
|
|
* @throws UnexpectedValueException If there are errors while resolving the |
187
|
|
|
* options. |
188
|
|
|
*/ |
189
|
3 |
|
protected function resolveOptions($config) |
190
|
|
|
{ |
191
|
3 |
|
if (! $this->schema) { |
192
|
3 |
|
return $config; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
try { |
196
|
2 |
|
$resolver = new OptionsResolver(); |
197
|
2 |
|
if ($this->configureOptions($resolver)) { |
198
|
2 |
|
$config = $resolver->resolve($config); |
199
|
|
|
} |
200
|
1 |
|
} catch (Exception $exception) { |
201
|
1 |
|
throw new UnexpectedValueException( |
202
|
|
|
sprintf( |
203
|
1 |
|
_('Error while resolving config options: %1$s'), |
204
|
1 |
|
$exception->getMessage() |
205
|
|
|
) |
206
|
|
|
); |
207
|
|
|
} |
208
|
|
|
|
209
|
1 |
|
return $config; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Configure the possible and required options for the Config. |
214
|
|
|
* |
215
|
|
|
* This should return a bool to let the resolve_options() know whether the |
216
|
|
|
* actual resolving needs to be done or not. |
217
|
|
|
* |
218
|
|
|
* @since 0.1.0 |
219
|
|
|
* |
220
|
|
|
* @param OptionsResolver $resolver Reference to the OptionsResolver |
221
|
|
|
* instance. |
222
|
|
|
* @return bool Whether to do the resolving. |
223
|
|
|
* @throws UnexpectedValueException If there are errors while processing. |
224
|
|
|
*/ |
225
|
2 |
|
protected function configureOptions(OptionsResolver $resolver) |
226
|
|
|
{ |
227
|
2 |
|
$defined = $this->schema->getDefinedOptions(); |
228
|
2 |
|
$defaults = $this->schema->getDefaultOptions(); |
229
|
2 |
|
$required = $this->schema->getRequiredOptions(); |
230
|
|
|
|
231
|
2 |
|
if (! $defined && ! $defaults && ! $required) { |
232
|
|
|
return false; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
try { |
236
|
2 |
|
if ($defined) { |
237
|
2 |
|
$resolver->setDefined($defined); |
238
|
|
|
} |
239
|
2 |
|
if ($defaults) { |
240
|
2 |
|
$resolver->setDefaults($defaults); |
241
|
|
|
} |
242
|
2 |
|
if ($required) { |
243
|
2 |
|
$resolver->setRequired($required); |
244
|
|
|
} |
245
|
|
|
} catch (Exception $exception) { |
246
|
|
|
throw new UnexpectedValueException( |
247
|
|
|
sprintf( |
248
|
|
|
_('Error while processing config options: %1$s'), |
249
|
|
|
$exception->getMessage() |
250
|
|
|
) |
251
|
|
|
); |
252
|
|
|
} |
253
|
|
|
|
254
|
2 |
|
return true; |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.