Completed
Push — master ( d77511...b11ea7 )
by Peter
12s queued 10s
created

Configuration::normalizeDefaultDatabase()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 13
cts 13
cp 1
rs 9.3222
c 0
b 0
f 0
cc 5
nc 1
nop 1
crap 5
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * GpsLab component.
6
 *
7
 * @author    Peter Gribanov <[email protected]>
8
 * @copyright Copyright (c) 2017, Peter Gribanov
9
 * @license   http://opensource.org/licenses/MIT
10
 */
11
12
namespace GpsLab\Bundle\GeoIP2Bundle\DependencyInjection;
13
14
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
15
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
16
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
17
use Symfony\Component\Config\Definition\ConfigurationInterface;
18
19
class Configuration implements ConfigurationInterface
20
{
21
    private const URL = 'https://download.maxmind.com/app/geoip_download?edition_id=%s&license_key=%s&suffix=tar.gz';
22
23
    private const PATH = '%s/%s.mmdb';
24
25
    /**
26
     * @var string
27
     */
28
    private $cache_dir;
29
30
    /**
31
     * @param string|null $cache_dir
32
     */
33 41
    public function __construct(?string $cache_dir)
34
    {
35 41
        $this->cache_dir = $cache_dir ?: sys_get_temp_dir();
36 41
    }
37
38
    /**
39
     * @return TreeBuilder
40
     */
41 41
    public function getConfigTreeBuilder(): TreeBuilder
42
    {
43 41
        $tree_builder = new TreeBuilder('gpslab_geoip');
44
45 41
        if (method_exists($tree_builder, 'getRootNode')) {
46
            // Symfony 4.2 +
47
            $root_node = $tree_builder->getRootNode();
48
        } else {
49
            // Symfony 4.1 and below
50 41
            $root_node = $tree_builder->root('gpslab_geoip');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

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...
51
        }
52
53 41
        $this->normalizeDefaultDatabase($root_node);
54 41
        $this->normalizeRootConfigurationToDefaultDatabase($root_node);
55 41
        $this->validateAvailableDefaultDatabase($root_node);
56 41
        $this->allowGlobalLicense($root_node);
57 41
        $this->allowGlobalLocales($root_node);
58 41
        $this->validateDatabaseLocales($root_node);
59
60 41
        $root_node->fixXmlConfig('locale');
61 41
        $locales = $root_node->children()->arrayNode('locales');
62 41
        $locales->prototype('scalar');
63
        $locales
64 41
            ->treatNullLike([])
65 41
            ->defaultValue(['en']);
66
67 41
        $root_node->children()->scalarNode('license');
68
69 41
        $default_database = $root_node->children()->scalarNode('default_database');
70 41
        $default_database->defaultValue('default');
71
72 41
        $root_node->fixXmlConfig('database');
73 41
        $root_node->append($this->getDatabaseNode());
74
75 41
        return $tree_builder;
76
    }
77
78
    /**
79
     * @return ArrayNodeDefinition
80
     */
81 41
    private function getDatabaseNode(): ArrayNodeDefinition
82
    {
83 41
        $tree_builder = new TreeBuilder('databases');
84
85 41
        if (method_exists($tree_builder, 'getRootNode')) {
86
            // Symfony 4.2 +
87
            $root_node = $tree_builder->getRootNode();
88
        } else {
89
            // Symfony 4.1 and below
90 41
            $root_node = $tree_builder->root('databases');
0 ignored issues
show
Bug introduced by
The method root() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

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...
91
        }
92
93 41
        $root_node->useAttributeAsKey('name');
94
95
        /** @var ArrayNodeDefinition $database_node */
96 41
        $database_node = $root_node->prototype('array');
97
98 41
        $this->normalizeUrl($database_node);
99 41
        $this->normalizePath($database_node);
100
101 41
        $url = $database_node->children()->scalarNode('url');
102 41
        $url->isRequired();
103
104 41
        $this->validateURL($url);
105
106 41
        $path = $database_node->children()->scalarNode('path');
107 41
        $path->isRequired();
108
109 41
        $database_node->fixXmlConfig('locale');
110 41
        $locales = $database_node->children()->arrayNode('locales');
111 41
        $locales->prototype('scalar');
112
        $locales
113 41
            ->treatNullLike([])
114 41
            ->requiresAtLeastOneElement()
115 41
            ->defaultValue(['en']);
116
117 41
        $database_node->children()->scalarNode('license');
118
119 41
        $database_node->children()->scalarNode('edition');
120
121 41
        return $root_node;
122
    }
123
124
    /**
125
     * Normalize default_database from databases.
126
     *
127
     * @param NodeDefinition $root_node
128
     */
129 41
    private function normalizeDefaultDatabase(NodeDefinition $root_node): void
130
    {
131
        $root_node
132 41
            ->beforeNormalization()
133
            ->ifTrue(static function ($v): bool {
134
                return
135 39
                    is_array($v) &&
136 39
                    !array_key_exists('default_database', $v) &&
137 39
                    array_key_exists('databases', $v) &&
138 39
                    is_array($v['databases']) &&
139 39
                    $v['databases'];
140 41
            })
141
            ->then(static function (array $v): array {
142 10
                $keys = array_keys($v['databases']);
143 10
                $v['default_database'] = reset($keys);
144
145 10
                return $v;
146 41
            });
147 41
    }
148
149
    /**
150
     * Normalize databases root configuration to default_database.
151
     *
152
     * @param NodeDefinition $root_node
153
     */
154 41
    private function normalizeRootConfigurationToDefaultDatabase(NodeDefinition $root_node): void
155
    {
156
        $root_node
157 41
            ->beforeNormalization()
158
            ->ifTrue(static function ($v): bool {
159 39
                return $v && is_array($v) && !array_key_exists('databases', $v) && !array_key_exists('database', $v);
160 41
            })
161
            ->then(static function (array $v): array {
162
                // key that should not be rewritten to the database config
163 10
                $database = [];
164 10
                foreach ($v as $key => $value) {
165 10
                    if ($key !== 'default_database') {
166 10
                        $database[$key] = $v[$key];
167 10
                        unset($v[$key]);
168
                    }
169
                }
170 10
                $v['default_database'] = isset($v['default_database']) ? (string) $v['default_database'] : 'default';
171 10
                $v['databases'] = [$v['default_database'] => $database];
172
173 10
                return $v;
174 41
            });
175 41
    }
176
177
    /**
178
     * Validate that the default_database exists in the list of databases.
179
     *
180
     * @param NodeDefinition $root_node
181
     */
182 41
    private function validateAvailableDefaultDatabase(NodeDefinition $root_node): void
183
    {
184
        $root_node
185 41
            ->validate()
186
                ->ifTrue(static function ($v): bool {
187
                    return
188 31
                        is_array($v) &&
189 31
                        array_key_exists('default_database', $v) &&
190 31
                        array_key_exists('databases', $v) &&
191 31
                        $v['databases'] &&
192 31
                        !array_key_exists($v['default_database'], $v['databases']);
193 41
                })
194
                ->then(static function (array $v): array {
195 4
                    $databases = implode('", "', array_keys($v['databases']));
196
197 4
                    throw new \InvalidArgumentException(sprintf('Undefined default database "%s". Available "%s" databases.', $v['default_database'], $databases));
198 41
                });
199 41
    }
200
201
    /**
202
     * Add a license option to the databases configuration if it does not exist.
203
     * Allow use a global license for all databases.
204
     *
205
     * @param NodeDefinition $root_node
206
     */
207 41
    private function allowGlobalLicense(NodeDefinition $root_node): void
208
    {
209
        $root_node
210 41
            ->beforeNormalization()
211
            ->ifTrue(static function ($v): bool {
212
                return
213 39
                    is_array($v) &&
214 39
                    array_key_exists('license', $v) &&
215 39
                    array_key_exists('databases', $v) &&
216 39
                    is_array($v['databases']);
217 41
            })
218
            ->then(static function (array $v): array {
219 6
                foreach ($v['databases'] as $name => $database) {
220 4
                    if (!array_key_exists('license', $database)) {
221 4
                        $v['databases'][$name]['license'] = $v['license'];
222
                    }
223
                }
224
225 6
                return $v;
226 41
            });
227 41
    }
228
229
    /**
230
     * Add a locales option to the databases configuration if it does not exist.
231
     * Allow use a global locales for all databases.
232
     *
233
     * @param NodeDefinition $root_node
234
     */
235 41
    private function allowGlobalLocales(NodeDefinition $root_node): void
236
    {
237
        $root_node
238 41
            ->beforeNormalization()
239
            ->ifTrue(static function ($v): bool {
240
                return
241 39
                    is_array($v) &&
242 39
                    array_key_exists('locales', $v) &&
243 39
                    array_key_exists('databases', $v) &&
244 39
                    is_array($v['databases']);
245 41
            })
246
            ->then(static function (array $v): array {
247 2
                foreach ($v['databases'] as $name => $database) {
248 2
                    if (!array_key_exists('locales', $database)) {
249 2
                        $v['databases'][$name]['locales'] = $v['locales'];
250
                    }
251
                }
252
253 2
                return $v;
254 41
            });
255 41
    }
256
257
    /**
258
     * Validate database locales.
259
     *
260
     * @param NodeDefinition $root_node
261
     */
262 41
    private function validateDatabaseLocales(NodeDefinition $root_node): void
263
    {
264
        $root_node
265 41
            ->validate()
266
            ->ifTrue(static function ($v): bool {
267 27
                return is_array($v) && array_key_exists('databases', $v) && is_array($v['databases']);
268 41
            })
269
            ->then(static function (array $v): array {
270 27
                foreach ($v['databases'] as $name => $database) {
271 17
                    if (empty($database['locales'])) {
272 17
                        throw new \InvalidArgumentException(sprintf('The list of locales should not be empty in databases "%s".', $name));
273
                    }
274
                }
275
276 27
                return $v;
277 41
            });
278 41
    }
279
280
    /**
281
     * Normalize url option from license key and edition id.
282
     *
283
     * @param NodeDefinition $database_node
284
     */
285 41
    private function normalizeUrl(NodeDefinition $database_node): void
286
    {
287
        $database_node
288 41
            ->beforeNormalization()
289
            ->ifTrue(static function ($v): bool {
290
                return
291 29
                    is_array($v) &&
292 29
                    !array_key_exists('url', $v) &&
293 29
                    array_key_exists('license', $v) &&
294 29
                    array_key_exists('edition', $v);
295 41
            })
296
            ->then(static function (array $v): array {
297 19
                $v['url'] = sprintf(self::URL, urlencode($v['edition']), urlencode($v['license']));
298
299 19
                return $v;
300 41
            });
301 41
    }
302
303
    /**
304
     * Normalize path option from edition id.
305
     *
306
     * @param NodeDefinition $database_node
307
     */
308 41
    private function normalizePath(NodeDefinition $database_node): void
309
    {
310
        $database_node
311 41
            ->beforeNormalization()
312
            ->ifTrue(static function ($v): bool {
313 29
                return is_array($v) && !array_key_exists('path', $v) && array_key_exists('edition', $v);
314 41
            })
315
            ->then(function (array $v): array {
316 23
                $v['path'] = sprintf(self::PATH, $this->cache_dir, $v['edition']);
317
318 23
                return $v;
319 41
            });
320 41
    }
321
322
    /**
323
     * The url option must be a valid URL.
324
     *
325
     * @param NodeDefinition $url
326
     */
327 41
    private function validateURL(NodeDefinition $url): void
328
    {
329
        $url
330 41
            ->validate()
331
            ->ifTrue(static function ($v): bool {
332 27
                return is_string($v) && !filter_var($v, FILTER_VALIDATE_URL);
333 41
            })
334
            ->then(static function (string $v): array {
335 2
                throw new \InvalidArgumentException(sprintf('URL "%s" must be valid.', $v));
336 41
            });
337 41
    }
338
}
339