Completed
Push — master ( ebba66...366e1c )
by Yann
27:01 queued 22:24
created

Configuration::getConfigTreeBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 18
c 2
b 0
f 0
ccs 12
cts 12
cp 1
rs 9.4285
cc 1
eloc 13
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
            ->end()
87
        ;
88
89 11
        return $node;
90
    }
91
92
    /**
93
     * @return NodeDefinition
94
     */
95 11
    private function getMessagesNode()
96
    {
97 11
        $node = $this->root('messages');
98
99
        $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...
100 11
            ->defaultValue([])
101 11
            ->prototype('array')
102 11
                ->fixXmlConfig('channel')
103 11
                ->children()
104 11
                    ->scalarNode('id')->isRequired()->end()
105 11
                    ->arrayNode('channels')
106 11
                        ->beforeNormalization()
107 11
                            ->ifString()->then($this->stringToArray())
108 11
                        ->end()
109 11
                        ->requiresAtLeastOneElement()
110 11
                        ->prototype('scalar')->end()
111 11
                    ->end()
112 11
                    ->arrayNode('defaults')
113 11
                        ->defaultValue([])
114 11
                        ->prototype('variable')->end()
115 11
                    ->end()
116 11
                    ->arrayNode('options')
117 11
                        ->defaultValue([])
118 11
                        ->useAttributeAsKey('channel')
119 11
                        ->prototype('variable')
120 11
                            ->validate()
121 11
                                ->ifTrue($this->isNotHash())->thenInvalid('Expected a hash for channel options, got %s.')
122 11
                            ->end()
123 11
                        ->end()
124 11
                    ->end()
125 11
                ->end()
126 11
            ->end()
127
        ;
128
129 11
        return $node;
130
    }
131
132
    /**
133
     * @return NodeDefinition
134
     */
135 11
    private function getSwiftmailerChannelNode()
136
    {
137 11
        $node = $this->root('swiftmailer');
138
139
        $node
140 11
            ->canBeEnabled()
141 11
            ->validate()
142
                ->ifTrue(function ($value) {
143 4
                    if (!$value['enabled']) {
144 1
                        return false;
145
                    }
146 3
                    if (isset($value['from_addr']) & !empty($value['from_addr'])) {
147 3
                        return false;
148
                    }
149
150
                    return true;
151 11
                })
152 11
                ->thenInvalid('The child node "from_addr" at path "messenger.channels.swiftmailer" must be configured.')
153 11
            ->end()
154 11
            ->children()
155 11
                ->scalarNode('from_addr')
156 11
                    ->defaultNull()
157 11
                ->end()
158 11
                ->scalarNode('translator_catalog')->defaultValue('notifications')->end()
159 11
            ->end()
160
        ;
161
162 11
        return $node;
163
    }
164
165
    /**
166
     * @return NodeDefinition
167
     */
168 11
    private function getDoctrineChannelNode()
169
    {
170 11
        $node = $this->root('doctrine');
171
172
        $node
173 11
            ->canBeEnabled()
174 11
            ->children()
175 11
                ->scalarNode('manager')->defaultValue('default')->end()
176 11
            ->end()
177
        ;
178
179 11
        return $node;
180
    }
181
182
    /**
183
     * @return NodeDefinition
184
     */
185 11
    private function getContentBuilderNode()
186
    {
187 11
        $node = $this->root('content_builder');
188
189
        $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...
190 11
            ->defaultValue([])
191 11
            ->useAttributeAsKey('name')
192 11
            ->prototype('variable')
193 11
            ->end()
194
        ;
195
196 11
        return $node;
197
    }
198
199
    /**
200
     * @return \Closure
201
     */
202 11
    private function stringToArray()
203
    {
204
        return function ($value) {
205 1
            return [$value];
206 11
        };
207
    }
208
209
    /**
210
     * @return \Closure
211
     */
212
    private function isNotHash()
213
    {
214 11
        return function ($value) {
215
            if (!is_array($value)) {
216
                return true;
217
            }
218
219
            if (array_values($value) === $value) {
220
                return true;
221
            }
222
223
            return false;
224 11
        };
225
    }
226
}
227