YamlDriver   F
last analyzed

Complexity

Total Complexity 104

Size/Duplication

Total Lines 437
Duplicated Lines 0 %

Test Coverage

Coverage 79.21%

Importance

Changes 0
Metric Value
eloc 223
c 0
b 0
f 0
dl 0
loc 437
ccs 141
cts 178
cp 0.7921
rs 2
wmc 104

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getAllClassNames() 0 19 4
A __construct() 0 6 1
A loadMetadataForClass() 0 15 4
A getExtensions() 0 3 1
A getCallbackMetadata() 0 25 5
F addClassProperties() 0 60 19
A getExtension() 0 3 1
F loadMetadataFromFile() 0 259 69

How to fix   Complexity   

Complex Class

Complex classes like YamlDriver 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.

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Metadata\Driver;
6
7
use JMS\Serializer\Annotation\ExclusionPolicy;
8
use JMS\Serializer\Exception\InvalidMetadataException;
9
use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface;
10
use JMS\Serializer\Metadata\ClassMetadata;
11
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
12
use JMS\Serializer\Metadata\PropertyMetadata;
13
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
14
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
15
use JMS\Serializer\Type\Parser;
16
use JMS\Serializer\Type\ParserInterface;
17
use Metadata\ClassMetadata as BaseClassMetadata;
18
use Metadata\Driver\AbstractFileDriver;
19
use Metadata\Driver\AdvancedFileLocatorInterface;
20
use Metadata\Driver\FileLocatorInterface;
21
use Metadata\MethodMetadata;
22
use ReflectionClass;
23
use RuntimeException;
24
use Symfony\Component\Yaml\Yaml;
25
26
class YamlDriver extends AbstractFileDriver
27
{
28
    use ExpressionMetadataTrait;
29 32
30
    /**
31 32
     * @var ParserInterface
32 32
     */
33 32
    private $typeParser;
34 32
    /**
35
     * @var PropertyNamingStrategyInterface
36 32
     */
37
    private $namingStrategy;
38 32
    /**
39
     * @var FileLocatorInterface
40 32
     */
41
    private $locator;
42
43
    public function __construct(FileLocatorInterface $locator, PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null)
44 32
    {
45 32
        $this->locator = $locator;
46 32
        $this->typeParser = $typeParser ?? new Parser();
47 32
        $this->namingStrategy = $namingStrategy;
48 32
        $this->expressionEvaluator = $expressionEvaluator;
49 32
    }
50 32
51 32
    public function loadMetadataForClass(ReflectionClass $class): ?BaseClassMetadata
52 32
    {
53
        $path = null;
54 32
        foreach ($this->getExtensions() as $extension) {
55 32
            $path = $this->locator->findFileForClass($class, $extension);
56 5
            if (null !== $path) {
57 5
                break;
58 2
            }
59 2
        }
60
61
        if (null === $path) {
62 4
            return null;
63
        }
64
65 4
        return $this->loadMetadataFromFile($class, $path);
66
    }
67
68 5
    /**
69
     * {@inheritDoc}
70 5
     */
71 5
    public function getAllClassNames(): array
72
    {
73
        if (!$this->locator instanceof AdvancedFileLocatorInterface) {
74
            throw new RuntimeException(
75 32
                sprintf(
76 32
                    'Locator "%s" must be an instance of "AdvancedFileLocatorInterface".',
77 27
                    get_class($this->locator),
78 4
                ),
79
            );
80
        }
81 26
82 26
        $classes = [];
83
        foreach ($this->getExtensions() as $extension) {
84
            foreach ($this->locator->findAllClasses($extension) as $class) {
85 32
                $classes[$class] = $class;
86 28
            }
87 28
        }
88 27
89 28
        return array_values($classes);
90
    }
91 28
92 25
    protected function loadMetadataFromFile(ReflectionClass $class, string $file): ?BaseClassMetadata
93
    {
94 25
        $config = Yaml::parseFile($file, Yaml::PARSE_CONSTANT);
95 2
96
        if (!isset($config[$name = $class->name])) {
97
            throw new InvalidMetadataException(
98 25
                sprintf('Expected metadata for class %s to be defined in %s.', $class->name, $file),
99 2
            );
100
        }
101
102 23
        $config = $config[$name];
103 3
        $metadata = new ClassMetadata($name);
104
        $metadata->fileResources[] = $file;
105
        $fileResource = $class->getFilename();
106 23
        if (false !== $fileResource) {
107 1
            $metadata->fileResources[] = $fileResource;
108
        }
109
110 23
        $exclusionPolicy = isset($config['exclusion_policy']) ? strtoupper($config['exclusion_policy']) : 'NONE';
111
        $excludeAll = isset($config['exclude']) ? (bool) $config['exclude'] : false;
112
113
        if (isset($config['exclude_if'])) {
114 23
            $metadata->excludeIf = $this->parseExpression((string) $config['exclude_if']);
115
        }
116
117
        $classAccessType = $config['access_type'] ?? PropertyMetadata::ACCESS_TYPE_PROPERTY;
118 23
        $readOnlyClass = isset($config['read_only']) ? (bool) $config['read_only'] : false;
119 1
        $this->addClassProperties($metadata, $config);
120
121
        $propertiesMetadata = [];
122 23
        $propertiesData = [];
123 1
        if (array_key_exists('virtual_properties', $config)) {
124
            foreach ($config['virtual_properties'] as $methodName => $propertySettings) {
125
                if (isset($propertySettings['exp'])) {
126 23
                    $virtualPropertyMetadata = new ExpressionPropertyMetadata(
127 6
                        $name,
128
                        $methodName,
129
                        $this->parseExpression($propertySettings['exp']),
130 23
                    );
131 17
                    unset($propertySettings['exp']);
132
                } else {
133
                    if (!$class->hasMethod($methodName)) {
134 23
                        throw new InvalidMetadataException(
135 1
                            'The method ' . $methodName . ' not found in class ' . $class->name,
136
                        );
137
                    }
138 23
139 2
                    $virtualPropertyMetadata = new VirtualPropertyMetadata($name, $methodName);
140
                }
141 2
142 2
                $propertiesMetadata[] = $virtualPropertyMetadata;
143 2
                $propertiesData[] = $propertySettings;
144
            }
145
        }
146 2
147 1
        if (!$excludeAll) {
148
            foreach ($class->getProperties() as $property) {
149
                if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) {
150 2
                    continue;
151 1
                }
152
153 2
                $pName = $property->getName();
154
                $propertiesMetadata[] = new PropertyMetadata($name, $pName);
155
                $propertiesData[] =  !empty($config['properties']) && true === array_key_exists($pName, $config['properties'])
156 2
                    ? (array) $config['properties'][$pName]
157
                    : null;
158
            }
159
160
            foreach ($propertiesMetadata as $propertyKey => $pMetadata) {
161 23
                $isExclude = false;
162
                $isExpose = $pMetadata instanceof VirtualPropertyMetadata
163
                    || $pMetadata instanceof ExpressionPropertyMetadata
164
                    || isset($propertiesData[$propertyKey]);
165
166
                $pConfig = $propertiesData[$propertyKey];
167
                if (!empty($pConfig)) {
168
                    if (isset($pConfig['exclude'])) {
169
                        $isExclude = (bool) $pConfig['exclude'];
170
                    }
171
172
                    if ($isExclude) {
173
                        continue;
174
                    }
175
176
                    if (isset($pConfig['expose'])) {
177
                        $isExpose = (bool) $pConfig['expose'];
178
                    }
179
180
                    if (isset($pConfig['skip_when_empty'])) {
181
                        $pMetadata->skipWhenEmpty = (bool) $pConfig['skip_when_empty'];
182 23
                    }
183 4
184 4
                    if (isset($pConfig['since_version'])) {
185 2
                        $pMetadata->sinceVersion = (string) $pConfig['since_version'];
186
                    }
187
188 4
                    if (isset($pConfig['until_version'])) {
189 3
                        $pMetadata->untilVersion = (string) $pConfig['until_version'];
190
                    }
191
192
                    if (isset($pConfig['exclude_if'])) {
193 23
                        $pMetadata->excludeIf = $this->parseExpression((string) $pConfig['exclude_if']);
194 4
                    }
195
196
                    if (isset($pConfig['expose_if'])) {
197 23
                        $pMetadata->excludeIf = $this->parseExpression('!(' . $pConfig['expose_if'] . ')');
198
                    }
199
200
                    if (isset($pConfig['serialized_name'])) {
201 23
                        $pMetadata->serializedName = (string) $pConfig['serialized_name'];
202 2
                    }
203
204
                    if (isset($pConfig['type'])) {
205 23
                        $pMetadata->setType($this->typeParser->parse((string) $pConfig['type']));
206 1
                    }
207
208
                    if (isset($pConfig['groups'])) {
209
                        $pMetadata->groups = $pConfig['groups'];
210 23
                    }
211 1
212
                    if (isset($pConfig['xml_list'])) {
213 22
                        $pMetadata->xmlCollection = true;
214
215
                        $colConfig = $pConfig['xml_list'];
216 23
                        if (isset($colConfig['inline'])) {
217 23
                            $pMetadata->xmlCollectionInline = (bool) $colConfig['inline'];
218 23
                        }
219 23
220
                        if (isset($colConfig['entry_name'])) {
221
                            $pMetadata->xmlEntryName = (string) $colConfig['entry_name'];
222 23
                        }
223 2
224
                        if (isset($colConfig['skip_when_empty'])) {
225
                            $pMetadata->xmlCollectionSkipWhenEmpty = (bool) $colConfig['skip_when_empty'];
226 23
                        } else {
227 1
                            $pMetadata->xmlCollectionSkipWhenEmpty = true;
228
                        }
229
230
                        if (isset($colConfig['namespace'])) {
231 28
                            $pMetadata->xmlEntryNamespace = (string) $colConfig['namespace'];
232 28
                        }
233
                    }
234
235 28
                    if (isset($pConfig['xml_map'])) {
236 2
                        $pMetadata->xmlCollection = true;
237 2
238
                        $colConfig = $pConfig['xml_map'];
239
                        if (isset($colConfig['inline'])) {
240 28
                            $pMetadata->xmlCollectionInline = (bool) $colConfig['inline'];
241 23
                        }
242
243 23
                        if (isset($colConfig['entry_name'])) {
244 1
                            $pMetadata->xmlEntryName = (string) $colConfig['entry_name'];
245
                        }
246
247
                        if (isset($colConfig['namespace'])) {
248 28
                            $pMetadata->xmlEntryNamespace = (string) $colConfig['namespace'];
249 28
                        }
250
251 28
                        if (isset($colConfig['key_attribute_name'])) {
252
                            $pMetadata->xmlKeyAttribute = $colConfig['key_attribute_name'];
253
                        }
254
                    }
255
256 32
                    if (isset($pConfig['xml_element'])) {
257
                        $colConfig = $pConfig['xml_element'];
258
                        if (isset($colConfig['cdata'])) {
259
                            $pMetadata->xmlElementCData = (bool) $colConfig['cdata'];
260
                        }
261
262
                        if (isset($colConfig['namespace'])) {
263
                            $pMetadata->xmlNamespace = (string) $colConfig['namespace'];
264
                        }
265
                    }
266
267
                    if (isset($pConfig['xml_attribute'])) {
268
                        $pMetadata->xmlAttribute = (bool) $pConfig['xml_attribute'];
269
                    }
270 32
271
                    if (isset($pConfig['xml_attribute_map'])) {
272
                        $pMetadata->xmlAttributeMap = (bool) $pConfig['xml_attribute_map'];
273 32
                    }
274
275 32
                    if (isset($pConfig['xml_value'])) {
276
                        $pMetadata->xmlValue = (bool) $pConfig['xml_value'];
277
                    }
278 32
279
                    if (isset($pConfig['xml_key_value_pairs'])) {
280 32
                        $pMetadata->xmlKeyValuePairs = (bool) $pConfig['xml_key_value_pairs'];
281 1
                    }
282
283
                    //we need read_only before setter and getter set, because that method depends on flag being set
284 32
                    if (isset($pConfig['read_only'])) {
285 1
                        $pMetadata->readOnly = (bool) $pConfig['read_only'];
286
                    } else {
287
                        $pMetadata->readOnly = $pMetadata->readOnly || $readOnlyClass;
288 32
                    }
289 9
290
                    $pMetadata->setAccessor(
291
                        $pConfig['access_type'] ?? $classAccessType,
292 32
                        $pConfig['accessor']['getter'] ?? null,
293 1
                        $pConfig['accessor']['setter'] ?? null,
294
                    );
295
296 32
                    if (isset($pConfig['inline'])) {
297 1
                        $pMetadata->inline = (bool) $pConfig['inline'];
298
                    }
299
300 32
                    if (isset($pConfig['max_depth'])) {
301
                        $pMetadata->maxDepth = (int) $pConfig['max_depth'];
302 3
                    }
303 3
304
                    if (isset($pConfig['union_discriminator'])) {
305
                        $pMetadata->setType([
306
                            'name' => 'union',
307 32
                            'params' => [null, $pConfig['union_discriminator']['field'], $pConfig['union_discriminator']['map']],
308 5
                        ]);
309
                    }
310
                }
311 5
312
                if (!$pMetadata->serializedName) {
313
                    $pMetadata->serializedName = $this->namingStrategy->translateName($pMetadata);
314
                }
315 5
316
                if ($pMetadata->inline) {
317
                    $metadata->isList = $metadata->isList || PropertyMetadata::isCollectionList($pMetadata->type);
318 5
                    $metadata->isMap = $metadata->isMap || PropertyMetadata::isCollectionMap($pMetadata->type);
319 5
                }
320
321 5
                if (!empty($pConfig) && !empty($pConfig['name'])) {
322 2
                    $pMetadata->name = (string) $pConfig['name'];
323
                }
324 5
325 3
                if (
326 1
                    (ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
327
                    || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)
328 3
                ) {
329 2
                    $metadata->addPropertyMetadata($pMetadata);
330
                }
331
            }
332
        }
333
334 32
        if (isset($config['callback_methods'])) {
335
            $cConfig = $config['callback_methods'];
336
337
            if (isset($cConfig['pre_serialize'])) {
338
                $metadata->preSerializeMethods = $this->getCallbackMetadata($class, $cConfig['pre_serialize']);
339
            }
340
341
            if (isset($cConfig['post_serialize'])) {
342
                $metadata->postSerializeMethods = $this->getCallbackMetadata($class, $cConfig['post_serialize']);
343
            }
344
345
            if (isset($cConfig['post_deserialize'])) {
346
                $metadata->postDeserializeMethods = $this->getCallbackMetadata($class, $cConfig['post_deserialize']);
347
            }
348
        }
349
350
        return $metadata;
351
    }
