Completed
Pull Request — master (#46)
by Peter
01:33
created

Configuration::validateURL()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.9
c 0
b 0
f 0
cc 2
nc 1
nop 1
crap 2
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 41
            })
140
            ->then(static function (array $v): array {
141 14
                $keys = array_keys($v['databases']);
142 14
                $v['default_database'] = reset($keys);
143
144 14
                return $v;
145 41
            });
146 41
    }
147
148
    /**
149
     * Normalize databases root configuration to default_database
150
     *
151
     * @param NodeDefinition $root_node
152
     */
153 41
    private function normalizeRootConfigurationToDefaultDatabase(NodeDefinition $root_node): void
154
    {
155
        $root_node
156 41
            ->beforeNormalization()
157
            ->ifTrue(static function ($v): bool {
158 39
                return $v && is_array($v) && !array_key_exists('databases', $v) && !array_key_exists('database', $v);
159 41
            })
160
            ->then(static function (array $v): array {
161
                // key that should not be rewritten to the database config
162 10
                $database = [];
163 10
                foreach ($v as $key => $value) {
164 10
                    if ($key !== 'default_database') {
165 10
                        $database[$key] = $v[$key];
166 10
                        unset($v[$key]);
167
                    }
168
                }
169 10
                $v['default_database'] = isset($v['default_database']) ? (string) $v['default_database'] : 'default';
170 10
                $v['databases'] = [$v['default_database'] => $database];
171
172 10
                return $v;
173 41
            });
174 41
    }
175
176
    /**
177
     * Validate that the default_database exists in the list of databases.
178
     *
179
     * @param NodeDefinition $root_node
180
     */
181 41
    private function validateAvailableDefaultDatabase(NodeDefinition $root_node): void
182
    {
183
        $root_node
184 41
            ->validate()
185
                ->ifTrue(static function ($v): bool {
186
                    return
187 31
                        is_array($v) &&
188 31
                        array_key_exists('default_database', $v) &&
189 31
                        array_key_exists('databases', $v) &&
190 31
                        $v['databases'] &&
191 31
                        !array_key_exists($v['default_database'], $v['databases']);
192 41
                })
193
                ->then(static function (array $v): array {
194 4
                    $databases = implode('", "', array_keys($v['databases']));
195
196 4
                    throw new \InvalidArgumentException(sprintf('Undefined default database "%s". Available "%s" databases.', $v['default_database'], $databases));
197 41
                });
198 41
    }
199
200
    /**
201
     * Add a license option to the databases configuration if it does not exist.
202
     * Allow use a global license for all databases.
203
     *
204
     * @param NodeDefinition $root_node
205
     */
206 41
    private function allowGlobalLicense(NodeDefinition $root_node): void
207
    {
208
        $root_node
209 41
            ->beforeNormalization()
210
            ->ifTrue(static function ($v): bool {
211
                return
212 39
                    is_array($v) &&
213 39
                    array_key_exists('license', $v) &&
214 39
                    array_key_exists('databases', $v) &&
215 39
                    is_array($v['databases']);
216 41
            })
217
            ->then(static function (array $v): array {
218 6
                foreach ($v['databases'] as $name => $database) {
219 4
                    if (!array_key_exists('license', $database)) {
220 4
                        $v['databases'][$name]['license'] = $v['license'];
221
                    }
222
                }
223
224 6
                return $v;
225 41
            });
226 41
    }
227
228
    /**
229
     * Add a locales option to the databases configuration if it does not exist.
230
     * Allow use a global locales for all databases.
231
     *
232
     * @param NodeDefinition $root_node
233
     */
234 41
    private function allowGlobalLocales(NodeDefinition $root_node): void
235
    {
236
        $root_node
237 41
            ->beforeNormalization()
238
            ->ifTrue(static function ($v): bool {
239
                return
240 39
                    is_array($v) &&
241 39
                    array_key_exists('locales', $v) &&
242 39
                    array_key_exists('databases', $v) &&
243 39
                    is_array($v['databases']);
244 41
            })
245
            ->then(static function (array $v): array {
246 2
                foreach ($v['databases'] as $name => $database) {
247 2
                    if (!array_key_exists('locales', $database)) {
248 2
                        $v['databases'][$name]['locales'] = $v['locales'];
249
                    }
250
                }
251
252 2
                return $v;
253 41
            });
254 41
    }
255
256
    /**
257
     * Validate database locales.
258
     *
259
     * @param NodeDefinition $root_node
260
     */
261 41
    private function validateDatabaseLocales(NodeDefinition $root_node): void
262
    {
263
        $root_node
264 41
            ->validate()
265
            ->ifTrue(static function ($v): bool {
266
                return
267 27
                    is_array($v) &&
268 27
                    array_key_exists('databases', $v) &&
269 27
                    is_array($v['databases']);
270 41
            })
271
            ->then(static function (array $v): array {
272 27
                foreach ($v['databases'] as $name => $database) {
273 17
                    if (!array_key_exists('locales', $database) || empty($database['locales'])) {
274 17
                        throw new \InvalidArgumentException(sprintf('The list of locales should not be empty in databases "%s".', $name));
275
                    }
276
                }
277
278 27
                return $v;
279 41
            });
280 41
    }
281
282
    /**
283
     * Normalize url option from license key and edition id.
284
     *
285
     * @param NodeDefinition $database_node
286
     */
287 41
    private function normalizeUrl(NodeDefinition $database_node): void
288
    {
289
        $database_node
290 41
            ->beforeNormalization()
291
            ->ifTrue(static function ($v): bool {
292
                return
293 29
                    is_array($v) &&
294 29
                    !array_key_exists('url', $v) &&
295 29
                    array_key_exists('license', $v) &&
296 29
                    array_key_exists('edition', $v);
297 41
            })
298
            ->then(static function (array $v): array {
299 19
                $v['url'] = sprintf(self::URL, urlencode($v['edition']), urlencode($v['license']));
300
301 19
                return $v;
302 41
            });
303 41
    }
304
305
    /**
306
     * Normalize path option from edition id.
307
     *
308
     * @param NodeDefinition $database_node
309
     */
310 41
    private function normalizePath(NodeDefinition $database_node): void
311
    {
312
        $database_node
313 41
            ->beforeNormalization()
314
            ->ifTrue(static function ($v): bool {
315 29
                return is_array($v) && !array_key_exists('path', $v) && array_key_exists('edition', $v);
316 41
            })
317
            ->then(function (array $v): array {
318 23
                $v['path'] = sprintf(self::PATH, $this->cache_dir, $v['edition']);
319
320 23
                return $v;
321 41
            });
322 41
    }
323
324
    /**
325
     * The url option must be a valid URL.
326
     *
327
     * @param NodeDefinition $url
328
     */
329 41
    private function validateURL(NodeDefinition $url): void
330
    {
331
        $url
332 41
            ->validate()
333
            ->ifTrue(static function ($v): bool {
334 27
                return is_string($v) && !filter_var($v, FILTER_VALIDATE_URL);
335 41
            })
336
            ->then(static function (string $v): array {
337 2
                throw new \InvalidArgumentException(sprintf('URL "%s" must be valid.', $v));
338 41
            });
339 41
    }
340
}
341