Completed
Push — master ( 366e1c...39d38f )
by Yann
06:12
created

Configuration::getMobileChannelNode()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 36
rs 8.8571
ccs 11
cts 11
cp 1
cc 1
eloc 31
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Yokai\MessengerBundle\DependencyInjection;
4
5
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
6
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
7
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
8
use Symfony\Component\Config\Definition\ConfigurationInterface;
9
10
/**
11
 * @author Yann Eugoné <[email protected]>
12
 */
13
class Configuration implements ConfigurationInterface
14
{
15
    /**
16
     * @var string
17
     */
18
    private $name;
19
20
    /**
21
     * @var TreeBuilder
22
     */
23
    private $builder;
24
25
    /**
26
     * @param string $name
27
     */
28 11
    public function __construct($name)
29
    {
30 11
        $this->name = $name;
31 11
        $this->builder = new TreeBuilder();
32 11
    }
33
34
    /**
35
     * @inheritdoc
36
     */
37 11
    public function getConfigTreeBuilder()
38
    {
39 11
        $builder = $this->getBuilder();
40 11
        $root = $builder->root($this->name);
41
42
        $root
43 11
            ->addDefaultsIfNotSet()
44 11
            ->fixXmlConfig('message')
45 11
            ->children()
46 11
                ->scalarNode('logging_channel')->defaultValue('app')->end()
47 11
                ->append($this->getContentBuilderNode())
48 11
                ->append($this->getChannelsNode())
49 11
                ->append($this->getMessagesNode())
50 11
            ->end()
51
        ;
52
53 11
        return $builder;
54
    }
55
56
    /**
57
     * @return TreeBuilder
58
     */
59 11
    private function getBuilder()
60
    {
61 11
        return clone $this->builder;
62
    }
63
64
    /**
65
     * @param string $name
66
     *
67
     * @return ArrayNodeDefinition
68
     */
69 11
    private function root($name)
70
    {
71 11
        return $this->getBuilder()->root($name);
72
    }
73
74
    /**
75
     * @return NodeDefinition
76
     */
77 11
    private function getChannelsNode()
78
    {
79 11
        $node = $this->root('channels');
80
81
        $node
82 11
            ->addDefaultsIfNotSet()
83 11
            ->children()
84 11
                ->append($this->getSwiftmailerChannelNode())
85 11
                ->append($this->getDoctrineChannelNode())
86 11
                ->append($this->getMobileChannelNode())
87
            ->end()
88
        ;
89 11
90
        return $node;
91
    }
92
93
    /**
94
     * @return NodeDefinition
95 11
     */
96
    private function getMessagesNode()
97 11
    {
98
        $node = $this->root('messages');
99
100 11
        $node
0 ignored issues
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 prototype() 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...
101 11
            ->defaultValue([])
102 11
            ->prototype('array')
103 11
                ->fixXmlConfig('channel')
104 11
                ->children()
105 11
                    ->scalarNode('id')->isRequired()->end()
106 11
                    ->arrayNode('channels')
107 11
                        ->beforeNormalization()
108 11
                            ->ifString()->then($this->stringToArray())
109 11
                        ->end()
110 11
                        ->requiresAtLeastOneElement()
111 11
                        ->prototype('scalar')->end()
112 11
                    ->end()
113 11
                    ->arrayNode('defaults')
114 11
                        ->defaultValue([])
115 11
                        ->prototype('variable')->end()
116 11
                    ->end()
117 11
                    ->arrayNode('options')
118 11
                        ->defaultValue([])
119 11
                        ->useAttributeAsKey('channel')
120 11
                        ->prototype('variable')
121 11
                            ->validate()
122 11
                                ->ifTrue($this->isNotHash())
123 11
                                    ->thenInvalid('Expected a hash for channel options, got %s.')
124 11
                            ->end()
125 11
                        ->end()
126 11
                    ->end()
127
                ->end()
128
            ->end()
129 11
        ;
130
131
        return $node;
132
    }
133
134
    /**
135 11
     * @return NodeDefinition
136
     */
137 11
    private function getSwiftmailerChannelNode()
138
    {
139
        $node = $this->root('swiftmailer');
140 11
141 11
        $node
142
            ->canBeEnabled()
143 4
            ->validate()
144 1
                ->ifTrue($this->nodeRequiredIfEnabled('from_addr'))
145
                    ->thenInvalid($this->nodeMustBeConfigured('from_addr', 'channels.swiftmailer'))
146 3
            ->end()
147 3
            ->children()
148
                ->scalarNode('from_addr')
149
                    ->defaultNull()
150
                ->end()
151 11
                ->scalarNode('translator_catalog')->defaultValue('notifications')->end()
152 11
            ->end()
153 11
        ;
154 11
155 11
        return $node;
156 11
    }
157 11
158 11
    /**
159 11
     * @return NodeDefinition
160
     */
161
    private function getDoctrineChannelNode()
162 11
    {
163
        $node = $this->root('doctrine');
164
165
        $node
166
            ->canBeEnabled()
167
            ->children()
168 11
                ->scalarNode('manager')->defaultValue('default')->end()
169
            ->end()
170 11
        ;
171
172
        return $node;
173 11
    }
174 11
175 11
    /**
176 11
     * @return NodeDefinition
177
     */
178
    private function getMobileChannelNode()
179 11
    {
180
        $node = $this->root('mobile');
181
182
        $node
183
            ->canBeEnabled()
184
            ->children()
185 11
                ->scalarNode('environment')->defaultValue('dev')->end()
186
                ->arrayNode('apns')
187 11
                    ->canBeDisabled()
188
                    ->validate()
189
                        ->ifTrue($this->nodeRequiredIfEnabled('certificate'))
190 11
                            ->thenInvalid($this->nodeMustBeConfigured('certificate', 'channels.mobile.apns'))
191 11
                        ->ifTrue($this->nodeRequiredIfEnabled('certificate'))
192 11
                            ->thenInvalid($this->nodeMustBeConfigured('pass_phrase', 'channels.mobile.apns'))
193 11
                    ->end()
194
                    ->children()
195
                        ->scalarNode('certificate')->defaultNull()->end()
196 11
                        ->scalarNode('pass_phrase')->defaultNull()->end()
197
                    ->end()
198
                ->end()
199
                ->arrayNode('gcm')
200
                    ->canBeDisabled()
201
                    ->validate()
202 11
                        ->ifTrue($this->nodeRequiredIfEnabled('api_key'))
203
                            ->thenInvalid($this->nodeMustBeConfigured('api_key', 'channels.mobile.gcm'))
204
                    ->end()
205 1
                    ->children()
206 11
                        ->scalarNode('api_key')->defaultNull()->end()
207
                    ->end()
208
                ->end()
209
            ->end()
210
        ;
211
212
        return $node;
213
    }
214 11
215
    /**
216
     * @return NodeDefinition
217
     */
218
    private function getContentBuilderNode()
219
    {
220
        $node = $this->root('content_builder');
221
222
        $node
0 ignored issues
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...
223
            ->defaultValue([])
224 11
            ->useAttributeAsKey('name')
225
            ->prototype('variable')
226
            ->end()
227
        ;
228
229
        return $node;
230
    }
231
232
    /**
233
     * @return \Closure
234
     */
235
    private function stringToArray()
236
    {
237
        return function ($value) {
238
            return [$value];
239
        };
240
    }
241
242
    /**
243
     * @return \Closure
244
     */
245
    private function isNotHash()
246
    {
247
        return function ($value) {
248
            if (!is_array($value)) {
249
                return true;
250
            }
251
252
            if (array_values($value) === $value) {
253
                return true;
254
            }
255
256
            return false;
257
        };
258
    }
259
260
    /**
261
     * @param string $node
262
     *
263
     * @return \Closure
264
     */
265
    private function nodeRequiredIfEnabled($node)
266
    {
267
        return function ($value) use ($node) {
268
            if (!$value['enabled']) {
269
                return false;
270
            }
271
272
            if (isset($value[$node]) && !empty($value[$node])) {
273
                return false;
274
            }
275
276
            return true;
277
        };
278
    }
279
280
    /**
281
     * @param string $node
282
     * @param string $path
283
     *
284
     * @return string
285
     */
286
    private function nodeMustBeConfigured($node, $path)
287
    {
288
        return sprintf(
289
            'The child node "%s" at path "%s.%s" must be configured.',
290
            $node,
291
            $this->name,
292
            $path
293
        );
294
    }
295
}
296