Completed
Push — master ( 25de90...802968 )
by Nikola
07:19
created

Configuration::processInjectionsFromYaml()   C

Complexity

Conditions 13
Paths 2

Size

Total Lines 71
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 13

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 71
ccs 34
cts 34
cp 1
rs 5.5687
cc 13
eloc 40
nc 2
nop 1
crap 13

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * This file is part of the  TraitorBundle, an RunOpenCode project.
4
 *
5
 * (c) 2017 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace RunOpenCode\Bundle\Traitor\DependencyInjection;
11
12
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
13
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
14
use Symfony\Component\Config\Definition\ConfigurationInterface;
15
16
/**
17
 * Class Configuration
18
 *
19
 * @package RunOpenCode\Bundle\Traitor\DependencyInjection
20
 */
21
class Configuration implements ConfigurationInterface
22
{
23
    /**
24
     * {@inheritdoc}
25
     */
26 8
    public function getConfigTreeBuilder()
27
    {
28 8
        $treeBuilder = new TreeBuilder();
29
30 8
        $rootNode = $treeBuilder->root('runopencode_traitor');
31
32
        $rootNode
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 children() 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...
33 8
            ->addDefaultsIfNotSet()
34 8
            ->fixXmlConfig('inject')
35 8
            ->beforeNormalization()
36 8
                ->always()
37
                ->then(\Closure::bind(function ($value) {
38
39 8
                    if (isset($value['inject']) && count($value['inject']) > 0 && !isset($value['inject'][0]['trait'])) {
40 1
                        $value['inject'] = $this->processInjectionsFromYaml($value['inject']);
41
                    }
42
43 8
                    return $value;
44 8
                }, $this))
45 8
            ->end()
46 8
            ->children()
47 8
                ->booleanNode('use_common_traits')
48 8
                    ->defaultFalse()
49 8
                    ->info('For sake of productivity, some of the common Symfony and vendor traits, as well as traits from this library, can be automatically added to "inject" definition.')
50 8
                ->end()
51
52 8
                ->append($this->getInjectionsDefinition())
53 8
                ->append($this->getFiltesDefinition())
54 8
                ->append($this->getExclusionsDefinition())
55 8
            ->end();
56
57 8
        return $treeBuilder;
58
    }
59
60
    /**
61
     * Configure injections
62
     *
63
     * @return ArrayNodeDefinition
64
     */
65 8
    protected function getInjectionsDefinition()
66
    {
67 8
        $node = new ArrayNodeDefinition('injects');
68
69
        $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 fixXmlConfig() 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...
70 8
            ->useAttributeAsKey('trait')
71 8
            ->prototype('array')
72 8
                ->fixXmlConfig('call')
73 8
                ->children()
74 8
                    ->arrayNode('calls')
75 8
                        ->prototype('array')
76 8
                        ->fixXmlConfig('argument')
77 8
                            ->children()
78 8
                                ->scalarNode('method')->isRequired()->end()
79 8
                                ->arrayNode('arguments')
80 8
                                    ->prototype('array')
81 8
                                        ->beforeNormalization()
82 8
                                        ->ifString()
83
                                        ->then(function ($value) {
84
                                            return [
85 1
                                                'value' => $value
86
                                            ];
87 8
                                        })
88 8
                                        ->end()
89 8
                                        ->children()
90 8
                                            ->scalarNode('id')->defaultNull()->end()
91 8
                                            ->scalarNode('type')->defaultValue('string')->end()
92 8
                                            ->variableNode('value')->defaultNull()->end()
93 8
                                            ->enumNode('on_invalid')->values([ 'ignore', null, 'exception' ])->defaultValue('exception')->end()
94 8
                                        ->end()
95 8
                                    ->end()
96 8
                                ->end()
97 8
                            ->end()
98 8
                        ->end()
99 8
                    ->end()
100 8
                ->end()
101 8
            ->end();
102
103 8
        return $node;
104
    }
105
106
    /**
107
     * Configure filters
108
     * 
109
     * @return ArrayNodeDefinition
110
     */
111 8
    protected function getFiltesDefinition()
112
    {
113 8
        $node = new ArrayNodeDefinition('filter');
114
115
        $node
116 8
            ->info('Analyse services for injection which matches filter criteria.')
117 8
            ->fixXmlConfig('tag')
118 8
            ->fixXmlConfig('namespace')
119 8
                ->children()
120 8
                    ->arrayNode('tags')
121 8
                        ->prototype('scalar')->end()
122 8
                    ->end()
123 8
                    ->arrayNode('namespaces')
124 8
                        ->prototype('scalar')->end()
125 8
                    ->end()
126 8
                ->end();
127
128 8
        return $node;
129
    }
130
131
    /**
132
     * Configure exclusions
133
     *
134
     * @return ArrayNodeDefinition
135
     */
136 8
    protected function getExclusionsDefinition()
137
    {
138 8
        $node = new ArrayNodeDefinition('exclude');
139
140
        $node
141 8
            ->info('Exclude services from injection which matches filter criteria.')
142 8
            ->fixXmlConfig('service')
143 8
            ->fixXmlConfig('namespace')
144 8
            ->fixXmlConfig('class', 'classes')
145 8
            ->fixXmlConfig('tag')
146 8
                ->children()
147 8
                    ->arrayNode('services')
148 8
                        ->prototype('scalar')->end()
149 8
                    ->end()
150 8
                    ->arrayNode('namespaces')
151 8
                        ->prototype('scalar')->end()
152 8
                    ->end()
153 8
                    ->arrayNode('classes')
154 8
                        ->prototype('scalar')->end()
155 8
                    ->end()
156 8
                    ->arrayNode('tags')
157 8
                        ->prototype('scalar')->end()
158 8
                    ->end()
159 8
                ->end();
160
161 8
        return $node;
162
    }
163
164
    /**
165
     * Process injections loaded from YAML configuration.
166
     *
167
     * @param array $injections
168
     * @return array
169
     */
170 1
    private function processInjectionsFromYaml(array $injections)
171
    {
172 1
        $processed = [];
173
174 1
        foreach ($injections as $trait => $calls) {
175
176 1
            $processed[] = [
177 1
                'trait' => $trait,
178
                'call' => call_user_func(function($calls) {
179
180 1
                    $processed = [];
181
182 1
                    foreach ($calls as $call) {
183 1
                        $processed[] = [
184 1
                            'method' => isset($call['method']) ? $call['method'] : $call[0],
185
                            'argument' => call_user_func(function($arguments) {
186
187 1
                                $resolveArguments = function($value) use (&$resolveArguments) {
188 1
                                    if (is_array($value)) {
189 1
                                        $value = array_map($resolveArguments, $value);
190 1
                                    } elseif (is_string($value) && 0 === strpos($value, '@=')) {
191
192
                                        return [
193 1
                                            'type' => 'expression',
194 1
                                            'value' => substr($value, 2),
195
                                        ];
196
197 1
                                    } elseif (is_string($value) && 0 === strpos($value, '@')) {
198
199 1
                                        if (0 === strpos($value, '@@')) {
200
201
                                            return [
202 1
                                                'value' => substr($value, 1)
203
                                            ];
204 1
                                        } elseif (0 === strpos($value, '@?')) {
205 1
                                            $value = substr($value, 2);
206 1
                                            $invalidBehavior = 'ignore';
207
                                        } else {
208 1
                                            $value = substr($value, 1);
209 1
                                            $invalidBehavior = 'exception';
210
                                        }
211
212 1
                                        if ('=' === substr($value, -1)) {
213
                                            $value = substr($value, 0, -1);
214
                                        }
215
216
217
                                        return [
218 1
                                            'type' => 'service',
219 1
                                            'id' => $value,
220 1
                                            'on_invalid' => $invalidBehavior,
221
                                        ];
222
                                    }
223
224 1
                                    return $value;
225 1
                                };
226
227 1
                                return $resolveArguments($arguments);
228
229 1
                            }, isset($call['arguments']) ? $call['arguments'] : $call[1])
230
                        ];
231
                    }
232
233 1
                    return $processed;
234
235 1
                }, $calls)
236
            ];
237
        }
238
239 1
        return $processed;
240
    }
241
}
242