XmlResolver   C
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 272
Duplicated Lines 5.15 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 85.56%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 18
dl 14
loc 272
ccs 77
cts 90
cp 0.8556
rs 5.3384
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveFile() 0 8 2
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\parameter\exception\ParameterAlreadyExistsException;
17
use samsonframework\container\definition\reference\BoolReference;
18
use samsonframework\container\definition\reference\ClassReference;
19
use samsonframework\container\definition\reference\CollectionItem;
20
use samsonframework\container\definition\reference\CollectionReference;
21
use samsonframework\container\definition\reference\ConstantReference;
22
use samsonframework\container\definition\reference\FloatReference;
23
use samsonframework\container\definition\reference\IntegerReference;
24
use samsonframework\container\definition\reference\NullReference;
25
use samsonframework\container\definition\reference\ParameterReference;
26
use samsonframework\container\definition\reference\ReferenceInterface;
27
use samsonframework\container\definition\reference\ServiceReference;
28
use samsonframework\container\definition\reference\StringReference;
29
use samsonframework\container\definition\resolver\exception\FileNotFoundException;
30
use samsonframework\container\definition\resolver\exception\ReferenceNotImplementsException;
31
32
/**
33
 * Class XmlResolver
34
 *
35
 * @author Ruslan Molodyko <[email protected]>
36
 */
