Passed
Push — main ( 06ee02...095335 )
by Colin
01:43
created

src/Configuration.php (1 issue)

Labels
Severity
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\DataException;
18
use Dflydev\DotAccessData\Exception\InvalidPathException;
19
use Dflydev\DotAccessData\Exception\MissingPathException;
20
use League\Config\Exception\UnknownOptionException;
21
use League\Config\Exception\ValidationException;
22
use Nette\Schema\Expect;
23
use Nette\Schema\Processor;
24
use Nette\Schema\Schema;
25
use Nette\Schema\ValidationException as NetteValidationException;
26
27
final class Configuration implements ConfigurationBuilderInterface, ConfigurationInterface
28
{
29
    /** @psalm-readonly */
30
    private Data $userConfig;
31
32
    /**
33
     * @var array<string, Schema>
34
     *
35
     * @psalm-allow-private-mutation
36
     */
37
    private array $configSchemas = [];
38
39
    /** @psalm-allow-private-mutation */
40
    private ?Data $finalConfig = null;
41
42
    /**
43
     * @var array<string, mixed>
44
     *
45
     * @psalm-allow-private-mutation
46
     */
47
    private array $cache = [];
48
49
    /** @psalm-readonly */
50
    private ConfigurationInterface $reader;
51
52
    /**
53
     * @param array<string, Schema> $baseSchemas
54
     */
55 36
    public function __construct(array $baseSchemas = [])
56
    {
57 36
        $this->configSchemas = $baseSchemas;
58 36
        $this->userConfig    = new Data();
59
60 36
        $this->reader = new ReadOnlyConfiguration($this);
61 36
    }
62
63
    /**
64
     * Registers a new configuration schema at the given top-level key
65
     *
66
     * @psalm-allow-private-mutation
67
     */
68 6
    public function addSchema(string $key, Schema $schema): void
69
    {
70 6
        $this->invalidate();
71
72 6
        $this->configSchemas[$key] = $schema;
73 6
    }
74
75
    /**
76
     * {@inheritDoc}
77
     *
78
     * @psalm-allow-private-mutation
79
     */
80 6
    public function merge(array $config = []): void
81
    {
82 6
        $this->invalidate();
83
84 6
        $this->userConfig->import($config, Data::REPLACE);
85 6
    }
86
87
    /**
88
     * {@inheritDoc}
89
     *
90
     * @psalm-allow-private-mutation
91
     */
92 24
    public function set(string $key, $value): void
93
    {
94 24
        $this->invalidate();
95
96
        try {
97 24
            $this->userConfig->set($key, $value);
98 3
        } catch (DataException $ex) {
99 3
            throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
100
        }
101 24
    }
102
103
    /**
104
     * {@inheritDoc}
105
     *
106
     * @psalm-external-mutation-free
107
     */
108 30
    public function get(string $key)
109
    {
110 30
        if ($this->finalConfig === null) {
111 30
            $this->finalConfig = $this->build();
112 12
        } elseif (\array_key_exists($key, $this->cache)) {
113 3
            return $this->cache[$key];
114
        }
115
116
        try {
117 21
            return $this->cache[$key] = $this->finalConfig->get($key);
0 ignored issues
show
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

117
            return $this->cache[$key] = $this->finalConfig->/** @scrutinizer ignore-call */ get($key);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
118 6
        } catch (InvalidPathException | MissingPathException $ex) {
119 6
            throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
120
        }
121
    }
122
123
    /**
124
     * {@inheritDoc}
125
     *
126
     * @psalm-external-mutation-free
127
     */
128 6
    public function exists(string $key): bool
129
    {
130 6
        if ($this->finalConfig === null) {
131 3
            $this->finalConfig = $this->build();
132 6
        } elseif (\array_key_exists($key, $this->cache)) {
133 3
            return true;
134
        }
135
136
        try {
137 3
            return $this->finalConfig->has($key);
138 3
        } catch (InvalidPathException $ex) {
139 3
            return false;
140
        }
141
    }
142
143
    /**
144
     * @psalm-mutation-free
145
     */
146 3
    public function reader(): ConfigurationInterface
147
    {
148 3
        return $this->reader;
149
    }
150
151
    /**
152
     * @psalm-external-mutation-free
153
     */
154 30
    private function invalidate(): void
155
    {
156 30
        $this->cache       = [];
157 30
        $this->finalConfig = null;
158 30
    }
159
160
    /**
161
     * Applies the schema against the configuration to return the final configuration
162
     *
163
     * @throws ValidationException
164
     *
165
     * @psalm-allow-private-mutation
166
     */
167 33
    private function build(): Data
168
    {
169
        try {
170 33
            $schema    = Expect::structure($this->configSchemas);
171 33
            $processor = new Processor();
172 33
            $processed = $processor->process($schema, $this->userConfig->export());
173
174 24
            $this->raiseAnyDeprecationNotices($processor->getWarnings());
175
176 24
            return $this->finalConfig = new Data(self::convertStdClassesToArrays($processed));
177 15
        } catch (NetteValidationException $ex) {
178 15
            throw new ValidationException($ex);
179
        }
180
    }
181
182
    /**
183
     * Recursively converts stdClass instances to arrays
184
     *
185
     * @param mixed $data
186
     *
187
     * @return mixed
188
     *
189
     * @psalm-pure
190
     */
191 24
    private static function convertStdClassesToArrays($data)
192
    {
193 24
        if ($data instanceof \stdClass) {
194 24
            $data = (array) $data;
195
        }
196
197 24
        if (\is_array($data)) {
198 24
            foreach ($data as $k => $v) {
199 21
                $data[$k] = self::convertStdClassesToArrays($v);
200
            }
201
        }
202
203 24
        return $data;
204
    }
205
206
    /**
207
     * @param string[] $warnings
208
     */
209 24
    private function raiseAnyDeprecationNotices(array $warnings): void
210
    {
211 24
        foreach ($warnings as $warning) {
212 3
            @\trigger_error($warning, \E_USER_DEPRECATED);
213
        }
214 24
    }
215
}
216