Completed
Pull Request — master (#3)
by Jacob
02:20
created

Configuration::getRestNode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 1
eloc 14
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->getSearchClientsNode())
29
            ->end()
30
        ;
31
        return $treeBuilder;
32
    }
33
34
    /**
35
     * Formats the root REST endpoint.
36
     *
37
     * @param   string  $endpoint
38
     * @return  string
39
     */
40
    private function formatRestEndpoint($endpoint)
41
    {
42
        return sprintf('%s', trim($endpoint, '/'));
43
    }
44
45
    /**
46
     * Gets the api adapter configuration node.
47
     *
48
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
49
     */
50
    private function getAdapterNode()
51
    {
52
        $treeBuilder = new TreeBuilder();
53
        $node = $treeBuilder->root('adapter');
54
        return $node
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method addDefaultsIfNotSet() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
55
            ->isRequired()
56
            ->addDefaultsIfNotSet()
57
            ->children()
58
                ->scalarNode('type')->end()
59
                ->scalarNode('service')->end()
60
            ->end()
61
            ->validate()
62
                ->always(function($v) {
63
                    $this->validateAdapter($v);
64
                    return $v;
65
                })
66
            ->end()
67
        ;
68
    }
69
70
    /**
71
     * Gets the metadata configuration node.
72
     *
73
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
74
     */
75
    private function getMetadataNode()
76
    {
77
        $treeBuilder = new TreeBuilder();
78
        $node = $treeBuilder->root('metadata');
79
        return $node
80
            ->addDefaultsIfNotSet()
81
            ->children()
82
                ->arrayNode('drivers')
83
                    ->defaultValue(['default' => ['type' => 'yml']])
84
                    ->useAttributeAsKey('name')
85
                    ->prototype('array')
86
                        ->performNoDeepMerging()
87
                        ->children()
88
89
                            ->enumNode('type')->defaultValue('file')
90
                            ->values(['yml', null])
91
                            ->end()
92
                            ->scalarNode('service')->cannotBeEmpty()->end()
93
94
                            ->arrayNode('parameters')
95
                                ->prototype('variable')->end()
96
                            ->end()
97
98
                        ->end()
99
                    ->end()
100
                    ->validate()
101
                        ->always(function($v) {
102
                            $this->validateMetadataDrivers($v);
103
                            return $v;
104
                        })
105
                    ->end()
106
                ->end()
107
108
                ->arrayNode('cache')
109
                    ->canBeDisabled()
110
                    ->children()
111
112
                        ->enumNode('type')->defaultValue('file')
113
                            ->values(['file', 'binary_file', 'redis', null])
114
                        ->end()
115
                        ->scalarNode('service')->cannotBeEmpty()->end()
116
117
                        ->arrayNode('parameters')
118
                            ->prototype('variable')->end()
119
                        ->end()
120
121
                    ->end()
122
123
                    ->validate()
124
                        ->always(function($v) {
125
                            $this->validateMetadataCache($v);
126
                            return $v;
127
                        })
128
                    ->end()
129
                ->end()
130
            ->end()
131
        ;
132
    }
133
134
    /**
135
     * Gets the persisters configuration node.
136
     *
137
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
138
     */
139 View Code Duplication
    private function getPersistersNode()
1 ignored issue
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...
140
    {
141
        $treeBuilder = new TreeBuilder();
142
        $node = $treeBuilder->root('persisters');
143
        return $node
1 ignored issue
show
Bug introduced by
The method useAttributeAsKey() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. Did you maybe mean attribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
144
            ->isRequired()
145
            ->useAttributeAsKey('name')
146
            ->prototype('array')
147
                ->children()
148
149
                    ->scalarNode('type')->cannotBeEmpty()->end()
150
                    ->scalarNode('service')->cannotBeEmpty()->end()
151
152
                    ->arrayNode('parameters')
153
                        ->prototype('variable')->end()
154
                    ->end()
155
                ->end()
156
            ->end()
157
            ->validate()
158
                ->always(function($v) {
159
                    $this->validatePersisters($v);
160
                    return $v;
161
                })
162
            ->end()
163
        ;
164
    }