352
353
    /**
354
     * @return string[]
355
     */
356
    protected function getExtensions(): array
357
    {
358
        return array_unique([$this->getExtension(), 'yaml', 'yml']);
0 ignored issues
show
Deprecated Code introduced by
The function JMS\Serializer\Metadata\...lDriver::getExtension() has been deprecated: use getExtensions instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

358
        return array_unique([/** @scrutinizer ignore-deprecated */ $this->getExtension(), 'yaml', 'yml']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
359
    }
360
361
    /**
362
     * @deprecated use getExtensions instead.
363
     */
364
    protected function getExtension(): string
365
    {
366
        return 'yml';
367
    }
368
369
    private function addClassProperties(ClassMetadata $metadata, array $config): void
370
    {
371
        if (isset($config['custom_accessor_order']) && !isset($config['accessor_order'])) {
372
            $config['accessor_order'] = 'custom';
373
        }
374
375
        if (isset($config['accessor_order'])) {
376
            $metadata->setAccessorOrder($config['accessor_order'], $config['custom_accessor_order'] ?? []);
377
        }
378
379
        if (isset($config['xml_root_name'])) {
380
            $metadata->xmlRootName = (string) $config['xml_root_name'];
381
        }
382
383
        if (isset($config['xml_root_prefix'])) {
384
            $metadata->xmlRootPrefix = (string) $config['xml_root_prefix'];
385
        }
386
387
        if (isset($config['xml_root_namespace'])) {
388
            $metadata->xmlRootNamespace = (string) $config['xml_root_namespace'];
389
        }
390
391
        if (array_key_exists('xml_namespaces', $config)) {
392
            foreach ($config['xml_namespaces'] as $prefix => $uri) {
393
                $metadata->registerNamespace($uri, $prefix);
394
            }
395
        }
396
397
        if (isset($config['discriminator'])) {
398
            if (isset($config['discriminator']['disabled']) && true === $config['discriminator']['disabled']) {
399
                $metadata->discriminatorDisabled = true;
400
            } else {
401
                if (!isset($config['discriminator']['field_name'])) {
402
                    throw new InvalidMetadataException('The "field_name" attribute must be set for discriminators.');
403
                }
404
405
                if (!isset($config['discriminator']['map']) || !is_array($config['discriminator']['map'])) {
406
                    throw new InvalidMetadataException(
407
                        'The "map" attribute must be set, and be an array for discriminators.',
408
                    );
409
                }
410
411
                $groups = $config['discriminator']['groups'] ?? [];
412
                $metadata->setDiscriminator(
413
                    $config['discriminator']['field_name'],
414
                    $config['discriminator']['map'],
415
                    $groups,
416
                );
417
418
                if (isset($config['discriminator']['xml_attribute'])) {
419
                    $metadata->xmlDiscriminatorAttribute = (bool) $config['discriminator']['xml_attribute'];
420
                }
421
422
                if (isset($config['discriminator']['xml_element'])) {
423
                    if (isset($config['discriminator']['xml_element']['cdata'])) {
424
                        $metadata->xmlDiscriminatorCData = (bool) $config['discriminator']['xml_element']['cdata'];
425
                    }
426
427
                    if (isset($config['discriminator']['xml_element']['namespace'])) {
428
                        $metadata->xmlDiscriminatorNamespace = (string) $config['discriminator']['xml_element']['namespace'];
429
                    }
430
                }
431
            }
432
        }
433
    }
434
435
    /**
436
     * @param string|string[] $config
437
     */
438
    private function getCallbackMetadata(ReflectionClass $class, $config): array
439
    {
440
        if (is_string($config)) {
441
            $config = [$config];
442
        } elseif (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
443
            throw new InvalidMetadataException(
444
                sprintf(
445
                    'callback methods expects a string, or an array of strings that represent method names, but got %s.',
446
                    json_encode($config['pre_serialize']),
447
                ),
448
            );
449
        }
450
451
        $methods = [];
452
        foreach ($config as $name) {
453
            if (!$class->hasMethod($name)) {
454
                throw new InvalidMetadataException(
455
                    sprintf('The method %s does not exist in class %s.', $name, $class->name),
456
                );
457
            }
458
459
            $methods[] = new MethodMetadata($class->name, $name);
460
        }
461
462
        return $methods;
463
    }
464
}
465