Completed
Pull Request — master (#13)
by Eric
04:57
created

AbstractClassMetadataLoader   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 392
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 7
dl 0
loc 392
ccs 230
cts 230
cp 1
rs 6.0975
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
B loadClassMetadata() 0 29 5
loadData() 0 1 ?
B doLoadClassMetadata() 0 30 6
F loadPropertyMetadata() 0 77 20
A sortProperties() 0 14 4
B isPropertyExposed() 0 7 6
C configureClassOptions() 0 75 11
B configurePropertyOptions() 0 66 4
A getEmptyValidator() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like AbstractClassMetadataLoader 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 AbstractClassMetadataLoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Ivory Serializer package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\Serializer\Mapping\Loader;
13
14
use Ivory\Serializer\Exclusion\ExclusionPolicy;
15
use Ivory\Serializer\Mapping\ClassMetadataInterface;
16
use Ivory\Serializer\Mapping\PropertyMetadata;
17
use Ivory\Serializer\Mapping\PropertyMetadataInterface;
18
use Ivory\Serializer\Type\Parser\TypeParser;
19
use Ivory\Serializer\Type\Parser\TypeParserInterface;
20
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
21
use Symfony\Component\OptionsResolver\Options;
22
use Symfony\Component\OptionsResolver\OptionsResolver;
23
24
/**
25
 * @author GeLo <[email protected]>
26
 */
27
abstract class AbstractClassMetadataLoader implements ClassMetadataLoaderInterface
28
{
29
    /**
30
     * @var TypeParserInterface
31
     */
32
    private $typeParser;
33
34
    /**
35
     * @var mixed[][]
36
     */
37
    private $data = [];
38
39
    /**
40
     * @var OptionsResolver|null
41
     */
42
    private $classResolver;
43
44
    /**
45
     * @var OptionsResolver|null
46
     */
47
    private $propertyResolver;
48
49
    /**
50
     * @var \Closure|null
51
     */
52
    private $emptyValidator;
53
54
    /**
55
     * @param TypeParserInterface|null $typeParser
56
     */
57 2256
    public function __construct(TypeParserInterface $typeParser = null)
58
    {
59 2256
        $this->typeParser = $typeParser ?: new TypeParser();
60 2256
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 1836
    public function loadClassMetadata(ClassMetadataInterface $classMetadata)
66
    {
67 1836
        $class = $classMetadata->getName();
68
69 1836
        if (!array_key_exists($class, $this->data)) {
70 1836
            $this->data[$class] = $this->loadData($class);
71 888
        }
72
73 1776
        if (!is_array($data = $this->data[$class])) {
74 24
            return false;
75
        }
76
77 1752
        if ($this->classResolver === null) {
78 1752
            $this->configureClassOptions($this->classResolver = new OptionsResolver());
79 876
        }
80
81
        try {
82 1752
            $data = $this->classResolver->resolve($data);
83 1000
        } catch (\InvalidArgumentException $e) {
84 248
            throw new \InvalidArgumentException(sprintf(
85 248
                'The mapping for the class "%s" is not valid.',
86
                $class
87 248
            ), 0, $e);
88
        }
89
90 1504
        $this->doLoadClassMetadata($classMetadata, $data);
91
92 1504
        return true;
93
    }
94
95
    /**
96
     * @param string $class
97
     *
98
     * @return mixed[]|null
99
     */
100
    abstract protected function loadData($class);
101
102
    /**
103
     * @param ClassMetadataInterface $classMetadata
104
     * @param mixed[]                $data
105
     */
106 1504
    private function doLoadClassMetadata(ClassMetadataInterface $classMetadata, array $data)
107
    {
108 1504
        $properties = $classMetadata->getProperties();
109
110 1504
        foreach ($data['properties'] as $property => $value) {
111 1504
            $propertyMetadata = $classMetadata->getProperty($property);
112
113 1504
            if ($propertyMetadata === null) {
114 1504
                $propertyMetadata = new PropertyMetadata($property, $classMetadata->getName());
115 752
            }
116
117 1504
            $this->loadPropertyMetadata($propertyMetadata, $value, $data['readable'], $data['writable']);
118
119 1504
            if ($this->isPropertyExposed($value, $data['exclusion_policy'])) {
120 1504
                $properties[$property] = $propertyMetadata;
121 752
            } else {
122 800
                unset($properties[$property]);
123
            }
124 752
        }
125
126 1504
        if (isset($data['order'])) {
127 144
            $properties = $this->sortProperties($properties, $data['order']);
128 72
        }
129
130 1504
        if (isset($data['xml_root'])) {
131 96
            $classMetadata->setXmlRoot($data['xml_root']);
132 48
        }
133
134 1504
        $classMetadata->setProperties($properties);
135 1504
    }
136
137
    /**
138
     * @param PropertyMetadataInterface $propertyMetadata
139
     * @param mixed                     $data
140
     * @param bool                      $classReadable
141
     * @param bool                      $classWritable
142
     */
143 1504
    private function loadPropertyMetadata(
144
        PropertyMetadataInterface $propertyMetadata,
145
        $data,
146
        $classReadable,
147
        $classWritable
148
    ) {
149 1504
        $propertyMetadata->setReadable(isset($data['readable']) ? $data['readable'] : $classReadable);
150 1504
        $propertyMetadata->setWritable(isset($data['writable']) ? $data['writable'] : $classWritable);
151
152 1504
        if (isset($data['alias'])) {
153 144
            $propertyMetadata->setAlias($data['alias']);
154 72
        }
155
156 1504
        if (isset($data['type'])) {
157 1208
            $propertyMetadata->setType($this->typeParser->parse($data['type']));
158 604
        }
159
160 1504
        if (isset($data['accessor'])) {
161 48
            $propertyMetadata->setAccessor($data['accessor']);
162 24
        }
163
164 1504
        if (isset($data['mutator'])) {
165 48
            $propertyMetadata->setMutator($data['mutator']);
166 24
        }
167
168 1504
        if (isset($data['since'])) {
169 112
            $propertyMetadata->setSinceVersion($data['since']);
170 56
        }
171
172 1504
        if (isset($data['until'])) {
173 112
            $propertyMetadata->setUntilVersion($data['until']);
174 56
        }
175
176 1504
        if (isset($data['max_depth'])) {
177 80
            $propertyMetadata->setMaxDepth($data['max_depth']);
178 40
        }
179
180 1504
        if (isset($data['groups'])) {
181 128
            $propertyMetadata->setGroups($data['groups']);
182 64
        }
183
184 1504
        if (isset($data['xml_attribute'])) {
185 96
            $propertyMetadata->setXmlAttribute($data['xml_attribute']);
186 48
        }
187
188 1504
        if (isset($data['xml_value'])) {
189 48
            $propertyMetadata->setXmlValue($data['xml_value']);
190 24
        }
191
192 1504
        if (isset($data['xml_inline'])) {
193 48
            $propertyMetadata->setXmlInline($data['xml_inline']);
194
195 48
            if (!isset($data['xml_key_as_attribute'])) {
196 48
                $data['xml_key_as_attribute'] = true;
197 24
            }
198
199 48
            if (!isset($data['xml_key_as_node'])) {
200 48
                $data['xml_key_as_node'] = false;
201 24
            }
202 24
        }
203
204 1504
        if (isset($data['xml_entry'])) {
205 48
            $propertyMetadata->setXmlEntry($data['xml_entry']);
206 24
        }
207
208 1504
        if (isset($data['xml_entry_attribute'])) {
209 48
            $propertyMetadata->setXmlEntryAttribute($data['xml_entry_attribute']);
210 24
        }
211
212 1504
        if (isset($data['xml_key_as_attribute'])) {
213 48
            $propertyMetadata->setXmlKeyAsAttribute($data['xml_key_as_attribute']);
214 24
        }
215
216 1504
        if (isset($data['xml_key_as_node'])) {
217 48
            $propertyMetadata->setXmlKeyAsNode($data['xml_key_as_node']);
218 24
        }
219 1504
    }
220
221
    /**
222
     * @param PropertyMetadataInterface[] $properties
223
     * @param string|string[]             $order
224
     *
225
     * @return PropertyMetadataInterface[]
226
     */
227 144
    private function sortProperties(array $properties, $order)
228
    {
229 144
        if (is_string($order)) {
230 96
            if ($order === 'ASC') {
231 48
                ksort($properties);
232 24
            } else {
233 72
                krsort($properties);
234
            }
235 96
        } elseif (is_array($order)) {
236 48
            $properties = array_merge(array_flip($order), $properties);
237 24
        }
238
239 144
        return $properties;
240
    }
241
242
    /**
243
     * @param mixed[] $property
244
     * @param string  $policy
245
     *
246
     * @return bool
247
     */
248 1504
    private function isPropertyExposed($property, $policy)
249
    {
250 1504
        $expose = isset($property['expose']) && $property['expose'];
251 1504
        $exclude = isset($property['exclude']) && $property['exclude'];
252
253 1504
        return ($policy === ExclusionPolicy::ALL && $expose) || ($policy === ExclusionPolicy::NONE && !$exclude);
254
    }
255
256
    /**
257
     * @param OptionsResolver $resolver
258
     */
259 1752
    private function configureClassOptions(OptionsResolver $resolver)
260
    {
261 1752
        $emptyValidator = $this->getEmptyValidator();
262
263
        $resolver
264 1752
            ->setRequired(['properties'])
265 1752
            ->setDefaults([
266 1752
                'exclusion_policy' => ExclusionPolicy::NONE,
267 876
                'readable'         => true,
268 876
                'writable'         => true,
269 876
            ])
270 1752
            ->setDefined([
271 1752
                'order',
272 876
                'xml_root',
273 876
            ])
274 1752
            ->setAllowedTypes('order', ['array', 'string'])
275 1752
            ->setAllowedTypes('properties', 'array')
276 1752
            ->setAllowedTypes('readable', 'bool')
277 1752
            ->setAllowedTypes('writable', 'bool')
278 1752
            ->setAllowedTypes('xml_root', 'string')
279 1752
            ->setAllowedValues('exclusion_policy', [ExclusionPolicy::NONE, ExclusionPolicy::ALL])
280 1752
            ->setAllowedValues('order', $emptyValidator)
281 1752
            ->setAllowedValues('xml_root', $emptyValidator)
282
            ->setAllowedValues('properties', function ($properties) {
283 1700
                return count($properties) > 0;
284 1752
            })
285
            ->setNormalizer('order', function (Options $options, $order) use ($emptyValidator) {
286 164
                if (is_string($order)) {
287 108
                    if (strcasecmp($order, 'ASC') === 0 || strcasecmp($order, 'DESC') === 0) {
288 96
                        return strtoupper($order);
289
                    }
290
291 12
                    $order = array_map('trim', explode(',', $order));
292 6
                }
293
294 68
                if (is_array($order)) {
295 68
                    $properties = $options['properties'];
296
297 68
                    foreach ($order as $property) {
298 68
                        if (!isset($properties[$property])) {
299 20
                            throw new InvalidOptionsException(sprintf(
300 44
                                'The property "%s" defined in the mapping order does not exist.',
301
                                $property
302 10
                            ));
303
                        }
304 24
                    }
305 24
                }
306
307 48
                return $order;
308 1752
            })
309
            ->setNormalizer('properties', function (Options $options, array $properties) {
310 1688
                if ($this->propertyResolver === null) {
311 1688
                    $this->configurePropertyOptions($this->propertyResolver = new OptionsResolver());
312 844
                }
313
314 1688
                $results = [];
315
316 1688
                foreach ($properties as $key => $property) {
317 1688
                    if ($property === null) {
318 56
                        $property = [];
319 28
                    }
320
321
                    try {
322 1688
                        $results[$key] = $this->propertyResolver->resolve($property);
323 926
                    } catch (\InvalidArgumentException $e) {
324 164
                        throw new \InvalidArgumentException(sprintf(
325 164
                            'The mapping for the property "%s" is not valid.',
326
                            $key
327 926
                        ), 0, $e);
328
                    }
329 762
                }
330
331 1524
                return $results;
332 1752
            });
333 1752
    }
334
335
    /**
336
     * @param OptionsResolver $resolver
337
     */
338 1688
    private function configurePropertyOptions(OptionsResolver $resolver)
339
    {
340 1688
        $emptyValidator = $this->getEmptyValidator();
341
342
        $resolver
343 1688
            ->setDefined([
344 1688
                'accessor',
345 844
                'alias',
346 844
                'exclude',
347 844
                'expose',
348 844
                'groups',
349 844
                'max_depth',
350 844
                'mutator',
351 844
                'readable',
352 844
                'since',
353 844
                'type',
354 844
                'until',
355 844
                'writable',
356 844
                'xml_attribute',
357 844
                'xml_entry',
358 844
                'xml_entry_attribute',
359 844
                'xml_inline',
360 844
                'xml_key_as_attribute',
361 844
                'xml_key_as_node',
362 844
                'xml_value',
363 844
            ])
364 1688
            ->setAllowedTypes('accessor', 'string')
365 1688
            ->setAllowedTypes('alias', 'string')
366 1688
            ->setAllowedTypes('exclude', 'bool')
367 1688
            ->setAllowedTypes('expose', 'bool')
368 1688
            ->setAllowedTypes('groups', 'array')
369 1688
            ->setAllowedTypes('max_depth', 'int')
370 1688
            ->setAllowedTypes('mutator', 'string')
371 1688
            ->setAllowedTypes('readable', 'bool')
372 1688
            ->setAllowedTypes('since', 'string')
373 1688
            ->setAllowedTypes('type', 'string')
374 1688
            ->setAllowedTypes('until', 'string')
375 1688
            ->setAllowedTypes('writable', 'bool')
376 1688
            ->setAllowedTypes('xml_attribute', 'bool')
377 1688
            ->setAllowedTypes('xml_entry', 'string')
378 1688
            ->setAllowedTypes('xml_entry_attribute', 'string')
379 1688
            ->setAllowedTypes('xml_inline', 'bool')
380 1688
            ->setAllowedTypes('xml_key_as_attribute', 'bool')
381 1688
            ->setAllowedTypes('xml_key_as_node', 'bool')
382 1688
            ->setAllowedTypes('xml_value', 'bool')
383 1688
            ->setAllowedValues('accessor', $emptyValidator)
384 1688
            ->setAllowedValues('alias', $emptyValidator)
385 1688
            ->setAllowedValues('mutator', $emptyValidator)
386 1688
            ->setAllowedValues('since', $emptyValidator)
387 1688
            ->setAllowedValues('type', $emptyValidator)
388 1688
            ->setAllowedValues('until', $emptyValidator)
389 1688
            ->setAllowedValues('xml_entry', $emptyValidator)
390 1688
            ->setAllowedValues('xml_entry_attribute', $emptyValidator)
391
            ->setAllowedValues('groups', function (array $groups) use ($emptyValidator) {
392 136
                foreach ($groups as $group) {
393 136
                    if (!is_string($group) || !call_user_func($emptyValidator, $group)) {
394 72
                        return false;
395
                    }
396 64
                }
397
398 128
                return true;
399 1688
            })
400
            ->setAllowedValues('max_depth', function ($maxDepth) {
401 84
                return $maxDepth >= 0;
402 1688
            });
403 1688
    }
404
405
    /**
406
     * @return \Closure
407
     */
408 1752
    private function getEmptyValidator()
409
    {
410 1752
        if ($this->emptyValidator === null) {
411 1424
            $this->emptyValidator = function ($value) {
412 1424
                return !empty($value);
413
            };
414 876
        }
415
416 1752
        return $this->emptyValidator;
417
    }
418
}
419