165
166
    /**
167
     * Gets the rest configuration node.
168
     *
169
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
170
     */
171
    private function getRestNode()
172
    {
173
        $treeBuilder = new TreeBuilder();
174
        $node = $treeBuilder->root('rest');
175
        return $node
176
            ->addDefaultsIfNotSet()
177
            ->children()
178
                ->scalarNode('root_endpoint')->isRequired()->cannotBeEmpty()->defaultValue('modlr/api')
179
                    ->validate()
180
                        ->always(function($v) {
181
                            $v = $this->formatRestEndpoint($v);
182
                            return $v;
183
                        })
184
                    ->end()
185
                ->end()
186
            ->end()
187
188
        ;
189
    }
190
191
    /**
192
     * Gets the search clients configuration node.
193
     *
194
     * @return  \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition
195
     */
196 View Code Duplication
    private function getSearchClientsNode()
1 ignored issue
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...
197
    {
198
        $treeBuilder = new TreeBuilder();
199
        $node = $treeBuilder->root('search_clients');
200
        return $node
1 ignored issue
show
Bug introduced by
The method useAttributeAsKey() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. Did you maybe mean attribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
201
            ->isRequired()
202
            ->useAttributeAsKey('name')
203
            ->prototype('array')
204
                ->children()
205
206
                    ->scalarNode('type')->cannotBeEmpty()->end()
207
                    ->scalarNode('service')->cannotBeEmpty()->end()
208
209
                    ->arrayNode('parameters')
210
                        ->prototype('variable')->end()
211
                    ->end()
212
                ->end()
213
            ->end()
214
            ->validate()
215
                ->always(function($v) {
216
                    $this->validateSearchClients($v);
217
                    return $v;
218
                })
219
            ->end()
220
        ;
221
    }
222
223
    /**
224
     * Validates the api adapter config.
225
     *
226
     * @param   array   $adapter
227
     * @return  self
228
     * @throws  InvalidConfigurationException
229
     */
230
    private function validateAdapter(array $adapter)
231
    {
232
        if (!isset($adapter['type']) && !isset($adapter['service'])) {
233
            throw new InvalidConfigurationException('Either the adapter "type" or "service" must be set for "as3_modlr.adapter"');
234
        }
235
        if (isset($adapter['type']) && isset($adapter['service'])) {
236
            throw new InvalidConfigurationException('You cannot set both the adapter "type" and "service" for "as3_modlr.adapter" - please choose one.');
237
        }
238
239
        if (isset($adapter['type'])) {
240
            if ('jsonapiorg' === $adapter['type']) {
241
                if (false === class_exists('As3\Modlr\Api\JsonApiOrg\Adapter')) {
242
                    throw new InvalidConfigurationException('The jsonapi.org adapter class was not found for "as3_modlr.adapter.type" - was the library installed?');
243
                }
244
            } else {
245
                throw new InvalidConfigurationException('An unrecognized adapter type was set for "as3_modlr.adapter.type"');
246
            }
247
        }
248
        return $this;
249
    }
250
251
    /**
252
     * Validates the metadata cache config.
253
     *
254
     * @param   array   $config
255
     * @return  self
256
     * @throws  InvalidConfigurationException
257
     */
258
    private function validateMetadataCache(array $config)
259
    {
260
        if (false === $config['enabled']) {
261
            return $this;
262
        }
263
264 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...
265
            throw new InvalidConfigurationException('Either the cache "type" or "service" must be set for "as3_modlr.metadata.cache"');
266
        }
267
        if (isset($config['type']) && isset($config['service'])) {
268
            throw new InvalidConfigurationException('You cannot set both the cache "type" and "service" for "as3_modlr.metadata.cache" - please choose one.');
269
        }
