Completed
Pull Request — master (#25)
by Joshua
04:40
created

Configuration::getSearchClientsNode()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 22

Duplication

Lines 28
Ratio 100 %

Importance

Changes 0
Metric Value
dl 28
loc 28
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 22
nc 1
nop 0
1
<?php
2
3
namespace As3\Bundle\ModlrBundle\DependencyInjection;
4
5
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
6
use Symfony\Component\Config\Definition\ConfigurationInterface;
7
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
8
9
/**
10
 * Validates and merges configuration for Modlr.
11
 *
12
 * @author  Jacob Bare <[email protected]>
13
 */
14
class Configuration implements ConfigurationInterface
15
{
16
    /**
17
     * {@inheritDoc}
18
     */
19
    public function getConfigTreeBuilder()
20
    {
21
        $treeBuilder = new TreeBuilder();
22
        $treeBuilder->root('as3_modlr')
23
            ->children()
24
                ->append($this->getAdapterNode())
25
                ->append($this->getMetadataNode())
26
                ->append($this->getPersistersNode())
27
                ->append($this->getRestNode())
28
                ->append($this->getSchemaIndicesNode())
29
                ->append($this->getSearchClientsNode())
30
            ->end()
31
        ;
32
        return $treeBuilder;
33
    }
34
35
    /**
36
     * Creates a root config node with the provided key.
37
     *
38
     * @param   string $key
39
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
40
     */
41
    private function createRootNode($key)
42
    {
43
        $treeBuilder = new TreeBuilder();
44
        return $treeBuilder->root($key);
45
    }
46
47
    /**
48
     * Formats the root REST endpoint.
49
     *
50
     * @param   string  $endpoint
51
     * @return  string
52
     */
53
    private function formatRestEndpoint($endpoint)
54
    {
55
        return sprintf('%s', trim($endpoint, '/'));
56
    }
57
58
    /**
59
     * Gets the api adapter configuration node.
60
     *
61
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
62
     */
63 View Code Duplication
    private function getAdapterNode()
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...
64
    {
65
        $node = $this->createRootNode('adapter');
66
        return $node
67
            ->isRequired()
68
            ->addDefaultsIfNotSet()
69
            ->children()
70
                ->enumNode('type')
71
                    ->values(['jsonapiorg', null])
72
                ->end()
73
                ->scalarNode('service')->cannotBeEmpty()->end()
74
            ->end()
75
            ->validate()
76
                ->always(function($v) {
77
                    $this->validateAdapter($v);
78
                    return $v;
79
                })
80
            ->end()
81
        ;
82
    }
83
84
    /**
85
     * Gets the metadata configuration node.
86
     *
87
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
88
     */
89
    private function getMetadataNode()
90
    {
91
        $node = $this->createRootNode('metadata');
92
        return $node
93
            ->addDefaultsIfNotSet()
94
            ->children()
95
                ->arrayNode('drivers')
96
                    ->defaultValue(['default' => ['type' => 'yml']])
97
                    ->useAttributeAsKey('name')
98
                    ->prototype('array')
99
                        ->performNoDeepMerging()
100
                        ->children()
101
102
                            ->enumNode('type')->defaultValue('yml')
103
                                ->values(['yml', null])
104
                            ->end()
105
                            ->scalarNode('service')->cannotBeEmpty()->end()
106
107
                            ->arrayNode('parameters')
108
                                ->performNoDeepMerging()
109
                                ->prototype('variable')->end()
110
                            ->end()
111
112
                        ->end()
113
                    ->end()
114
                    ->validate()
115
                        ->always(function($v) {
116
                            $this->validateMetadataDrivers($v);
117
                            return $v;
118
                        })
119
                    ->end()
120
                ->end()
121
122
                ->arrayNode('cache')
123
                    ->canBeDisabled()
124
                    ->children()
125
126
                        ->enumNode('type')->defaultValue('file')
127
                            ->values(['file', 'binary_file', 'redis', null])
128
                        ->end()
129
                        ->scalarNode('service')->cannotBeEmpty()->end()
130
131
                        ->arrayNode('parameters')
132
                            ->performNoDeepMerging()
133
                            ->prototype('variable')->end()
134
                        ->end()
135
136
                    ->end()
137
138
                    ->validate()
139
                        ->always(function($v) {
140
                            $this->validateMetadataCache($v);
141
                            return $v;
142
                        })
143
                    ->end()
144
                ->end()
145
            ->end()
146
        ;
147
    }
148
149
    /**
150
     * Gets the persisters configuration node.
151
     *
152
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
153
     */
154 View Code Duplication
    private function getPersistersNode()
155
    {
156
        $node = $this->createRootNode('persisters');
157
        return $node
158
            ->isRequired()
159
            ->useAttributeAsKey('name')
160
            ->prototype('array')
161
                ->children()
162
163
                    ->enumNode('type')
164
                        ->values(['mongodb', null])
165
                    ->end()
166
                    ->scalarNode('service')->cannotBeEmpty()->end()
167
168
                    ->arrayNode('parameters')
169
                        ->performNoDeepMerging()
170
                        ->prototype('variable')->end()
171
                    ->end()
172
                ->end()
173
            ->end()
174
            ->validate()
175
                ->always(function($v) {
176
                    $this->validatePersisters($v);
177
                    return $v;
178
                })
179
            ->end()
180
        ;
181
    }
182
183
    /**
184
     * Gets the rest configuration node.
185
     *
186
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
187
     */
188 View Code Duplication
    private function getRestNode()
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...
189
    {
190
        $node = $this->createRootNode('rest');
191
        return $node
192
            ->addDefaultsIfNotSet()
193
            ->children()
194
                ->scalarNode('root_endpoint')->isRequired()->cannotBeEmpty()->defaultValue('modlr/api')
195
                    ->validate()
196
                        ->always(function($v) {
197
                            $v = $this->formatRestEndpoint($v);
198
                            return $v;
199
                        })
200
                    ->end()
201
                ->end()
202
                ->booleanNode('debug')->defaultValue(false)->end()
203
            ->end()
204
205
        ;
206
    }
207
208
    /**
209
     * Gets the schema indices configuration node.
210
     *
211
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
212
     */
213
    private function getSchemaIndicesNode()
214
    {
215
        $node = $this->createRootNode('schema_indices');
216
        return $node
217
            ->prototype('array')
218
                ->treatNullLike([])
219
                ->children()
220
                    ->scalarNode('model_type')->isRequired()->cannotBeEmpty()->end()
221
                    ->scalarNode('name')->end()
222
                    ->arrayNode('keys')
223
                        ->performNoDeepMerging()
224
                        ->cannotBeEmpty()
225
                        ->prototype('variable')->end()
226
                    ->end()
227
                    ->arrayNode('options')
228
                        ->performNoDeepMerging()
229
                        ->prototype('variable')->end()
230
                    ->end()
231
                ->end()
232
            ->end()
233
            ->validate()
234
                ->always(function($v) {
235
                    $v = $this->validateSchemaIndices($v);
236
                    return $v;
237
                })
238
            ->end()
239
        ;
240
    }
241
242
    /**
243
     * Gets the search clients configuration node.
244
     *
245
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
246
     */
247 View Code Duplication
    private function getSearchClientsNode()
248
    {
249
        $node = $this->createRootNode('search_clients');
250
        return $node
251
            ->isRequired()
252
            ->useAttributeAsKey('name')
253
            ->prototype('array')
254
                ->children()
255
256
                    ->enumNode('type')
257
                        ->values(['elastic', null])
258
                    ->end()
259
                    ->scalarNode('service')->cannotBeEmpty()->end()
260
261
                    ->arrayNode('parameters')
262
                        ->performNoDeepMerging()
263
                        ->prototype('variable')->end()
264
                    ->end()
265
                ->end()
266
            ->end()
267
            ->validate()
268
                ->always(function($v) {
269
                    $this->validateSearchClients($v);
270
                    return $v;
271
                })
272
            ->end()
273
        ;
274
    }
275
276
    /**
277
     * Validates the api adapter config.
278
     *
279
     * @param   array   $adapter
280
     * @return  self
281
     * @throws  InvalidConfigurationException
282
     */
283
    private function validateAdapter(array $adapter)
284
    {
285
        $this->validateTypeAndService($adapter, 'as3_modlr.adapter');
286
        switch ($adapter['type']) {
287
            case 'jsonapiorg':
288
                $this->validateLibClassExists('Api\JsonApiOrg\Adapter', 'as3_modlr.adapter.type');
289
                break;
290
            default:
291
                break;
292
        }
293
        return $this;
294
    }
295
296
    /**
297
     * Validates that a library class name exists.
298
     *
299
     * @param   string  $subClass
300
     * @param   string  $path
301
     * @throws  InvalidConfigurationException
302
     */
303
    private function validateLibClassExists($subClass, $path)
304
    {
305
        $class = Utility::getLibraryClass($subClass);
306
        if (false === class_exists($class)) {
307
            throw new InvalidConfigurationException(sprintf('The library class "%s" was not found for "%s" - was the library installed?', $class, $path));
308
        }
309
    }
310
311
    /**
312
     * Validates the metadata cache config.
313
     *
314
     * @param   array   $config
315
     * @return  self
316
     * @throws  InvalidConfigurationException
317
     */
318
    private function validateMetadataCache(array $config)
319
    {
320
        if (false === $config['enabled']) {
321
            return $this;
322
        }
323
324
        $this->validateTypeAndService($config, 'as3_modlr.metadata.cache');
325
        switch ($config['type']) {
326
            case 'redis':
327
                $this->validateMetadataCacheRedis($config);
328
                break;
329
            default:
330
                break;
331
        }
332
        return $this;
333
    }
334
335
    /**
336
     * Validates the Redis metadata cache config.
337
     *
338
     * @param   array   $config
339
     * @throws  InvalidConfigurationException
340
     */
341
    private function validateMetadataCacheRedis(array $config)
342
    {
343
        if (!isset($config['parameters']['handler'])) {
344
            throw new InvalidConfigurationException('A Redis handler service name must be defined for "as3_modlr.metadata.cache.parameters.handler"');
345
        }
346
    }
347
348
    /**
349
     * Validates the metadata drivers config.
350
     *
351
     * @param   array   $drivers
352
     * @return  self
353
     * @throws  InvalidConfigurationException
354
     */
355
    private function validateMetadataDrivers(array $drivers)
356
    {
357
        foreach ($drivers as $name => $config) {
358
            $this->validateTypeAndService($config, sprintf('as3_modlr.metadata.drivers.%s', $name));
359
        }
360
        return $this;
361
    }
362
363
    /**
364
     * Validates the MongoDb persister config.
365
     *
366
     * @param   array   $config
367
     * @throws  InvalidConfigurationException
368
     */
369
    private function validatePersisterMongoDb(array $config)
370
    {
371
        if (!isset($config['parameters']['host'])) {
372
            throw new InvalidConfigurationException(sprintf('The MongoDB persister requires a value for "as3_modlr.persisters.%s.parameters.host"', $name));
373
        }
374
    }
375
376
    /**
377
     * Validates the persisters config.
378
     *
379
     * @param   array   $persisters
380
     * @return  self
381
     * @throws  InvalidConfigurationException
382
     */
383
    private function validatePersisters(array $persisters)
384
    {
385
        foreach ($persisters as $name => $config) {
386
            $this->validateTypeAndService($config, sprintf('as3_modlr.persisters.%s', $name));
387
            switch ($config['type']) {
388
                case 'mongodb':
389
                    $this->validateLibClassExists('Persister\MongoDb\Persister', sprintf('as3_modlr.persisters.%s.type', $name));
390
                    $this->validatePersisterMongoDb($config);
391
                    break;
392
                default:
393
                    break;
394
            }
395
        }
396
    }
397
398
    /**
399
     * Validates the schema indices config.
400
     *
401
     * @param   array   $indices
402
     * @return  self
403
     * @throws  InvalidConfigurationException
404
     */
405
    private function validateSchemaIndices(array $indices)
406
    {
407
        foreach ($indices as $k => $index) {
408
            // Set default name, if not present.
409
            if (!isset($index['name'])) {
410
                $name = 'modlr_';
411
                foreach ($index['keys'] as $key => $value) {
412
                    $name .= sprintf('_%s', $key);
413
                    if (is_numeric($value)) {
414
                        $name .= sprintf('_%s', $value);
415
                    }
416
                }
417
                $indices[$k]['name'] = $name;
418
            }
419
            if (empty($index['keys'])) {
420
                throw new InvalidConfigurationException(sprintf('At least one key must be specified to define an index at "as3_modlr.schema_indices.%s.keys"!', $k));
421
            }
422
        }
423
        return $indices;
424
    }
425
426
    /**
427
     * Validates the search clients config.
428
     *
429
     * @param   array   $clients
430
     * @return  self
431
     * @throws  InvalidConfigurationException
432
     */
433
    private function validateSearchClients(array $clients)
434
    {
435
        foreach ($clients as $name => $config) {
436
            $this->validateTypeAndService($config, sprintf('as3_modlr.search_clients.%s', $name));
437
            switch ($config['type']) {
438
                case 'elastic':
439
                    $this->validateLibClassExists('Search\Elastic\Client', sprintf('as3_modlr.search_clients.%s.type', $name));
440
                    break;
441
                default:
442
                    break;
443
            }
444
        }
445
    }
446
447
    /**
448
     * Validates a configuration that uses type and service as options.
449
     *
450
     * @param   array   $config
451
     * @param   string  $path
452
     * @throws  InvalidArgumentException
453
     */
454
    private function validateTypeAndService(array $config, $path)
455
    {
456 View Code Duplication
        if (!isset($config['type']) && !isset($config['service'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
457
            throw new InvalidConfigurationException(sprintf('You must set one of "type" or "service" for "%s"', $path));
458
        }
459 View Code Duplication
        if (isset($config['type']) && isset($config['service'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
460
            throw new InvalidConfigurationException(sprintf('You cannot set both "type" and "service" for "%s" - please choose one.', $path));
461
        }
462
    }
463
}
464