Failed Conditions
Push — feature/moar-test-optimizing ( 091eca...fb462a )
by Lucas
24:29 queued 14:31
created

DocumentMap   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 390
Duplicated Lines 28.21 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 99.51%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 52
c 1
b 0
f 0
lcom 1
cbo 5
dl 110
loc 390
ccs 203
cts 204
cp 0.9951
rs 7.9487

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 4
A getDocuments() 0 4 1
A getDocument() 0 16 3
F processDocument() 0 97 29
A loadDoctrineClassMap() 22 22 2
A loadSerializerClassMap() 21 21 2
A loadValidationClassMap() 22 22 2
A getSerializerFields() 0 17 2
A getSerializerFieldType() 0 11 3
A getValidationFields() 0 16 1
A getDoctrineFields() 15 15 1
A getDoctrineEmbedOneFields() 15 15 1
A getDoctrineEmbedManyFields() 15 15 1

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 DocumentMap 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 DocumentMap, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * DocumentMap class file
4
 */
5
6
namespace Graviton\DocumentBundle\DependencyInjection\Compiler\Utils;
7
8
use Symfony\Component\Finder\Finder;
9
10
/**
11
 * Document map
12
 *
13
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
14
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
15
 * @link     http://swisscom.ch
16
 */
17
class DocumentMap
18
{
19
    /**
20
     * @var array
21
     */
22
    private $mappings = [];
23
    /**
24
     * @var Document[]
25
     */
26
    private $documents = [];
27
28
    /**
29
     * Constructor
30
     *
31
     * @param Finder $doctrineFinder   Doctrine mapping finder
32
     * @param Finder $serializerFinder Serializer mapping finder
33
     * @param Finder $validationFinder Validation mapping finder
34
     */
35 7
    public function __construct(Finder $doctrineFinder, Finder $serializerFinder, Finder $validationFinder)
36
    {
37 7
        $doctrineMap = $this->loadDoctrineClassMap($doctrineFinder);
38 7
        $serializerMap = $this->loadSerializerClassMap($serializerFinder);
39 7
        $validationMap = $this->loadValidationClassMap($validationFinder);
40
41 7
        foreach ($doctrineMap as $className => $doctrineMapping) {
42 7
            $this->mappings[$className] = [
43 7
                'doctrine'   => $doctrineMap[$className],
44 7
                'serializer' => isset($serializerMap[$className]) ? $serializerMap[$className] : null,
45 7
                'validation' => isset($validationMap[$className]) ? $validationMap[$className] : null,
46
            ];
47 7
        }
48 7
    }
49
50
    /**
51
     * Get document
52
     *
53
     * @param string $className Document class
54
     * @return Document
55
     */
56 7
    public function getDocument($className)
57
    {
58 7
        if (isset($this->documents[$className])) {
59 7
            return $this->documents[$className];
60
        }
61 7
        if (!isset($this->mappings[$className])) {
62
            throw new \InvalidArgumentException(sprintf('No XML mapping found for document "%s"', $className));
63
        }
64
65 7
        return $this->documents[$className] = $this->processDocument(
66 7
            $className,
67 7
            $this->mappings[$className]['doctrine'],
68 7
            $this->mappings[$className]['serializer'],
69 7
            $this->mappings[$className]['validation']
70 7
        );
71
    }
72
73
    /**
74
     * Get all documents
75
     *
76
     * @return Document[]
77
     */
78 5
    public function getDocuments()
79
    {
80 5
        return array_map([$this, 'getDocument'], array_keys($this->mappings));
81
    }
82
83
    /**
84
     * Process document
85
     *
86
     * @param string      $className         Class name
87
     * @param \DOMElement $doctrineMapping   Doctrine XML mapping
88
     * @param \DOMElement $serializerMapping Serializer XML mapping
0 ignored issues
show
Documentation introduced by
Should the type for parameter $serializerMapping not be null|\DOMElement?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
89
     * @param \DOMElement $validationMapping Validation XML mapping
0 ignored issues
show
Documentation introduced by
Should the type for parameter $validationMapping not be null|\DOMElement?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
90
     * @return Document
91
     */
92 7
    private function processDocument(
93
        $className,
94
        \DOMElement $doctrineMapping,
95
        \DOMElement $serializerMapping = null,
96
        \DOMElement $validationMapping = null
97
    ) {
98 7
        if ($serializerMapping === null) {
99 1
            $serializerFields = [];
100 1
        } else {
101 7
            $serializerFields = array_reduce(
102 7
                $this->getSerializerFields($serializerMapping),
103
                function (array $fields, array $field) {
104 7
                    $fields[$field['fieldName']] = $field;
105 7
                    return $fields;
106 7
                },
107 7
                []
108 7
            );
109
        }
110
111 7
        if ($validationMapping === null) {
112 1
            $validationFields = [];
113 1
        } else {
114 7
            $validationFields = array_reduce(
115 7
                $this->getValidationFields($validationMapping),
116
                function (array $fields, array $field) {
117 4
                    $fields[$field['fieldName']] = $field;
118 4
                    return $fields;
119 7
                },
120 7
                []
121 7
            );
122
        }
123
124 7
        $fields = [];
125 7
        foreach ($this->getDoctrineFields($doctrineMapping) as $doctrineField) {
126 7
            $serializerField = isset($serializerFields[$doctrineField['name']]) ?
127 7
                $serializerFields[$doctrineField['name']] :
128 7
                null;
129 7
            $validationField = isset($validationFields[$doctrineField['name']]) ?
130 7
                $validationFields[$doctrineField['name']] :
131 7
                null;
132
133 7
            if ($doctrineField['type'] === 'collection') {
134 1
                $fields[] = new ArrayField(
135 1
                    $serializerField === null ? 'array<string>' : $serializerField['fieldType'],
136 1
                    $doctrineField['name'],
137 1
                    $serializerField === null ? $doctrineField['name'] : $serializerField['exposedName'],
138 1
                    $serializerField === null ? false : $serializerField['readOnly'],
139 1
                    $validationField === null ? false : $validationField['required'],
140 1
                    $serializerField === null ? false : $serializerField['searchable']
0 ignored issues
show
Unused Code introduced by
The call to ArrayField::__construct() has too many arguments starting with $serializerField === nul...izerField['searchable'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
141 1
                );
142 7
            } else {
143 7
                $fields[] = new Field(
144 7
                    $doctrineField['type'],
145 7
                    $doctrineField['name'],
146 7
                    $serializerField === null ? $doctrineField['name'] : $serializerField['exposedName'],
147 7
                    $serializerField === null ? false : $serializerField['readOnly'],
148 7
                    $validationField === null ? false : $validationField['required'],
149
                    $serializerField === null ? false : $serializerField['searchable']
150 7
                );
151 7
            }
152 7
        }
153 7
        foreach ($this->getDoctrineEmbedOneFields($doctrineMapping) as $doctrineField) {
154 7
            $serializerField = isset($serializerFields[$doctrineField['name']]) ?
155 7
                $serializerFields[$doctrineField['name']] :
156 7
                null;
157 7
            $validationField = isset($validationFields[$doctrineField['name']]) ?
158
                $validationFields[$doctrineField['name']] :
159 7
                null;
160 7
161 7
            $fields[] = new EmbedOne(
162 7
                $this->getDocument($doctrineField['type']),
163 7
                $doctrineField['name'],
164 7
                $serializerField === null ? $doctrineField['name'] : $serializerField['exposedName'],
165 7
                $serializerField === null ? false : $serializerField['readOnly'],
166 7
                $validationField === null ? false : $validationField['required'],
167 7
                $serializerField === null ? false : $serializerField['searchable']
0 ignored issues
show
Unused Code introduced by
The call to EmbedOne::__construct() has too many arguments starting with $serializerField === nul...izerField['searchable'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
168 7
            );
169 7
        }
170 7
        foreach ($this->getDoctrineEmbedManyFields($doctrineMapping) as $doctrineField) {
171 7
            $serializerField = isset($serializerFields[$doctrineField['name']]) ?
172 7
                $serializerFields[$doctrineField['name']] :
173 7
                null;
174
            $validationField = isset($validationFields[$doctrineField['name']]) ?
175 7
                $validationFields[$doctrineField['name']] :
176 7
                null;
177 7
178 7
            $fields[] = new EmbedMany(
179 7
                $this->getDocument($doctrineField['type']),
180 7
                $doctrineField['name'],
181 7
                $serializerField === null ? $doctrineField['name'] : $serializerField['exposedName'],
182 7
                $serializerField === null ? false : $serializerField['readOnly'],
183
                $validationField === null ? false : $validationField['required']
184 7
            );
185
        }
186
187
        return new Document($className, $fields);
188
    }
189
190
    /**
191
     * Load doctrine class map
192
     *
193 7
     * @param Finder $finder Mapping finder
194
     * @return array
195 7
     */
196 7 View Code Duplication
    private function loadDoctrineClassMap(Finder $finder)
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...
197 7
    {
198 7
        $classMap = [];
199
        foreach ($finder as $file) {
200 7
            $document = new \DOMDocument();
201 7
            $document->load($file);
202
203 7
            $xpath = new \DOMXPath($document);
204 7
            $xpath->registerNamespace('doctrine', 'http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping');
205
206 7
            $classMap = array_reduce(
207 7
                iterator_to_array($xpath->query('//*[self::doctrine:document or self::doctrine:embedded-document]')),
208 7
                function (array $classMap, \DOMElement $element) {
209
                    $classMap[$element->getAttribute('name')] = $element;
210 7
                    return $classMap;
211 7
                },
212
                $classMap
213 7
            );
214
        }
215
216
        return $classMap;
217
    }
218
219
    /**
220
     * Load serializer class map
221
     *
222 7
     * @param Finder $finder Mapping finder
223
     * @return array
224 7
     */
225 7 View Code Duplication
    private function loadSerializerClassMap(Finder $finder)
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...
226 7
    {
227 7
        $classMap = [];
228
        foreach ($finder as $file) {
229 7
            $document = new \DOMDocument();
230
            $document->load($file);
231 7
232 7
            $xpath = new \DOMXPath($document);
233
234 7
            $classMap = array_reduce(
235 7
                iterator_to_array($xpath->query('//class')),
236 7
                function (array $classMap, \DOMElement $element) {
237
                    $classMap[$element->getAttribute('name')] = $element;
238 7
                    return $classMap;
239 7
                },
240
                $classMap
241 7
            );
242
        }
243
244
        return $classMap;
245
    }
246
247
    /**
248
     * Load validation class map
249
     *
250 7
     * @param Finder $finder Mapping finder
251
     * @return array
252 7
     */
253 7 View Code Duplication
    private function loadValidationClassMap(Finder $finder)
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...
254 7
    {
255 7
        $classMap = [];
256
        foreach ($finder as $file) {
257 7
            $document = new \DOMDocument();
258 7
            $document->load($file);
259
260 7
            $xpath = new \DOMXPath($document);
261 7
            $xpath->registerNamespace('constraint', 'http://symfony.com/schema/dic/constraint-mapping');
262
263 7
            $classMap = array_reduce(
264 7
                iterator_to_array($xpath->query('//constraint:class')),
265 7
                function (array $classMap, \DOMElement $element) {
266
                    $classMap[$element->getAttribute('name')] = $element;
267 7
                    return $classMap;
268 7
                },
269
                $classMap
270 7
            );
271
        }
272
273
        return $classMap;
274
    }
275
276
    /**
277
     * Get serializer fields
278
     *
279 7
     * @param \DOMElement $mapping Serializer XML mapping
280
     * @return array
281 7
     */
282
    private function getSerializerFields(\DOMElement $mapping)
283 7
    {
284
        $xpath = new \DOMXPath($mapping->ownerDocument);
285
286 7
        return array_map(
287 7
            function (\DOMElement $element) {
288 7
                return [
289 7
                    'fieldName'   => $element->getAttribute('name'),
290 7
                    'fieldType'   => $this->getSerializerFieldType($element),
291 7
                    'exposedName' => $element->getAttribute('serialized-name') ?: $element->getAttribute('name'),
292 7
                    'readOnly'    => $element->getAttribute('read-only') === 'true',
293 7
                    'searchable'  => $element->getAttribute('searchable') === 'true',
294
                ];
295
            },
296
            iterator_to_array($xpath->query('property', $mapping))
297
        );
298
    }
299
300
    /**
301
     * Get serializer field type
302 7
     *
303
     * @param \DOMElement $field Field node
304 7
     * @return string|null
305 7
     */
306
    private function getSerializerFieldType(\DOMElement $field)
307
    {
308 1
        if ($field->getAttribute('type')) {
309
            return $field->getAttribute('type');
310 1
        }
311 1
312
        $xpath = new \DOMXPath($field->ownerDocument);
313
314
        $type = $xpath->query('type', $field)->item(0);
315
        return $type === null ? null : $type->nodeValue;
316
    }
317
318
    /**
319
     * Get validation fields
320 7
     *
321
     * @param \DOMElement $mapping Validation XML mapping
322 7
     * @return array
323 7
     */
324
    private function getValidationFields(\DOMElement $mapping)
325 7
    {
326
        $xpath = new \DOMXPath($mapping->ownerDocument);
327 4
        $xpath->registerNamespace('constraint', 'http://symfony.com/schema/dic/constraint-mapping');
328
329 4
        return array_map(
330 4
            function (\DOMElement $element) use ($xpath) {
331 4
                $constraints = $xpath->query('constraint:constraint[@name="NotBlank" or @name="NotNull"]', $element);
332 7
                return [
333 7
                    'fieldName' => $element->getAttribute('name'),
334 7
                    'required'  => $constraints->length > 0,
335
                ];
336
            },
337
            iterator_to_array($xpath->query('constraint:property', $mapping))
338
        );
339
    }
340
341
    /**
342
     * Get doctrine document fields
343 7
     *
344
     * @param \DOMElement $mapping Doctrine XML mapping
345 7
     * @return array
346 7
     */
347 View Code Duplication
    private function getDoctrineFields(\DOMElement $mapping)
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...
348 7
    {
349
        $xpath = new \DOMXPath($mapping->ownerDocument);
350
        $xpath->registerNamespace('doctrine', 'http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping');
351 7
352 7
        return array_map(
353 7
            function (\DOMElement $element) {
354 7
                return [
355 7
                    'name' => $element->getAttribute('fieldName'),
356 7
                    'type' => $element->getAttribute('type'),
357
                ];
358
            },
359
            iterator_to_array($xpath->query('doctrine:field', $mapping))
360
        );
361
    }
362
363
    /**
364
     * Get doctrine document embed-one fields
365 7
     *
366
     * @param \DOMElement $mapping Doctrine XML mapping
367 7
     * @return array
368 7
     */
369 View Code Duplication
    private function getDoctrineEmbedOneFields(\DOMElement $mapping)
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...
370 7
    {
371
        $xpath = new \DOMXPath($mapping->ownerDocument);
372
        $xpath->registerNamespace('doctrine', 'http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping');
373 7
374 7
        return array_map(
375 7
            function (\DOMElement $element) {
376 7
                return [
377 7
                    'name' => $element->getAttribute('field'),
378 7
                    'type' => $element->getAttribute('target-document'),
379
                ];
380
            },
381
            iterator_to_array($xpath->query('*[self::doctrine:embed-one or self::doctrine:reference-one]', $mapping))
382
        );
383
    }
384
385
    /**
386
     * Get doctrine document embed-many fields
387 7
     *
388
     * @param \DOMElement $mapping Doctrine XML mapping
389 7
     * @return array
390 7
     */
391 View Code Duplication
    private function getDoctrineEmbedManyFields(\DOMElement $mapping)
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...
392 7
    {
393 7
        $xpath = new \DOMXPath($mapping->ownerDocument);
394
        $xpath->registerNamespace('doctrine', 'http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping');
395 7
396 7
        return array_map(
397 7
            function (\DOMElement $element) {
398 7
                return [
399 7
                    'name' => $element->getAttribute('field'),
400 7
                    'type' => $element->getAttribute('target-document'),
401
                ];
402
            },
403
            iterator_to_array($xpath->query('*[self::doctrine:embed-many or self::doctrine:reference-many]', $mapping))
404
        );
405
    }
406
}
407