270
271
        if (isset($config['type']) && 'redis' === $config['type']) {
272
            if (!isset($config['parameters']['handler'])) {
273
                throw new InvalidConfigurationException('A Redis handler service name must be defined for "as3_modlr.metadata.cache.parameters.handler"');
274
            }
275
        }
276
        return $this;
277
    }
278
279
    /**
280
     * Validates the metadata drivers config.
281
     *
282
     * @param   array   $drivers
283
     * @return  self
284
     * @throws  InvalidConfigurationException
285
     */
286
    private function validateMetadataDrivers(array $drivers)
287
    {
288
        foreach ($drivers as $name => $config) {
289
290 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...
291
                throw new InvalidConfigurationException(sprintf('Either the metadata driver "type" or "service" must be set for "as3_modlr.metadata.drivers.%s"', $name));
292
            }
293 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...
294
                throw new InvalidConfigurationException(sprintf('You cannot set both the metadata driver "type" and "service" for "as3_modlr.metadata.drivers.%s" - please choose one.', $name));
295
            }
296
        }
297
        return $this;
298
    }
299
300
    /**
301
     * Validates the persisters config.
302
     *
303
     * @param   array   $persisters
304
     * @return  self
305
     * @throws  InvalidConfigurationException
306
     */
307
    private function validatePersisters(array $persisters)
308
    {
309
        foreach ($persisters as $name => $config) {
310 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...
311
                throw new InvalidConfigurationException(sprintf('Either the persister "type" or "service" must be set for "as3_modlr.persisters.%s"', $name));
312
            }
313 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...
314
                throw new InvalidConfigurationException(sprintf('You cannot set both the search client "type" and "service" for "as3_modlr.persisters.%s" - please choose one.', $name));
315
            }
316
317
            if (isset($config['type'])) {
318
                if ('mongodb' === $config['type']) {
319 View Code Duplication
                    if (false === class_exists(Utility::getLibraryClass('Persister\MongoDb\Persister'))) {
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...
320
                        throw new InvalidConfigurationException(sprintf('The MongoDB persister library class was not found for "as3_modlr.persisters.%s.type" - was the library installed?', $name));
321
                    }
322
                    if (!isset($config['parameters']['host'])) {
323
                        throw new InvalidConfigurationException(sprintf('The MongoDB persister requires a value for "as3_modlr.persisters.%s.parameters.host"', $name));
324
                    }
325
                } else {
326
                    throw new InvalidConfigurationException(sprintf('An unrecognized persister type was set for "as3_modlr.persisters.%s.type"', $name));
327
                }
328
            }
329
        }
330
    }
331
332
    /**
333
     * Validates the search clients config.
334
     *
335
     * @param   array   $clients
336
     * @return  self
337
     * @throws  InvalidConfigurationException
338
     */
339
    private function validateSearchClients(array $clients)
340
    {
341
        foreach ($clients as $name => $config) {
342 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...
343
                throw new InvalidConfigurationException(sprintf('Either the search client "type" or "service" must be set for "as3_modlr.search_clients.%s"', $name));
344
            }
345 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...
346
                throw new InvalidConfigurationException(sprintf('You cannot set both the search client "type" and "service" for "as3_modlr.search_clients.%s" - please choose one.', $name));
347
            }
348
349
            if (isset($config['type'])) {
350
                if ('elastic' === $config['type']) {
351 View Code Duplication
                    if (false === class_exists(Utility::getLibraryClass('Search\Elastic\Client'))) {
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...
352
                        throw new InvalidConfigurationException(sprintf('The Elastic persister library class was not found for "as3_modlr.search_clients.%s.type" - was the library installed?', $name));
353
                    }
354
                } else {
355
                    throw new InvalidConfigurationException(sprintf('An unrecognized search type was set for "as3_modlr.search_clients.%s.type"', $name));
356
                }
357
            }
358
        }
359
    }
360
}
361