Completed
Push — master ( d10f54...00ced4 )
by
unknown
05:32
created

XmlResolver   C

Complexity

Total Complexity 46

Size/Duplication

Total Lines 247
Duplicated Lines 5.67 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 91.67%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 17
dl 14
loc 247
rs 6.16
c 0
b 0
f 0
ccs 77
cts 84
cp 0.9167

8 Methods

Rating   Name   Duplication   Size   Complexity  
C resolve() 0 62 14
A resolveConstructor() 7 7 2
A resolveProperty() 0 5 1
A resolveMethod() 7 7 2
A resolveArguments() 0 8 2
C resolveDependency() 0 39 16
B resolveCollection() 0 18 5
A xml2array() 0 7 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like XmlResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlResolver, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style introduced by
End of line character is invalid; expected "\n" but found "\r\n"
Loading history...
2
/**
3
 * Created by Ruslan Molodyko.
4
 * Date: 12.09.2016
5
 * Time: 6:33
6
 */
7
namespace samsonframework\container\definition\resolver\xml;
8
9
use samsonframework\container\definition\builder\DefinitionBuilder;
10
use samsonframework\container\definition\ClassDefinition;
11
use samsonframework\container\definition\exception\ClassDefinitionAlreadyExistsException;
12
use samsonframework\container\definition\exception\MethodDefinitionAlreadyExistsException;
13
use samsonframework\container\definition\exception\ParameterDefinitionAlreadyExistsException;
14
use samsonframework\container\definition\exception\PropertyDefinitionAlreadyExistsException;
15
use samsonframework\container\definition\MethodDefinition;
16
use samsonframework\container\definition\reference\BoolReference;
17
use samsonframework\container\definition\reference\ClassReference;
18
use samsonframework\container\definition\reference\CollectionItem;
19
use samsonframework\container\definition\reference\CollectionReference;
20
use samsonframework\container\definition\reference\ConstantReference;
21
use samsonframework\container\definition\reference\FloatReference;
22
use samsonframework\container\definition\reference\IntegerReference;
23
use samsonframework\container\definition\reference\NullReference;
24
use samsonframework\container\definition\reference\ParameterReference;
25
use samsonframework\container\definition\reference\ReferenceInterface;
26
use samsonframework\container\definition\reference\ServiceReference;
27
use samsonframework\container\definition\reference\StringReference;
28
use samsonframework\container\definition\resolver\exception\ReferenceNotImplementsException;
29
30
/**
31
 * Class XmlResolver
32
 *
33
 * @author Ruslan Molodyko <[email protected]>
34
 */
