loadPropertyMetadata()   F
last analyzed

Complexity

Conditions 20
Paths > 20000

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 78
c 0
b 0
f 0
ccs 40
cts 40
cp 1
rs 0
cc 20
nc 327680
nop 2
crap 20

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
/*
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 3156
    public function __construct(TypeParserInterface $typeParser = null)
58
    {
59 3156
        $this->typeParser = $typeParser ?: new TypeParser();
60 3156
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 2524
    public function loadClassMetadata(ClassMetadataInterface $classMetadata)
66
    {
67 2524
        $class = $classMetadata->getName();
68
69 2524
        if (!array_key_exists($class, $this->data)) {
70 2524
            $this->data[$class] = $this->loadData($class);
71
        }
72
73 2400
        if (!is_array($data = $this->data[$class])) {
74 40
            return false;
75
        }
76
77 2360
        if ($this->classResolver === null) {
78 2360
            $this->configureClassOptions($this->classResolver = new OptionsResolver());
79
        }
80
81
        try {
82 2360
            $data = $this->classResolver->resolve($data);
83 552
        } catch (\InvalidArgumentException $e) {
84 552
            throw new \InvalidArgumentException(sprintf(
85 552
                'The mapping for the class "%s" is not valid.',
86 414
                $class
87 552
            ), 0, $e);
88
        }
89
90 1808
        $this->doLoadClassMetadata($classMetadata, $data);
91
92 1808
        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 1808
    private function doLoadClassMetadata(ClassMetadataInterface $classMetadata, array $data)
107
    {
108 1808
        $properties = $data['exclusion_policy'] === ExclusionPolicy::NONE ? $classMetadata->getProperties() : [];
109
110 1808
        foreach ($data['properties'] as $property => $value) {
111 1808
            $propertyMetadata = $classMetadata->getProperty($property);
112
113 1808
            if ($propertyMetadata === null) {
114 1808
                $propertyMetadata = new PropertyMetadata($property, $classMetadata->getName());
115
            }
116
117 1808
            $this->loadPropertyMetadata($propertyMetadata, $value);
118
119 1808
            if ($this->isPropertyExposed($value, $data['exclusion_policy'])) {
120 1808
                $properties[$property] = $propertyMetadata;
121
            } else {
122 968
                unset($properties[$property]);
123
            }
124
        }
125
126 1808
        if (isset($data['order'])) {
127 192
            $properties = $this->sortProperties($properties, $data['order']);
128
        }
129
130 1808
        if (isset($data['xml_root'])) {
131 128
            $classMetadata->setXmlRoot($data['xml_root']);
132
        }
133
134 1808
        $classMetadata->setProperties($properties);
135 1808
    }
136
137
    /**
138
     * @param PropertyMetadataInterface $propertyMetadata
139
     * @param mixed                     $data
140
     */
141 1808
    private function loadPropertyMetadata(PropertyMetadataInterface $propertyMetadata, $data)
142
    {
143 1808
        if (isset($data['alias'])) {
144 160
            $propertyMetadata->setAlias($data['alias']);
145
        }
146
147 1808
        if (isset($data['type'])) {
148 1272
            $propertyMetadata->setType($this->typeParser->parse($data['type']));
149
        }
150
151 1808
        if (isset($data['readable'])) {
152 128
            $propertyMetadata->setReadable($data['readable']);
153
        }
154
155 1808
        if (isset($data['writable'])) {
156 128
            $propertyMetadata->setWritable($data['writable']);
157
        }
158
159 1808
        if (isset($data['accessor'])) {
160 64
            $propertyMetadata->setAccessor($data['accessor']);
161
        }
162
163 1808
        if (isset($data['mutator'])) {
164 64
            $propertyMetadata->setMutator($data['mutator']);
165
        }
166
167 1808
        if (isset($data['since'])) {
168 128
            $propertyMetadata->setSinceVersion($data['since']);
169
        }
170
171 1808
        if (isset($data['until'])) {
172 128
            $propertyMetadata->setUntilVersion($data['until']);
173
        }
174
175 1808
        if (isset($data['max_depth'])) {
176 96
            $propertyMetadata->setMaxDepth($data['max_depth']);
177
        }
178
179 1808
        if (isset($data['groups'])) {
180 144
            $propertyMetadata->addGroups($data['groups']);
181
        }
182
183 1808
        if (isset($data['xml_attribute'])) {
184 128
            $propertyMetadata->setXmlAttribute($data['xml_attribute']);
185
        }
186
187 1808
        if (isset($data['xml_value'])) {
188 64
            $propertyMetadata->setXmlValue($data['xml_value']);
189
        }
190
191 1808
        if (isset($data['xml_inline'])) {
192 64
            $propertyMetadata->setXmlInline($data['xml_inline']);
193
194 64
            if (!isset($data['xml_key_as_attribute'])) {
195 64
                $data['xml_key_as_attribute'] = true;
196
            }
197
198 64
            if (!isset($data['xml_key_as_node'])) {
199 64
                $data['xml_key_as_node'] = false;
200
            }
201
        }
202
203 1808
        if (isset($data['xml_entry'])) {
204 64
            $propertyMetadata->setXmlEntry($data['xml_entry']);
205
        }
206
207 1808
        if (isset($data['xml_entry_attribute'])) {
208 64
            $propertyMetadata->setXmlEntryAttribute($data['xml_entry_attribute']);
209
        }
210
211 1808
        if (isset($data['xml_key_as_attribute'])) {
212 64
            $propertyMetadata->setXmlKeyAsAttribute($data['xml_key_as_attribute']);
213
        }
214
215 1808
        if (isset($data['xml_key_as_node'])) {
216 64
            $propertyMetadata->setXmlKeyAsNode($data['xml_key_as_node']);
217
        }
218 1808
    }
