Passed
Push — main ( 862db6...ec7436 )
by Colin
01:43
created

Configuration::exists()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the league/config package.
7
 *
8
 * (c) Colin O'Dell <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace League\Config;
15
16
use Dflydev\DotAccessData\Data;
17
use Dflydev\DotAccessData\Exception\InvalidPathException;
18
use Dflydev\DotAccessData\Exception\MissingPathException;
19
use League\Config\Exception\UnknownOptionException;
20
use League\Config\Exception\ValidationException;
21
use Nette\Schema\Expect;
22
use Nette\Schema\Processor;
23
use Nette\Schema\Schema;
24
use Nette\Schema\ValidationException as NetteValidationException;
25
26
final class Configuration implements ConfigurationBuilderInterface, ConfigurationInterface
27
{
28
    /**
29
     * @var Data
30
     *
31
     * @psalm-readonly
32
     */
33
    private $userConfig;
34
35
    /** @var array<string, Schema> */
36
    private $configSchemas = [];
37
38
    /** @var Data|null */
39
    private $finalConfig;
40
41
    /** @var array<string, mixed> */
42
    private $cache = [];
43
44
    /**
45
     * @var ConfigurationInterface
46
     *
47
     * @psalm-readonly
48
     */
49
    private $reader;
50
51
    /**
52
     * @param array<string, Schema> $baseSchemas
53
     */
54 33
    public function __construct(array $baseSchemas = [])
55
    {
56 33
        $this->configSchemas = $baseSchemas;
57 33
        $this->userConfig    = new Data();
58
59 33
        $this->reader = new ReadOnlyConfiguration($this);
60 33
    }
61
62
    /**
63
     * Registers a new configuration schema at the given top-level key
64
     */
65 6
    public function addSchema(string $key, Schema $schema): void
66
    {
67 6
        $this->invalidate();
68
69 6
        $this->configSchemas[$key] = $schema;
70 6
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 6
    public function merge(array $config = []): void
76
    {
77 6
        $this->invalidate();
78
79 6
        $this->userConfig->import($config, Data::REPLACE);
80 6
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 21
    public function set(string $key, $value): void
86
    {
87 21
        $this->invalidate();
88
89 21
        $this->userConfig->set($key, $value);
90 21
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95 30
    public function get(string $key)
96
    {
97 30
        if ($this->finalConfig === null) {
98 30
            $this->finalConfig = $this->build();
99 12
        } elseif (\array_key_exists($key, $this->cache)) {
100 3
            return $this->cache[$key];
101
        }
102
103
        try {
104 21
            return $this->cache[$key] = $this->finalConfig->get($key);
105 6
        } catch (InvalidPathException | MissingPathException $ex) {
106 6
            throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
107
        }
108
    }
109
110 6
    public function exists(string $key): bool
111
    {
112 6
        if ($this->finalConfig === null) {
113 3
            $this->finalConfig = $this->build();
114 6
        } elseif (\array_key_exists($key, $this->cache)) {
115 3
            return true;
116
        }
117
118 3
        return $this->finalConfig->has($key);
119
    }
120
121 3
    public function reader(): ConfigurationInterface
122
    {
123 3
        return $this->reader;
124
    }
125
126 27
    private function invalidate(): void
127
    {
128 27
        $this->cache       = [];
129 27
        $this->finalConfig = null;
130 27
    }
131
132
    /**
133
     * Applies the schema against the configuration to return the final configuration
134
     */
135 33
    private function build(): Data
136
    {
137
        try {
138 33
            $schema    = Expect::structure($this->configSchemas);
139 33
            $processor = new Processor();
140 33
            $processed = $processor->process($schema, $this->userConfig->export());
141
142 24
            $this->raiseAnyDeprecationNotices($processor->getWarnings());
143
144 24
            return $this->finalConfig = new Data(self::convertStdClassesToArrays($processed));
145 15
        } catch (NetteValidationException $ex) {
146 15
            throw new ValidationException($ex);
147
        }
148
    }
149
150
    /**
151
     * Recursively converts stdClass instances to arrays
152
     *
153
     * @param mixed $data
154
     *
155
     * @return mixed
156
     */
157 24
    private static function convertStdClassesToArrays($data)
158
    {
159 24
        if ($data instanceof \stdClass) {
160 24
            $data = (array) $data;
161
        }
162
163 24
        if (\is_array($data)) {
164 24
            foreach ($data as &$v) {
165 21
                $v = self::convertStdClassesToArrays($v);
166
            }
167
        }
168
169 24
        return $data;
170
    }
171
172
    /**
173
     * @param string[] $warnings
174
     */
175 24
    private function raiseAnyDeprecationNotices(array $warnings): void
176
    {
177 24
        foreach ($warnings as $warning) {
178 3
            @\trigger_error($warning, \E_USER_DEPRECATED);
179
        }
180 24
    }
181
}
182