35
class XmlResolver
36
{
37
    /** How parameter presents in xml code */
38
    const PARAMETERS_KEY = 'parameters';
39
    /** How dependencies presents in xml code */
40
    const DEPENDENCIES_KEY = 'dependencies';
41
    /** How instance presents in xml code */
42
    const INSTANCE_KEY = 'definition';
43
44
    /**
45
     * Resolve xml code
46
     *
47
     * @param DefinitionBuilder $definitionBuilder
48
     * @param $xml
49
     *
50
     * @throws ClassDefinitionAlreadyExistsException
51
     * @throws \InvalidArgumentException
52
     * @throws PropertyDefinitionAlreadyExistsException
53
     * @throws ReferenceNotImplementsException
54
     * @throws MethodDefinitionAlreadyExistsException
55
     * @throws ParameterDefinitionAlreadyExistsException
56
     * @throws \samsonframework\container\definition\parameter\exception\ParameterAlreadyExistsException
57
     */
58 1
    public function resolve(DefinitionBuilder $definitionBuilder, $xml)
59
    {
60
        /**
61
         * Iterate config and resolve single instance
62
         *
63
         * @var string $key
64
         * @var array $arrayData
65
         */
66 1
        foreach ($this->xml2array(new \SimpleXMLElement($xml)) as $key => $arrayData) {
67
            // Resolve parameters
68 1
            if ($key === self::PARAMETERS_KEY) {
69
                // Define parameters
70 1
                foreach ($arrayData as $parameterKey => $parameterArray) {
71 1
                    $definitionBuilder->defineParameter($parameterKey, $this->resolveDependency($parameterArray));
72
                }
73
            }
74
            // Resolve dependencies
75 1
            if ($key === self::DEPENDENCIES_KEY) {
76
                // Iterate instances
77 1
                foreach ($arrayData as $dependencyKey => $definitionsArrayData) {
78
                    // Get only definition instances
79 1
                    if ($dependencyKey === self::INSTANCE_KEY) {
80
                        /**
81
                         * If we have only one instance we need to add array
82
                         * @var array $collection
83
                         */
84 1
                        $collection = !array_key_exists(0,
85 1
                            $definitionsArrayData) ? [$definitionsArrayData] : $definitionsArrayData;
86
                        /**
87
                         * Iterate collection of instances
88
                         * @var array $definitionsArrayData
89
                         */
90 1
                        foreach ($collection as $definitionArrayData) {
91
                            /**
92
                             * Create class definition
93
                             * @var ClassDefinition $classDefinition
94
                             */
95 1
                            $classDefinition = $definitionBuilder->addDefinition($definitionArrayData['@attributes']['class']);
96
                            // Resolve constructor
97 1
                            if (array_key_exists('constructor', $definitionArrayData)) {
98 1
                                $this->resolveConstructor($classDefinition, $definitionArrayData['constructor']);
99
                            }
100
                            // Resolve methods
101 1
                            if (array_key_exists('methods', $definitionArrayData)) {
102
                                // Iteare methods
103 1
                                foreach ($definitionArrayData['methods'] as $methodName => $methodArray) {
104 1
                                    $this->resolveMethod($classDefinition, $methodArray, $methodName);
105
                                }
106
                            }
107
                            // Resolve properties
108 1
                            if (array_key_exists('properties', $definitionArrayData)) {
109
                                // Iterate properties
110 1
                                foreach ($definitionArrayData['properties'] as $propertyName => $propertyArray) {
111 1
                                    $this->resolveProperty($classDefinition, $propertyArray, $propertyName);
112
                                }
113
                            }
114
                        }
115
                    }
116
                }
117
            }
118
        }
119 1
    }
120
121
    /**
122
     * Resolve constructor
123
     *
124
     * @param ClassDefinition $classDefinition
125
     * @param array $constructorArray
126
     * @throws MethodDefinitionAlreadyExistsException
127
     * @throws \InvalidArgumentException
128
     * @throws ParameterDefinitionAlreadyExistsException
129
     * @throws ReferenceNotImplementsException
130
     */
131 1 View Code Duplication
    public function resolveConstructor(ClassDefinition $classDefinition, array $constructorArray)
0 ignored issues
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...
132
    {
133 1
        $methodDefinition = $classDefinition->defineConstructor();
134 1
        if (array_key_exists('arguments', $constructorArray)) {
135 1
            $this->resolveArguments($methodDefinition, $constructorArray['arguments']);
0 ignored issues
show
Compatibility introduced by
$methodDefinition of type object<samsonframework\c...MethodBuilderInterface> is not a sub-type of object<samsonframework\c...ition\MethodDefinition>. It seems like you assume a concrete implementation of the interface samsonframework\containe...\MethodBuilderInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
136
        }
137 1
    }
138
139
    /**
140
     * Resolve property
141
     *
142
     * @param ClassDefinition $classDefinition
143
     * @param array $propertyArray
144
     * @param string $propertyName
145
     * @throws \InvalidArgumentException
146
     * @throws ReferenceNotImplementsException
147
     * @throws PropertyDefinitionAlreadyExistsException
148
     */
149 1
    public function resolveProperty(ClassDefinition $classDefinition, array $propertyArray, string $propertyName)
150
    {
151 1
        $propertyDefinition = $classDefinition->defineProperty($propertyName);
152 1
        $propertyDefinition->defineDependency($this->resolveDependency($propertyArray));
153 1
    }
154
155
    /**
156
     * Resolve method
157
     *
158
     * @param ClassDefinition $classDefinition
159
     * @param array $methodArray
160
     * @param string $methodName
161
     * @throws MethodDefinitionAlreadyExistsException
162
     * @throws \InvalidArgumentException
163
     * @throws ParameterDefinitionAlreadyExistsException
164
     * @throws ReferenceNotImplementsException
165
     */
166 1 View Code Duplication
    public function resolveMethod(ClassDefinition $classDefinition, array $methodArray, string $methodName)
0 ignored issues
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...
167
    {
168 1
        $methodDefinition = $classDefinition->defineMethod($methodName);
169 1
        if (array_key_exists('arguments', $methodArray)) {
170 1
            $this->resolveArguments($methodDefinition, $methodArray['arguments']);
0 ignored issues
show
Compatibility introduced by
$methodDefinition of type object<samsonframework\c...MethodBuilderInterface> is not a sub-type of object<samsonframework\c...ition\MethodDefinition>. It seems like you assume a concrete implementation of the interface samsonframework\containe...\MethodBuilderInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
171
        }
172 1
    }
173
174
    /**
175
     * Resolve method/constructor arguments
176
     *
177
     * @param MethodDefinition $methodDefinition
178
     * @param array $arguments
179
     * @throws ParameterDefinitionAlreadyExistsException
180
     * @throws \InvalidArgumentException
181
     * @throws ReferenceNotImplementsException
182
     */
183 1
    public function resolveArguments(MethodDefinition $methodDefinition, array $arguments)
184
    {
185 1
        foreach ($arguments as $argumentName => $argumentValue) {
186
            $methodDefinition
187 1
                ->defineParameter($argumentName)
188 1
                    ->defineDependency($this->resolveDependency($argumentValue));
189
        }
190 1
    }
191
192
    /**
193
     * Resolve dependency
194
     *
195
     * @param $data
196
     * @return ReferenceInterface
197
     * @throws \InvalidArgumentException
198
     * @throws ReferenceNotImplementsException
199
     */
200 1
    public function resolveDependency($data): ReferenceInterface
201
    {
202
        // Get value type
203 1
        $type = $data['@attributes']['type'] ?? 'string';
204
        // Get value
205 1
        $value = $data['@attributes']['value'] ?? null;
206
        // When that is not a collection then value can not be null
207 1
        if ($type !== 'collection' && $value === null) {
208
            throw new \InvalidArgumentException(sprintf('Value for type "%s" have to be specified', $type));
209
        }
210
        // Resolve type
211
        switch ($type) {
212 1
            case 'text':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
213 1
            case 'string':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
214 1
                return new StringReference($value);
215 1
            case 'int':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
216 1
            case 'integer':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
217
                return new IntegerReference($value);
218 1
            case 'float':
219
                return new FloatReference($value);
220 1
            case 'boolean':
221 1
            case 'bool':
222
                return new BoolReference($value);
223 1
            case 'class':
224 1
                return new ClassReference($value);
225 1
            case 'constant':
226 1
                return new ConstantReference($value);
227 1
            case 'service':
228
                return new ServiceReference($value);
229 1
            case 'null':
230
                return new NullReference();
231 1
            case 'parameter':
232 1
                return new ParameterReference($value);
233 1
            case 'collection':
234 1
                return $this->resolveCollection($data);
235
            default:
236
                throw new ReferenceNotImplementsException();
237
        }
238
    }
239
240
    /**
241
     * Resolve collection type
242
     *
243
     * @param array $data
244
     * @return CollectionReference
245
     * @throws ReferenceNotImplementsException
246
     * @throws \InvalidArgumentException
247
     */
248 1
    public function resolveCollection(array $data): CollectionReference
249
    {
250 1
        $collection = new CollectionReference();
251 1
        if (array_key_exists('item', $data)) {
252
            /** @var array $itemCollection */
253 1
            $itemCollection = array_key_exists('key', $data['item']) && array_key_exists('value', $data['item'])
254 1
                ? [$data['item']]
255 1
                : $data['item'];
256
            /** @var array $item */
257 1
            foreach ($itemCollection as $item) {
258 1
                $collection->addItem(new CollectionItem(
259 1
                    $this->resolveDependency($item['key']),
260 1
                    $this->resolveDependency($item['value'])
261
                ));
262
            }
263
        }
264 1
        return $collection;
265
    }
266
267
    /**
268
     * Convert xml to array
269
     *
270
     * @param $xmlObject
271
     * @param array $out
272
     * @return array
273
     */
274 1
    protected function xml2array($xmlObject, array $out = []): array
275
    {
276 1
        foreach ((array)$xmlObject as $index => $node) {
277 1
            $out[$index] = (is_object($node) || is_array($node)) ? $this->xml2array($node) : $node;
278
        }
279 1
        return $out;
280
    }
281
}
282