Completed
Push — master ( 393ba4...9cdca5 )
by Yann
9s
created

Configuration::getSwiftmailerChannelNode()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

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