219
220
    /**
221
     * @param PropertyMetadataInterface[] $properties
222
     * @param string|string[]             $order
223
     *
224
     * @return PropertyMetadataInterface[]
225
     */
226 192
    private function sortProperties(array $properties, $order)
227
    {
228 192
        if (is_string($order)) {
229 128
            if ($order === 'ASC') {
230 64
                ksort($properties);
231
            } else {
232 128
                krsort($properties);
233
            }
234 64
        } elseif (is_array($order)) {
235 64
            $properties = array_merge(array_flip($order), $properties);
236
        }
237
238 192
        return $properties;
239
    }
240
241
    /**
242
     * @param mixed[] $property
243
     * @param string  $policy
244
     *
245
     * @return bool
246
     */
247 1808
    private function isPropertyExposed($property, $policy)
248
    {
249 1808
        $expose = isset($property['expose']) && $property['expose'];
250 1808
        $exclude = isset($property['exclude']) && $property['exclude'];
251
252 1808
        return ($policy === ExclusionPolicy::ALL && $expose) || ($policy === ExclusionPolicy::NONE && !$exclude);
253
    }
254
255
    /**
256
     * @param OptionsResolver $resolver
257
     */
258 2360
    private function configureClassOptions(OptionsResolver $resolver)
259
    {
260 2360
        $emptyValidator = $this->getEmptyValidator();
261
262
        $resolver
263 2360
            ->setRequired(['properties'])
264 2360
            ->setDefaults(['exclusion_policy' => ExclusionPolicy::NONE])
265 2360
            ->setDefined([
266 2360
                'readable',
267
                'order',
268
                'writable',
269
                'xml_root',
270
            ])
271 2360
            ->setAllowedTypes('order', ['array', 'string'])
272 2360
            ->setAllowedTypes('properties', 'array')
273 2360
            ->setAllowedTypes('readable', 'bool')
274 2360
            ->setAllowedTypes('writable', 'bool')
275 2360
            ->setAllowedTypes('xml_root', 'string')
276 2360
            ->setAllowedValues('exclusion_policy', [ExclusionPolicy::NONE, ExclusionPolicy::ALL])
277 2360
            ->setAllowedValues('order', $emptyValidator)
278 2360
            ->setAllowedValues('xml_root', $emptyValidator)
279 2360
            ->setAllowedValues('properties', function ($properties) {
280 2244
                return count($properties) > 0;
281 2360
            })
282 2360
            ->setNormalizer('order', function (Options $options, $order) use ($emptyValidator) {
283 236
                if (is_string($order)) {
284 156
                    if (strcasecmp($order, 'ASC') === 0 || strcasecmp($order, 'DESC') === 0) {
285 128
                        return strtoupper($order);
286
                    }
287
288 28
                    $order = array_map('trim', explode(',', $order));
289
                }
290
291 108
                if (is_array($order)) {
292 108
                    $properties = $options['properties'];
293
294 108
                    foreach ($order as $property) {
295 108
                        if (!isset($properties[$property])) {
296 44
                            throw new InvalidOptionsException(sprintf(
297 44
                                'The property "%s" defined in the mapping order does not exist.',
298 65
                                $property
299
                            ));
300
                        }
301
                    }
302
                }
303
304 64
                return $order;
305 2360
            })
306 2360
            ->setNormalizer('properties', function (Options $options, array $properties) {
307 2216
                if ($this->propertyResolver === null) {
308 2216
                    $this->configurePropertyOptions($this->propertyResolver = new OptionsResolver());
309
                }
310
311 2216
                $results = [];
312 2216
                $visibilities = ['readable', 'writable'];
313
314 2216
                foreach ($properties as $key => $property) {
315 2216
                    if ($property === null) {
316 160
                        $property = [];
317
                    }
318
319
                    try {
320 2216
                        $results[$key] = $this->propertyResolver->resolve($property);
321 364
                    } catch (\InvalidArgumentException $e) {
322 364
                        throw new \InvalidArgumentException(sprintf(
323 364
                            'The mapping for the property "%s" is not valid.',
324 273
                            $key
325 364
                        ), 0, $e);
326
                    }
327
328 1852
                    foreach ($visibilities as $visibility) {
329 1852
                        if (isset($options[$visibility]) && !isset($results[$key][$visibility])) {
330 990
                            $results[$key][$visibility] = $options[$visibility];
331
                        }
332
                    }
333
                }
334
335 1852
                return $results;
336 2360
            });
337 2360
    }