37
class XmlResolver
38
{
39
    /** How parameter presents in xml code */
40
    const PARAMETERS_KEY = 'parameters';
41
    /** How dependencies presents in xml code */
42
    const DEPENDENCIES_KEY = 'dependencies';
43
    /** How instance presents in xml code */
44
    const INSTANCE_KEY = 'definition';
45
46
    /**
47
     * Resolve file
48
     *
49
     * @param DefinitionBuilder $builder
50
     * @param string $path
51
     *
52
     * @throws FileNotFoundException
53
     * @throws ClassDefinitionAlreadyExistsException
54
     * @throws \InvalidArgumentException
55
     * @throws PropertyDefinitionAlreadyExistsException
56
     * @throws ReferenceNotImplementsException
57
     * @throws MethodDefinitionAlreadyExistsException
58
     * @throws ParameterDefinitionAlreadyExistsException
59
     * @throws ParameterAlreadyExistsException
60
     */
61
    public function resolveFile(DefinitionBuilder $builder, string $path)
62
    {
63
        if (!file_exists($path)) {
64
            throw new FileNotFoundException($path);
65
        }
66
        $xml = file_get_contents($path);
67
        $this->resolve($builder, $xml);
68
    }
69
70
    /**
71
     * Resolve xml code
72
     *
73
     * @param DefinitionBuilder $definitionBuilder
74
     * @param string $xml
75
     *
76
     * @throws ClassDefinitionAlreadyExistsException
77
     * @throws \InvalidArgumentException
78
     * @throws PropertyDefinitionAlreadyExistsException
79
     * @throws ReferenceNotImplementsException
80
     * @throws MethodDefinitionAlreadyExistsException
81
     * @throws ParameterDefinitionAlreadyExistsException
82
     * @throws ParameterAlreadyExistsException
83
     */
84 1
    public function resolve(DefinitionBuilder $definitionBuilder, string $xml)
85
    {
86
        /**
87
         * Iterate config and resolve single instance
88
         *
89
         * @var string $key
90
         * @var array $arrayData
91
         */
92 1
        foreach ($this->xml2array(new \SimpleXMLElement($xml)) as $key => $arrayData) {
93
            // Resolve parameters
94 1
            if ($key === self::PARAMETERS_KEY) {
95
                // Define parameters
96 1
                foreach ($arrayData as $parameterKey => $parameterArray) {
97 1
                    $definitionBuilder->defineParameter($parameterKey, $this->resolveDependency($parameterArray));
98
                }
99
            }
100
            // Resolve dependencies
101 1
            if ($key === self::DEPENDENCIES_KEY) {
102
                // Iterate instances
103 1
                foreach ($arrayData as $dependencyKey => $definitionsArrayData) {
104
                    // Get only definition instances
105 1
                    if ($dependencyKey === self::INSTANCE_KEY) {
106
                        /**
107
                         * If we have only one instance we need to add array
108
                         * @var array $collection
109
                         */
110 1
                        $collection = !array_key_exists(0,
111 1
                            $definitionsArrayData) ? [$definitionsArrayData] : $definitionsArrayData;
112
                        /**
113
                         * Iterate collection of instances
114
                         * @var array $definitionsArrayData
115
                         */
116 1
                        foreach ($collection as $definitionArrayData) {
117
                            /**
118
                             * Create class definition
119
                             * @var ClassDefinition $classDefinition
120
                             */
121 1
                            $classDefinition = $definitionBuilder->addDefinition($definitionArrayData['@attributes']['class']);
122
                            // Resolve constructor
123 1
                            if (array_key_exists('constructor', $definitionArrayData)) {
124 1
                                $this->resolveConstructor($classDefinition, $definitionArrayData['constructor']);
125
                            }
126
                            // Resolve methods
127 1
                            if (array_key_exists('methods', $definitionArrayData)) {
128
                                // Iteare methods
129 1
                                foreach ($definitionArrayData['methods'] as $methodName => $methodArray) {
130 1
                                    $this->resolveMethod($classDefinition, $methodArray, $methodName);
131
                                }
132
                            }
133
                            // Resolve properties
134 1
                            if (array_key_exists('properties', $definitionArrayData)) {
135
                                // Iterate properties
136 1
                                foreach ($definitionArrayData['properties'] as $propertyName => $propertyArray) {
137 1
                                    $this->resolveProperty($classDefinition, $propertyArray, $propertyName);
138
                                }
139
                            }
140
                        }
141
                    }
142
                }
143
            }
144
        }
145 1
    }
146
147
    /**
148
     * Resolve constructor
149
     *
150
     * @param ClassDefinition $classDefinition
151
     * @param array $constructorArray
152
     * @throws MethodDefinitionAlreadyExistsException
153
     * @throws \InvalidArgumentException
154
     * @throws ParameterDefinitionAlreadyExistsException
155
     * @throws ReferenceNotImplementsException
156
     */
157 1 View Code Duplication
    protected 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...
158
    {
159 1
        $methodDefinition = $classDefinition->defineConstructor();
160 1
        if (array_key_exists('arguments', $constructorArray)) {
161 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...
162
        }
163 1
    }
164
165
    /**
166
     * Resolve property
167
     *
168
     * @param ClassDefinition $classDefinition
169
     * @param array $propertyArray
170
     * @param string $propertyName
171
     * @throws \InvalidArgumentException
172
     * @throws ReferenceNotImplementsException
173
     * @throws PropertyDefinitionAlreadyExistsException
174
     */
175 1
    protected function resolveProperty(ClassDefinition $classDefinition, array $propertyArray, string $propertyName)
176
    {
177 1
        $propertyDefinition = $classDefinition->defineProperty($propertyName);
178 1
        $propertyDefinition->defineDependency($this->resolveDependency($propertyArray));
179 1
    }
180
181
    /**
182
     * Resolve method
183
     *
184
     * @param ClassDefinition $classDefinition
185
     * @param array $methodArray
186
     * @param string $methodName
187
     * @throws MethodDefinitionAlreadyExistsException
188
     * @throws \InvalidArgumentException
189
     * @throws ParameterDefinitionAlreadyExistsException
190
     * @throws ReferenceNotImplementsException
191
     */
192 1 View Code Duplication
    protected 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...
193
    {
194 1
        $methodDefinition = $classDefinition->defineMethod($methodName);
195 1
        if (array_key_exists('arguments', $methodArray)) {
196 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...
197
        }
198 1
    }
199
200
    /**
201
     * Resolve method/constructor arguments
202
     *
203
     * @param MethodDefinition $methodDefinition
204
     * @param array $arguments
205
     * @throws ParameterDefinitionAlreadyExistsException
206
     * @throws \InvalidArgumentException
207
     * @throws ReferenceNotImplementsException
208
     */
209 1
    protected function resolveArguments(MethodDefinition $methodDefinition, array $arguments)
210
    {
211 1
        foreach ($arguments as $argumentName => $argumentValue) {
212
            $methodDefinition
213 1
                ->defineParameter($argumentName)
214 1
                    ->defineDependency($this->resolveDependency($argumentValue));
215
        }
216 1
    }
217
218
    /**
219
     * Resolve dependency
220
     *
221
     * @param $data
222
     * @return ReferenceInterface
223
     * @throws \InvalidArgumentException
224
     * @throws ReferenceNotImplementsException
225
     */
226 1
    protected function resolveDependency($data): ReferenceInterface
227
    {
228
        // Get value type
229 1
        $type = $data['@attributes']['type'] ?? 'string';
230
        // Get value
231 1
        $value = $data['@attributes']['value'] ?? null;
232
        // When that is not a collection then value can not be null
233 1
        if ($type !== 'collection' && $value === null) {
234
            throw new \InvalidArgumentException(sprintf('Value for type "%s" have to be specified', $type));
235
        }
236
        // Resolve type
237
        switch ($type) {
238 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...
239 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...
240 1
                return new StringReference($value);
241 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...
242 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...
243
                return new IntegerReference($value);
244 1
            case 'float':
245
                return new FloatReference($value);
246 1
            case 'boolean':
247 1
            case 'bool':
248
                return new BoolReference($value);
249 1
            case 'class':
250 1
                return new ClassReference($value);
251 1
            case 'constant':
252 1
                return new ConstantReference($value);
253 1
            case 'service':
254
                return new ServiceReference($value);
255 1
            case 'null':
256
                return new NullReference();
257 1
            case 'parameter':
258 1
                return new ParameterReference($value);
259 1
            case 'collection':
260 1
                return $this->resolveCollection($data);
261
            default:
262
                throw new ReferenceNotImplementsException();
263
        }
264
    }
265
266
    /**
267
     * Resolve collection type
268
     *
269
     * @param array $data
270
     * @return CollectionReference
271
     * @throws \samsonframework\container\definition\builder\exception\ReferenceNotImplementsException
272
     * @throws ReferenceNotImplementsException
273
     * @throws \InvalidArgumentException
274
     */
275 1
    protected function resolveCollection(array $data): CollectionReference
276
    {
277 1
        $collection = new CollectionReference();
278 1
        if (array_key_exists('item', $data)) {
279
            /** @var array $itemCollection */
280 1
            $itemCollection = array_key_exists('key', $data['item']) && array_key_exists('value', $data['item'])
281 1
                ? [$data['item']]
282 1
                : $data['item'];
283
            /** @var array $item */
284 1
            foreach ($itemCollection as $item) {
285 1
                $collection->addItem(new CollectionItem(
286 1
                    $this->resolveDependency($item['key']),
287 1
                    $this->resolveDependency($item['value'])
288
                ));
289
            }
290
        }
291 1
        return $collection;
292
    }
293
294
    /**
295
     * Convert xml to array
296
     *
297
     * @param $xmlObject
298
     * @param array $out
299
     * @return array
300
     */
301 1
    protected function xml2array($xmlObject, array $out = []): array
302
    {
303 1
        foreach ((array)$xmlObject as $index => $node) {
304 1
            $out[$index] = (is_object($node) || is_array($node)) ? $this->xml2array($node) : $node;
305
        }
306 1
        return $out;
307
    }
308
}
309