Failed Conditions
Pull Request — master (#47)
by Mateusz
24:21
created

Config   D

Complexity

Total Complexity 97

Size/Duplication

Total Lines 758
Duplicated Lines 5.28 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 92.47%

Importance

Changes 9
Bugs 2 Features 3
Metric Value
wmc 97
c 9
b 2
f 3
lcom 1
cbo 2
dl 40
loc 758
ccs 172
cts 186
cp 0.9247
rs 4.4444

28 Methods

Rating   Name   Duplication   Size   Complexity  
A getKeys() 0 4 1
A __construct() 0 6 1
A getBaseConfig() 0 4 1
A get() 0 4 1
B getRaw() 0 20 10
B contains() 0 20 8
A set() 0 21 4
A merge() 0 6 2
A replace() 0 5 1
A remove() 0 14 3
A clear() 0 4 1
A toFlatArray() 0 4 1
A toFlatRawArray() 0 6 3
A toArray() 0 4 1
A toRawArray() 0 12 4
A isEmpty() 0 10 4
C validate() 0 39 16
A assertArray() 10 10 3
A assertNotNull() 0 10 2
A assertString() 10 10 4
A assertInteger() 10 10 4
A assertBoolean() 10 10 4
A assertNonEmpty() 0 9 2
A replacePlaceholders() 0 20 4
A filterByKeyPrefix() 0 15 3
A containsKeyPrefix() 0 10 3
A removeByKeyPrefix() 0 10 3
A addKeyValue() 0 15 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Config, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/repository.itory-manager package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Manager\Api\Config;
13
14
use Puli\Manager\Api\InvalidConfigException;
15
16
/**
17
 * Stores configuration values.
18
 *
19
 * Use the methods {@link get()}, {@link set()} and {@link merge()} to retrieve
20
 * and store values:
21
 *
22
 * ```php
23
 * $config = new Config();
24
 * $config->set(Config::PULI_DIR, '.puli');
25
 *
26
 * echo $config->get(Config::PULI_DIR);
27
 * // => .puli
28
 * ```
29
 *
30
 * You can customize the value returned by {@link get()} if a key is not set
31
 * by passing that value in the second parameter:
32
 *
33
 * ```php
34
 * $config = new Config();
35
 *
36
 * echo $config->get(Config::PULI_DIR, '.puli');
37
 * ```
38
 *
39
 * A configuration may also inherit default values from another configuration:
40
 *
41
 * ```php
42
 * $defaultConfig = new Config();
43
 * $defaultConfig->set(Config::PULI_DIR, '.puli');
44
 *
45
 * $config = new Config($defaultConfig);
46
 *
47
 * $config->get(Config::PULI_DIR);
48
 * // => .puli
49
 * ```
50
 *
51
 * You can disable the fallback to the default value by passing `false` to
52
 * {@link get()}:
53
 *
54
 * ```php
55
 * $defaultConfig = new Config();
56
 * $defaultConfig->set(Config::PULI_DIR, '.puli');
57
 *
58
 * $config = new Config($defaultConfig);
59
 *
60
 * $config->get(Config::PULI_DIR, null, false);
61
 * // => null
62
 * ```
63
 *
64
 * Configuration values support placeholders for other values in the format
65
 * `{$<key>}`. These placeholders will be replaced by the actual values of the
66
 * referenced keys when the values are accessed:
67
 *
68
 * ```php
69
 * $config = new Config();
70
 * $config->set(Config::PULI_DIR, '.puli');
71
 * $config->set(Config::FACTORY_FILE, '{$puli-dir}/PuliFactory.php');
72
 *
73
 * echo $config->get(Config::FACTORY_FILE);
74
 * // => .puli/PuliRegistry.php
75
 * ```
76
 *
77
 * @since  1.0
78
 *
79
 * @author Bernhard Schussek <[email protected]>
80
 */
81
class Config
82
{
83
    const PULI_DIR = 'puli-dir';
84
85
    const BOOTSTRAP_FILE = 'bootstrap-file';
86
87
    const FACTORY = 'factory';
88
89
    const FACTORY_AUTO_GENERATE = 'factory.auto-generate';
90
91
    const FACTORY_IN = 'factory.in';
92
93
    const FACTORY_IN_CLASS = 'factory.in.class';
94
95
    const FACTORY_IN_FILE = 'factory.in.file';
96
97
    const FACTORY_OUT = 'factory.out';
98
99
    const FACTORY_OUT_CLASS = 'factory.out.class';
100
101
    const FACTORY_OUT_FILE = 'factory.out.file';
102
103
    const REPOSITORY = 'repository';
104
105
    const REPOSITORY_TYPE = 'repository.type';
106
107
    const REPOSITORY_PATH = 'repository.path';
108
109
    const REPOSITORY_SYMLINK = 'repository.symlink';
110
111
    const REPOSITORY_OPTIMIZE = 'repository.optimize';
112
113
    const REPOSITORY_STORE = 'repository.store';
114
115
    const REPOSITORY_STORE_TYPE = 'repository.store.type';
116
117
    const REPOSITORY_STORE_PATH = 'repository.store.path';
118
119
    const REPOSITORY_STORE_HOST = 'repository.store.host';
120
121
    const REPOSITORY_STORE_PORT = 'repository.store.port';
122
123
    const REPOSITORY_STORE_BUCKET = 'repository.store.bucket';
124
125
    const REPOSITORY_STORE_CACHE = 'repository.store.cache';
126
127
    const CHANGE_STREAM = 'change-stream';
128
129
    const CHANGE_STREAM_TYPE = 'change-stream.type';
130
131
    const CHANGE_STREAM_PATH = 'change-stream.path';
132
133
    const CHANGE_STREAM_STORE = 'change-stream.store';
134
135
    const CHANGE_STREAM_STORE_TYPE = 'change-stream.store.type';
136
137
    const CHANGE_STREAM_STORE_PATH = 'change-stream.store.path';
138
139
    const CHANGE_STREAM_STORE_HOST = 'change-stream.store.host';
140
141
    const CHANGE_STREAM_STORE_PORT = 'change-stream.store.port';
142
143
    const CHANGE_STREAM_STORE_BUCKET = 'change-stream.store.bucket';
144
145
    const CHANGE_STREAM_STORE_CACHE = 'change-stream.store.cache';
146
147
    const DISCOVERY = 'discovery';
148
149
    const DISCOVERY_TYPE = 'discovery.type';
150
151
    const DISCOVERY_PATH = 'discovery.path';
152
153
    const DISCOVERY_STORE = 'discovery.store';
154
155
    const DISCOVERY_STORE_TYPE = 'discovery.store.type';
156
157
    const DISCOVERY_STORE_PATH = 'discovery.store.path';
158
159
    const DISCOVERY_STORE_HOST = 'discovery.store.host';
160
161
    const DISCOVERY_STORE_PORT = 'discovery.store.port';
162
163
    const DISCOVERY_STORE_BUCKET = 'discovery.store.bucket';
164
165
    const DISCOVERY_STORE_CACHE = 'discovery.store.cache';
166
167
    const CACHE_FILE = 'cache-file';
168
169
    /**
170
     * The accepted config keys.
171
     *
172
     * @var bool[]
173
     */
174
    private static $keys = array(
175
        self::PULI_DIR => true,
176
        self::BOOTSTRAP_FILE => true,
177
        self::FACTORY_AUTO_GENERATE => true,
178
        self::FACTORY_IN_CLASS => true,
179
        self::FACTORY_IN_FILE => true,
180
        self::FACTORY_OUT_CLASS => true,
181
        self::FACTORY_OUT_FILE => true,
182
        self::REPOSITORY_TYPE => true,
183
        self::REPOSITORY_PATH => true,
184
        self::REPOSITORY_SYMLINK => true,
185
        self::REPOSITORY_OPTIMIZE => true,
186
        self::REPOSITORY_STORE_TYPE => true,
187
        self::REPOSITORY_STORE_PATH => true,
188
        self::REPOSITORY_STORE_HOST => true,
189
        self::REPOSITORY_STORE_PORT => true,
190
        self::REPOSITORY_STORE_BUCKET => true,
191
        self::REPOSITORY_STORE_CACHE => true,
192
        self::CHANGE_STREAM_TYPE => true,
193
        self::CHANGE_STREAM_PATH => true,
194
        self::CHANGE_STREAM_STORE_TYPE => true,
195
        self::CHANGE_STREAM_STORE_PATH => true,
196
        self::CHANGE_STREAM_STORE_HOST => true,
197
        self::CHANGE_STREAM_STORE_PORT => true,
198
        self::CHANGE_STREAM_STORE_BUCKET => true,
199
        self::CHANGE_STREAM_STORE_CACHE => true,
200
        self::DISCOVERY_TYPE => true,
201
        self::DISCOVERY_PATH => true,
202
        self::DISCOVERY_STORE_TYPE => true,
203
        self::DISCOVERY_STORE_PATH => true,
204
        self::DISCOVERY_STORE_HOST => true,
205
        self::DISCOVERY_STORE_PORT => true,
206
        self::DISCOVERY_STORE_BUCKET => true,
207
        self::DISCOVERY_STORE_CACHE => true,
208
        self::CACHE_FILE => true,
209
    );
210
211
    private static $compositeKeys = array(
212
        self::FACTORY => true,
213
        self::FACTORY_IN => true,
214
        self::FACTORY_OUT => true,
215
        self::REPOSITORY => true,
216
        self::REPOSITORY_STORE => true,
217
        self::CHANGE_STREAM => true,
218
        self::CHANGE_STREAM_STORE => true,
219
        self::DISCOVERY => true,
220
        self::DISCOVERY_STORE => true,
221
    );
222
223
    /**
224
     * The configuration values.
225
     *
226
     * @var array
227
     */
228
    private $values = array();
229
230
    /**
231
     * The configuration to fall back to.
232
     *
233
     * @var Config
234
     */
235
    private $baseConfig;
236
237
    /**
238
     * Returns all valid configuration keys.
239
     *
240
     * @return string[] The config keys.
0 ignored issues
show
Documentation introduced by
Should the return type not be integer[]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
241
     */
242 20
    public static function getKeys()
243
    {
244 20
        return array_keys(self::$keys);
245
    }
246
247
    /**
248
     * Creates a new configuration.
249
     *
250
     * @param Config|null $baseConfig The configuration to fall back to if a value is
251
     *                                not set in here.
252
     * @param array       $values     The values to initially set in the configuration.
253
     */
254 726
    public function __construct(Config $baseConfig = null, array $values = array())
255
    {
256 726
        $this->baseConfig = $baseConfig;
257
258 726
        $this->merge($values);
259 726
    }
260
261
    /**
262
     * Returns the base configuration.
263
     *
264
     * @return Config The base configuration or `null` if none is set.
265
     */
266
    public function getBaseConfig()
267
    {
268
        return $this->baseConfig;
269
    }
270
271
    /**
272
     * Returns the value of a configuration key.
273
     *
274
     * If fallback is enabled, the value of the base configuration is returned
275
     * if the key was not set.
276
     *
277
     * You can also pass a default value in the second parameter. This default
278
     * value is returned if the configuration key was neither found in this nor
279
     * in its fallback configuration.
280
     *
281
     * @param string $key      The configuration key.
282
     * @param mixed  $default  The value to return if the key was not set.
283
     * @param bool   $fallback Whether to return the value of the base
284
     *                         configuration if the key was not set.
285
     *
286
     * @return mixed The value of the configuration key.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|object|integer|double|null|boolean|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
287
     *
288
     * @throws NoSuchConfigKeyException If the configuration key is invalid.
289
     */
290 134
    public function get($key, $default = null, $fallback = true)
291
    {
292 134
        return $this->replacePlaceholders($this->getRaw($key, $default, $fallback), $fallback);
293
    }
294
295
    /**
296
     * Returns the raw value of a configuration key.
297
     *
298
     * Unlike {@link get()}, this method does not resolve placeholders:
299
     *
300
     * ```php
301
     * $config = new Config();
302
     * $config->set(Config::PULI_DIR, '.puli');
303
     * $config->set(Config::INSTALL_FILE, '{$puli-dir}/install-file.json');
304
     *
305
     * echo $config->get(Config::PULI_DIR);
306
     * // => .puli/install-file.json
307
     *
308
     * echo $config->getRaw(Config::PULI_DIR);
309
     * // => {$puli-dir}/install-file.json
310
     * ```
311
     *
312
     * @param string $key      The configuration key.
313
     * @param mixed  $default  The value to return if the key was not set.
314
     * @param bool   $fallback Whether to return the value of the base
315
     *                         configuration if the key was not set.
316
     *
317
     * @return mixed The value of the configuration key.
318
     *
319
     * @throws NoSuchConfigKeyException If the configuration key is invalid.
320
     */
321 160
    public function getRaw($key, $default = null, $fallback = true)
322
    {
323 160
        if (isset(self::$compositeKeys[$key])) {
324 47
            return array_replace_recursive(
325 47
                is_array($default) ? $default : array(),
326 47
                $fallback && $this->baseConfig ? $this->baseConfig->getRaw($key) : array(),
327 47
                $this->filterByKeyPrefix($key.'.')
328
            );
329
        }
330
331 146
        if (!isset(self::$keys[$key])) {
332 2
            throw NoSuchConfigKeyException::forKey($key);
333
        }
334
335 144
        if (!array_key_exists($key, $this->values) && $fallback && $this->baseConfig) {
336 72
            return $this->baseConfig->getRaw($key, $default);
337
        }
338
339 144
        return isset($this->values[$key]) ? $this->values[$key] : $default;
340
    }
341
342
    /**
343
     * Returns whether a configuration key is set.
344
     *
345
     * @param string $key      The configuration key to search.
346
     * @param bool   $fallback Whether to check the base configuration if the
347
     *                         key is not found.
348
     *
349
     * @return bool Returns `true` if the configuration key is set.
350
     *
351
     * @throws NoSuchConfigKeyException If the configuration key is invalid.
352
     */
353 22
    public function contains($key, $fallback = true)
354
    {
355 22
        if (!isset(self::$compositeKeys[$key]) && !isset(self::$keys[$key])) {
356 1
            throw NoSuchConfigKeyException::forKey($key);
357
        }
358
359 21
        if (array_key_exists($key, $this->values)) {
360 13
            return true;
361
        }
362
363 16
        if (isset(self::$compositeKeys[$key]) && $this->containsKeyPrefix($key.'.')) {
364 2
            return true;
365
        }
366
367 16
        if ($fallback && $this->baseConfig) {
368 12
            return $this->baseConfig->contains($key);
369
        }
370
371 16
        return false;
372
    }
373
374
    /**
375
     * Sets the value of a configuration key.
376
     *
377
     * @param string $key   The configuration key.
378
     * @param mixed  $value The value to set.
379
     *
380
     * @throws NoSuchConfigKeyException If the configuration key is invalid.
381
     * @throws InvalidConfigException   If the value is invalid.
382
     */
383 448
    public function set($key, $value)
384
    {
385 448
        if (isset(self::$compositeKeys[$key])) {
386 9
            $this->assertArray($key, $value);
387 9
            $this->removeByKeyPrefix($key.'.');
388
389 9
            foreach ($value as $k => $v) {
390 9
                $this->set($key.'.'.$k, $v);
391
            }
392
393 9
            return;
394
        }
395
396 448
        if (!isset(self::$keys[$key])) {
397 1
            throw NoSuchConfigKeyException::forKey($key);
398
        }
399
400 447
        $this->validate($key, $value);
401
402 422
        $this->values[$key] = $value;
403 422
    }
404
405
    /**
406
     * Sets a list of configuration values.
407
     *
408
     * @param array $values The values to set.
409
     *
410
     * @throws NoSuchConfigKeyException If a configuration key is invalid.
411
     * @throws InvalidConfigException   If a value is invalid.
412
     */
413 726
    public function merge(array $values)
414
    {
415 726
        foreach ($values as $key => $value) {
416 297
            $this->set($key, $value);
417
        }
418 726
    }
419
420
    /**
421
     * Replaces the configuration with a list of configuration values.
422
     *
423
     * @param array $values The values to set.
424
     *
425
     * @throws NoSuchConfigKeyException If a configuration key is invalid.
426
     * @throws InvalidConfigException   If a value is invalid.
427
     */
428 4
    public function replace(array $values)
429
    {
430 4
        $this->clear();
431 4
        $this->merge($values);
432 4
    }
433
434
    /**
435
     * Removes a configuration key.
436
     *
437
     * If the configuration has a base configuration, the default value will
438
     * be returned by {@link get()} after removing the key.
439
     *
440
     * @param string $key The configuration key to remove.
441
     *
442
     * @throws NoSuchConfigKeyException If the configuration key is invalid.
443
     */
444 13
    public function remove($key)
445
    {
446 13
        if (isset(self::$compositeKeys[$key])) {
447 1
            $this->removeByKeyPrefix($key.'.');
448
449 1
            return;
450
        }
451
452 12
        if (!isset(self::$keys[$key])) {
453 1
            throw NoSuchConfigKeyException::forKey($key);
454
        }
455
456 11
        unset($this->values[$key]);
457 11
    }
458
459
    /**
460
     * Removes all configuration keys.
461
     *
462
     * If the configuration has a base configuration, the default values will
463
     * be returned by {@link get()} after removing the keys.
464
     */
465 5
    public function clear()
466
    {
467 5
        $this->values = array();
468 5
    }
469
470
    /**
471
     * Returns all configuration values as flat array.
472
     *
473
     * @param bool $includeFallback Whether to include values set in the base
474
     *                              configuration passed to {@link __construct()}.
475
     *
476
     * @return array The configuration values.
0 ignored issues
show
Documentation introduced by
Should the return type not be array|object|integer|double|null|boolean|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
477
     */
478 5
    public function toFlatArray($includeFallback = true)
479
    {
480 5
        return $this->replacePlaceholders($this->toFlatRawArray($includeFallback), $includeFallback);
481
    }
482
483
    /**
484
     * Returns all raw configuration values as flat array.
485
     *
486
     * Unlike {@link toFlatArray()}, this method does not resolve placeholders:
487
     *
488
     * ```php
489
     * $config = new Config();
490
     * $config->set(Config::PULI_DIR, '.puli');
491
     * $config->set(Config::REGISTRY_FILE, '{$puli-dir}/ServiceRegistry.php');
492
     *
493
     * print_r($config->toFlatArray());
494
     * // Array(
495
     * //   'puli-dir' => '.puli',
496
     * //   'registry-file' => '.puli/ServiceRegistry.php',
497
     * // )
498
     *
499
     * print_r($config->toFlatRawArray());
500
     * // Array(
501
     * //   'puli-dir' => '.puli',
502
     * //   'registry-file' => '{$puli-dir}/ServiceRegistry.php',
503
     * // )
504
     * ```
505
     *
506
     * @param bool $includeFallback Whether to include values set in the base
507
     *                              configuration passed to {@link __construct()}.
508
     *
509
     * @return array The raw configuration values.
510
     */
511 34
    public function toFlatRawArray($includeFallback = true)
512
    {
513 34
        return $includeFallback && $this->baseConfig
514 9
            ? array_replace($this->baseConfig->toFlatRawArray(), $this->values)
515 34
            : $this->values;
516
    }
517
518
    /**
519
     * Returns all configuration values as nested array.
520
     *
521
     * @param bool $includeFallback Whether to include values set in the base
522
     *                              configuration passed to {@link __construct()}.
523
     *
524
     * @return array The configuration values.
0 ignored issues
show
Documentation introduced by
Should the return type not be array|object|integer|double|null|boolean|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
525
     */
526 3
    public function toArray($includeFallback = true)
527
    {
528 3
        return $this->replacePlaceholders($this->toRawArray($includeFallback), $includeFallback);
529
    }
530
531
    /**
532
     * Returns all raw configuration values as nested array.
533
     *
534
     * Unlike {@link toArray()}, this method does not resolve placeholders:
535
     *
536
     * ```php
537
     * $config = new Config();
538
     * $config->set(Config::PULI_DIR, '.puli');
539
     * $config->set(Config::REPO_STORAGE_DIR, '{$puli-dir}/repository');
540
     *
541
     * print_r($config->toArray());
542
     * // Array(
543
     * //     'puli-dir' => '.puli',
544
     * //     'repository. => array(
545
     * //         'storage-dir' => '.puli/repository',
546
     * //      ),
547
     * // )
548
     *
549
     * print_r($config->toRawArray());
550
     * // Array(
551
     * //     'puli-dir' => '.puli',
552
     * //     'repository. => array(
553
     * //         'storage-dir' => '{$puli-dir}/repository',
554
     * //      ),
555
     * // )
556
     * ```
557
     *
558
     * @param bool $includeFallback Whether to include values set in the base
559
     *                              configuration passed to {@link __construct()}.
560
     *
561
     * @return array The raw configuration values.
562
     */
563 15
    public function toRawArray($includeFallback = true)
564
    {
565 15
        $values = array();
566
567 15
        foreach ($this->values as $key => $value) {
568 8
            $this->addKeyValue($key, $value, $values);
569
        }
570
571 15
        return $includeFallback && $this->baseConfig
572 2
            ? array_replace_recursive($this->baseConfig->toRawArray(), $values)
573 15
            : $values;
574
    }
575
576
    /**
577
     * Returns whether the configuration is empty.
578
     *
579
     * @param bool $includeFallback Whether to include values set in the base
580
     *                              configuration passed to {@link __construct()}.
581
     *
582
     * @return bool Returns `true` if no key is set and `false` otherwise.
583
     */
584 5
    public function isEmpty($includeFallback = true)
585
    {
586 5
        if (!empty($this->values)) {
587 5
            return false;
588
        }
589
590 5
        return $includeFallback && $this->baseConfig
591 2
            ? $this->baseConfig->isEmpty(true)
592 5
            : true;
593
    }
594
595
    /**
596
     * @param string $key
597
     * @param mixed  $value
598
     */
599 447
    private function validate($key, $value)
600
    {
601
        switch ($key) {
602 447
            case self::FACTORY_AUTO_GENERATE:
603 444
            case self::REPOSITORY_SYMLINK:
604 442
            case self::REPOSITORY_OPTIMIZE:
605 440
            case self::REPOSITORY_STORE_CACHE:
606 438
            case self::CHANGE_STREAM_STORE_CACHE:
607 436
            case self::DISCOVERY_STORE_CACHE:
608 310
                $this->assertNotNull($key, $value);
609 310
                $this->assertBoolean($key, $value);
610 304
                break;
611
612 434
            case self::REPOSITORY_STORE_PORT:
613 434
            case self::CHANGE_STREAM_STORE_PORT:
614 434
            case self::DISCOVERY_STORE_PORT:
615
                $this->assertNotNull($key, $value);
616
                $this->assertInteger($key, $value);
617
                break;
618
619 434
            case self::BOOTSTRAP_FILE:
620 430
            case self::FACTORY_IN_FILE:
621 426
            case self::REPOSITORY_STORE_TYPE:
622 425
            case self::CHANGE_STREAM_STORE_TYPE:
623 424
            case self::DISCOVERY_STORE_TYPE:
624 351
                if (null !== $value) {
625 345
                    $this->assertString($key, $value);
626 343
                    $this->assertNonEmpty($key, $value);
627
                }
628 347
                break;
629
630
            default:
631 419
                $this->assertNotNull($key, $value);
632 414
                $this->assertNonEmpty($key, $value);
633 409
                $this->assertString($key, $value);
634
635 404
                break;
636
        }
637 422
    }
638
639
    /**
640
     * @param string $key
641
     * @param mixed  $value
642
     *
643
     * @throws InvalidConfigException If the config value isn't an array.
644
     */
645 9 View Code Duplication
    private function assertArray($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
646
    {
647 9
        if (!is_array($value)) {
648
            throw new InvalidConfigException(sprintf(
649
                'The config key "%s" must be an array. Got: %s',
650
                $key,
651
                is_object($value) ? get_class($value) : gettype($value)
652
            ));
653
        }
654 9
    }
655
656
    /**
657
     * @param string $key
658
     * @param mixed  $value
659
     *
660
     * @throws InvalidConfigException If the config value is null.
661
     */
662 432
    private function assertNotNull($key, $value)
663
    {
664 432
        if (null === $value) {
665 5
            throw new InvalidConfigException(sprintf(
666
                'The config key "%s" must not be null. Use remove() to unset '.
667 5
                'keys.',
668
                $key
669
            ));
670
        }
671 427
    }
672
673
    /**
674
     * @param string $key
675
     * @param mixed  $value
676
     *
677
     * @throws InvalidConfigException If the config value isn't a string.
678
     */
679 418 View Code Duplication
    private function assertString($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
680
    {
681 418
        if (!is_string($value) && null !== $value) {
682 7
            throw new InvalidConfigException(sprintf(
683 7
                'The config key "%s" must be a string. Got: %s',
684
                $key,
685 7
                is_object($value) ? get_class($value) : gettype($value)
686
            ));
687
        }
688 411
    }
689
690
    /**
691
     * @param string $key
692
     * @param mixed  $value
693
     *
694
     * @throws InvalidConfigException If the config value isn't an integer.
695
     */
696 View Code Duplication
    private function assertInteger($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
697
    {
698
        if (!is_int($value) && null !== $value) {
699
            throw new InvalidConfigException(sprintf(
700
                'The config key "%s" must be an integer. Got: %s',
701
                $key,
702
                is_object($value) ? get_class($value) : gettype($value)
703
            ));
704
        }
705
    }
706
707
    /**
708
     * @param string $key
709
     * @param mixed  $value
710
     *
711
     * @throws InvalidConfigException If the config value isn't a boolean.
712
     */
713 310 View Code Duplication
    private function assertBoolean($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
714
    {
715 310
        if (!is_bool($value) && null !== $value) {
716 6
            throw new InvalidConfigException(sprintf(
717 6
                'The config key "%s" must be a bool. Got: %s',
718
                $key,
719 6
                is_object($value) ? get_class($value) : gettype($value)
720
            ));
721
        }
722 304
    }
723
724
    /**
725
     * @param string $key
726
     * @param mixed  $value
727
     *
728
     * @throws InvalidConfigException If the config value is an empty string.
729
     */
730 421
    private function assertNonEmpty($key, $value)
731
    {
732 421
        if ('' === $value) {
733 7
            throw new InvalidConfigException(sprintf(
734 7
                'The value of the config key "%s" must not be empty.',
735
                $key
736
            ));
737
        }
738 414
    }
739
740
    /**
741
     * @param mixed $raw
742
     * @param bool  $fallback
743
     *
744
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|object|integer|double|null|boolean|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
745
     */
746 133
    private function replacePlaceholders($raw, $fallback = true)
747
    {
748 133
        if (is_array($raw)) {
749 47
            foreach ($raw as $key => $value) {
750 45
                $raw[$key] = $this->replacePlaceholders($value, $fallback);
751
            }
752
753 47
            return $raw;
754
        }
755
756 132
        if (!is_string($raw)) {
757 86
            return $raw;
758
        }
759
760 91
        $config = $this;
761
762 91
        return preg_replace_callback('~\{\$(.+)\}~', function ($matches) use ($config, $fallback) {
763 48
            return $config->get($matches[1], null, $fallback);
764 91
        }, $raw);
765
    }
766
767
    /**
768
     * @param string $keyPrefix
769
     *
770
     * @return array
771
     */
772 47
    private function filterByKeyPrefix($keyPrefix)
773
    {
774 47
        $values = array();
775 47
        $offset = strlen($keyPrefix);
776
777 47
        foreach ($this->values as $k => $v) {
778 45
            if (0 !== strpos($k, $keyPrefix)) {
779 32
                continue;
780
            }
781
782 44
            $this->addKeyValue(substr($k, $offset), $v, $values);
783
        }
784
785 47
        return $values;
786
    }
787
788
    /**
789
     * @param string $keyPrefix
790
     *
791
     * @return bool
792
     */
793 3
    private function containsKeyPrefix($keyPrefix)
794
    {
795 3
        foreach ($this->values as $k => $v) {
796 2
            if (0 === strpos($k, $keyPrefix)) {
797 2
                return true;
798
            }
799
        }
800
801 3
        return false;
802
    }
803
804
    /**
805
     * @param string $keyPrefix
806
     */
807 10
    private function removeByKeyPrefix($keyPrefix)
808
    {
809 10
        foreach ($this->values as $k => $v) {
810 7
            if (0 !== strpos($k, $keyPrefix)) {
811 6
                continue;
812
            }
813
814 2
            unset($this->values[$k]);
815
        }
816 10
    }
817
818
    /**
819
     * @param string $key
820
     * @param mixed  $value
821
     * @param array  $values
822
     */
823 52
    private function addKeyValue($key, $value, array &$values)
824
    {
825 52
        $target = &$values;
826 52
        $keyParts = explode('.', $key);
827
828 52
        for ($i = 0, $l = count($keyParts) - 1; $i < $l; ++$i) {
829 40
            if (!isset($target[$keyParts[$i]])) {
830 40
                $target[$keyParts[$i]] = array();
831
            }
832
833 40
            $target = &$target[$keyParts[$i]];
834
        }
835
836 52
        $target[$keyParts[$l]] = $value;
837 52
    }
838
}
839