338
339
    /**
340
     * @param OptionsResolver $resolver
341
     */
342 2216
    private function configurePropertyOptions(OptionsResolver $resolver)
343
    {
344 2216
        $emptyValidator = $this->getEmptyValidator();
345
346
        $resolver
347 2216
            ->setDefined([
348 2216
                'accessor',
349
                'alias',
350
                'exclude',
351
                'expose',
352
                'groups',
353
                'max_depth',
354
                'mutator',
355
                'readable',
356
                'since',
357
                'type',
358
                'until',
359
                'writable',
360
                'xml_attribute',
361
                'xml_entry',
362
                'xml_entry_attribute',
363
                'xml_inline',
364
                'xml_key_as_attribute',
365
                'xml_key_as_node',
366
                'xml_value',
367
            ])
368 2216
            ->setAllowedTypes('accessor', 'string')
369 2216
            ->setAllowedTypes('alias', 'string')
370 2216
            ->setAllowedTypes('exclude', 'bool')
371 2216
            ->setAllowedTypes('expose', 'bool')
372 2216
            ->setAllowedTypes('groups', 'array')
373 2216
            ->setAllowedTypes('max_depth', 'int')
374 2216
            ->setAllowedTypes('mutator', 'string')
375 2216
            ->setAllowedTypes('readable', 'bool')
376 2216
            ->setAllowedTypes('since', 'string')
377 2216
            ->setAllowedTypes('type', 'string')
378 2216
            ->setAllowedTypes('until', 'string')
379 2216
            ->setAllowedTypes('writable', 'bool')
380 2216
            ->setAllowedTypes('xml_attribute', 'bool')
381 2216
            ->setAllowedTypes('xml_entry', 'string')
382 2216
            ->setAllowedTypes('xml_entry_attribute', 'string')
383 2216
            ->setAllowedTypes('xml_inline', 'bool')
384 2216
            ->setAllowedTypes('xml_key_as_attribute', 'bool')
385 2216
            ->setAllowedTypes('xml_key_as_node', 'bool')
386 2216
            ->setAllowedTypes('xml_value', 'bool')
387 2216
            ->setAllowedValues('accessor', $emptyValidator)
388 2216
            ->setAllowedValues('alias', $emptyValidator)
389 2216
            ->setAllowedValues('mutator', $emptyValidator)
390 2216
            ->setAllowedValues('since', $emptyValidator)
391 2216
            ->setAllowedValues('type', $emptyValidator)
392 2216
            ->setAllowedValues('until', $emptyValidator)
393 2216
            ->setAllowedValues('xml_entry', $emptyValidator)
394 2216
            ->setAllowedValues('xml_entry_attribute', $emptyValidator)
395 2216
            ->setAllowedValues('groups', function (array $groups) use ($emptyValidator) {
396 164
                foreach ($groups as $group) {
397 164
                    if (!is_string($group) || !call_user_func($emptyValidator, $group)) {
398 92
                        return false;
399
                    }
400
                }
401
402 144
                return count($groups) > 0;
403 2216
            })
404 2216
            ->setAllowedValues('max_depth', function ($maxDepth) {
405 104
                return $maxDepth >= 0;
406 2216
            });
407 2216
    }
408
409
    /**
410
     * @return \Closure
411
     */
412 2360
    private function getEmptyValidator()
413
    {
414 2360
        if ($this->emptyValidator === null) {
415 1712
            $this->emptyValidator = function ($value) {
416 1712
                return !empty($value);
417
            };
418
        }
419
420
        return $this->emptyValidator;
421
    }
422
}
423