Completed
Pull Request — master (#888)
by Robert
03:06
created

XmlDriver   D

Complexity

Total Complexity 85

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 83.51%

Importance

Changes 0
Metric Value
wmc 85
lcom 1
cbo 11
dl 0
loc 345
ccs 157
cts 188
cp 0.8351
rs 4.5142
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
F loadMetadataFromFile() 0 326 82
A getExtension() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/*
4
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer\Metadata\Driver;
20
21
use JMS\Serializer\Accessor\Updater\ClassAccessorUpdater;
22
use JMS\Serializer\Annotation\ExclusionPolicy;
23
use JMS\Serializer\Exception\RuntimeException;
24
use JMS\Serializer\Exception\XmlErrorException;
25
use JMS\Serializer\GraphNavigator;
26
use JMS\Serializer\Metadata\ClassMetadata;
27
use JMS\Serializer\Metadata\ClassMetadataUpdaterInterface;
28
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
29
use JMS\Serializer\Metadata\PropertyMetadata;
30
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
31
use Metadata\Driver\AbstractFileDriver;
32
use Metadata\Driver\FileLocatorInterface;
33
use Metadata\MethodMetadata;
34
35
class XmlDriver extends AbstractFileDriver
36
{
37
    /**
38
     * @var ClassMetadataUpdaterInterface
39
     */
40
    private $classMetadataUpdater;
41
42 30
    public function __construct(FileLocatorInterface $locator, ClassMetadataUpdaterInterface $classMetadataUpdater = null)
43
    {
44 30
        parent::__construct($locator);
45 30
        $this->classMetadataUpdater = $classMetadataUpdater ?: new ClassAccessorUpdater();
46 30
    }
47
48 30
    protected function loadMetadataFromFile(\ReflectionClass $class, $path)
49
    {
50 30
        $previous = libxml_use_internal_errors(true);
51 30
        libxml_clear_errors();
52
53 30
        $elem = simplexml_load_file($path);
54 30
        libxml_use_internal_errors($previous);
55
56 30
        if (false === $elem) {
57 1
            throw new XmlErrorException(libxml_get_last_error());
58
        }
59
60 29
        $metadata = new ClassMetadata($name = $class->name);
61 29
        if (!$elems = $elem->xpath("./class[@name = '" . $name . "']")) {
62
            throw new RuntimeException(sprintf('Could not find class %s inside XML element.', $name));
63
        }
64 29
        $elem = reset($elems);
65
66 29
        $metadata->fileResources[] = $path;
67 29
        $metadata->fileResources[] = $class->getFileName();
68 29
        $exclusionPolicy = strtoupper($elem->attributes()->{'exclusion-policy'}) ?: 'NONE';
69 29
        $excludeAll = null !== ($exclude = $elem->attributes()->exclude) ? 'true' === strtolower($exclude) : false;
70 29
        $metadata->accessType = (string) $elem->attributes()->{'access-type'};
71 29
        $metadata->accessTypeNaming = (string) $elem->attributes()->{'access-type-naming'};
72
73 29
        $propertiesMetadata = array();
74 29
        $propertiesNodes = array();
75
76 29
        if (null !== $accessorOrder = $elem->attributes()->{'accessor-order'}) {
77
            $metadata->setAccessorOrder((string)$accessorOrder, preg_split('/\s*,\s*/', (string)$elem->attributes()->{'custom-accessor-order'}));
78
        }
79
80 29
        if (null !== $xmlRootName = $elem->attributes()->{'xml-root-name'}) {
81 7
            $metadata->xmlRootName = (string)$xmlRootName;
82
        }
83
84 29
        if (null !== $xmlRootNamespace = $elem->attributes()->{'xml-root-namespace'}) {
85 1
            $metadata->xmlRootNamespace = (string)$xmlRootNamespace;
86
        }
87
88 29
        $readOnlyClass = 'true' === strtolower($elem->attributes()->{'read-only'});
89
90 29
        $discriminatorFieldName = (string)$elem->attributes()->{'discriminator-field-name'};
91 29
        $discriminatorMap = array();
92 29
        foreach ($elem->xpath('./discriminator-class') as $entry) {
93 4
            if (!isset($entry->attributes()->value)) {
94
                throw new RuntimeException('Each discriminator-class element must have a "value" attribute.');
95
            }
96
97 4
            $discriminatorMap[(string)$entry->attributes()->value] = (string)$entry;
98
        }
99
100 29
        if ('true' === (string)$elem->attributes()->{'discriminator-disabled'}) {
101
            $metadata->discriminatorDisabled = true;
102 29
        } elseif (!empty($discriminatorFieldName) || !empty($discriminatorMap)) {
103
104 4
            $discriminatorGroups = array();
105 4
            foreach ($elem->xpath('./discriminator-groups/group') as $entry) {
106 1
                $discriminatorGroups[] = (string)$entry;
107
            }
108 4
            $metadata->setDiscriminator($discriminatorFieldName, $discriminatorMap, $discriminatorGroups);
109
        }
110
111 29
        foreach ($elem->xpath('./xml-namespace') as $xmlNamespace) {
112 3
            if (!isset($xmlNamespace->attributes()->uri)) {
113
                throw new RuntimeException('The prefix attribute must be set for all xml-namespace elements.');
114
            }
115
116 3
            if (isset($xmlNamespace->attributes()->prefix)) {
117 3
                $prefix = (string)$xmlNamespace->attributes()->prefix;
118
            } else {
119 2
                $prefix = null;
120
            }
121
122 3
            $metadata->registerNamespace((string)$xmlNamespace->attributes()->uri, $prefix);
123
        }
124
125 29
        foreach ($elem->xpath('./xml-discriminator') as $xmlDiscriminator) {
126 2
            if (isset($xmlDiscriminator->attributes()->attribute)) {
127 1
                $metadata->xmlDiscriminatorAttribute = (string)$xmlDiscriminator->attributes()->attribute === 'true';
128
            }
129 2
            if (isset($xmlDiscriminator->attributes()->cdata)) {
130 1
                $metadata->xmlDiscriminatorCData = (string)$xmlDiscriminator->attributes()->cdata === 'true';
131
            }
132 2
            if (isset($xmlDiscriminator->attributes()->namespace)) {
133 2
                $metadata->xmlDiscriminatorNamespace = (string)$xmlDiscriminator->attributes()->namespace;
134
            }
135
        }
136
137 29
        foreach ($elem->xpath('./virtual-property') as $method) {
138
139 6
            if (isset($method->attributes()->expression)) {
140 2
                $virtualPropertyMetadata = new ExpressionPropertyMetadata($name, (string)$method->attributes()->name, (string)$method->attributes()->expression);
141
            } else {
142 5
                if (!isset($method->attributes()->method)) {
143
                    throw new RuntimeException('The method attribute must be set for all virtual-property elements.');
144
                }
145 5
                $virtualPropertyMetadata = new VirtualPropertyMetadata($name, (string)$method->attributes()->method);
146
            }
147
148 6
            $propertiesMetadata[] = $virtualPropertyMetadata;
149 6
            $propertiesNodes[] = $method;
150
        }
151
152 29
        if (!$excludeAll) {
153
154 29
            foreach ($class->getProperties() as $property) {
155 25
                if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) {
0 ignored issues
show
Bug introduced by
The property info does not seem to exist in ReflectionProperty.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
156 2
                    continue;
157
                }
158
159 24
                $propertiesMetadata[] = new PropertyMetadata($name, $pName = $property->getName());
160 24
                $pElems = $elem->xpath("./property[@name = '" . $pName . "']");
161
162 24
                $propertiesNodes[] = $pElems ? reset($pElems) : null;
163
            }
164
165
            /** @var PropertyMetadata $pMetadata */
166 29
            foreach ($propertiesMetadata as $propertyKey => $pMetadata) {
167
168 26
                $isExclude = false;
169 26
                $isExpose = $pMetadata instanceof VirtualPropertyMetadata
170 26
                    || $pMetadata instanceof ExpressionPropertyMetadata;
171
172 26
                $pElem = $propertiesNodes[$propertyKey];
173 26
                if (!empty($pElem)) {
174
175 26
                    if (null !== $exclude = $pElem->attributes()->exclude) {
176 4
                        $isExclude = 'true' === strtolower($exclude);
177
                    }
178
179 26
                    if ($isExclude) {
180 4
                        continue;
181
                    }
182
183 25
                    if (null !== $expose = $pElem->attributes()->expose) {
184 2
                        $isExpose = 'true' === strtolower($expose);
185
                    }
186
187 25
                    if (null !== $excludeIf = $pElem->attributes()->{'exclude-if'}) {
188 2
                        $pMetadata->excludeIf = $excludeIf;
189
                    }
190
191 25
                    if (null !== $skip = $pElem->attributes()->{'skip-when-empty'}) {
192 1
                        $pMetadata->skipWhenEmpty = 'true' === strtolower($skip);
193
                    }
194
195 25
                    if (null !== $excludeIf = $pElem->attributes()->{'expose-if'}) {
196 2
                        $pMetadata->excludeIf = "!(" . $excludeIf . ")";
197 2
                        $isExpose = true;
198
                    }
199
200 25
                    if (null !== $version = $pElem->attributes()->{'since-version'}) {
201
                        $pMetadata->sinceVersion = (string)$version;
202
                    }
203
204 25
                    if (null !== $version = $pElem->attributes()->{'until-version'}) {
205
                        $pMetadata->untilVersion = (string)$version;
206
                    }
207
208 25
                    if (null !== $serializedName = $pElem->attributes()->{'serialized-name'}) {
209 5
                        $pMetadata->serializedName = (string)$serializedName;
210
                    }
211
212 25
                    if (null !== $type = $pElem->attributes()->type) {
213 18
                        $pMetadata->setType((string)$type);
214 12
                    } elseif (isset($pElem->type)) {
215 2
                        $pMetadata->setType((string)$pElem->type);
216
                    }
217
218 25
                    if (null !== $groups = $pElem->attributes()->groups) {
219 2
                        $pMetadata->groups = preg_split('/\s*,\s*/', (string) trim($groups));
220 24
                    } elseif (isset($pElem->groups)) {
221 1
                        $pMetadata->groups = (array) $pElem->groups->value;
222
                    }
223
224 25
                    if (isset($pElem->{'xml-list'})) {
225
226 2
                        $pMetadata->xmlCollection = true;
227
228 2
                        $colConfig = $pElem->{'xml-list'};
229 2
                        if (isset($colConfig->attributes()->inline)) {
230 1
                            $pMetadata->xmlCollectionInline = 'true' === (string)$colConfig->attributes()->inline;
231
                        }
232
233 2
                        if (isset($colConfig->attributes()->{'entry-name'})) {
234 1
                            $pMetadata->xmlEntryName = (string)$colConfig->attributes()->{'entry-name'};
235
                        }
236
237 2
                        if (isset($colConfig->attributes()->{'skip-when-empty'})) {
238 1
                            $pMetadata->xmlCollectionSkipWhenEmpty = 'true' === (string)$colConfig->attributes()->{'skip-when-empty'};
239
                        } else {
240 1
                            $pMetadata->xmlCollectionSkipWhenEmpty = true;
241
                        }
242
243 2
                        if (isset($colConfig->attributes()->namespace)) {
244
                            $pMetadata->xmlEntryNamespace = (string)$colConfig->attributes()->namespace;
245
                        }
246
                    }
247
248 25
                    if (isset($pElem->{'xml-map'})) {
249
                        $pMetadata->xmlCollection = true;
250
251
                        $colConfig = $pElem->{'xml-map'};
252
                        if (isset($colConfig->attributes()->inline)) {
253
                            $pMetadata->xmlCollectionInline = 'true' === (string)$colConfig->attributes()->inline;
254
                        }
255
256
                        if (isset($colConfig->attributes()->{'entry-name'})) {
257
                            $pMetadata->xmlEntryName = (string)$colConfig->attributes()->{'entry-name'};
258
                        }
259
260
                        if (isset($colConfig->attributes()->namespace)) {
261
                            $pMetadata->xmlEntryNamespace = (string)$colConfig->attributes()->namespace;
262
                        }
263
264
                        if (isset($colConfig->attributes()->{'key-attribute-name'})) {
265
                            $pMetadata->xmlKeyAttribute = (string)$colConfig->attributes()->{'key-attribute-name'};
266
                        }
267
                    }
268
269 25
                    if (isset($pElem->{'xml-element'})) {
270 4
                        $colConfig = $pElem->{'xml-element'};
271 4
                        if (isset($colConfig->attributes()->cdata)) {
272 2
                            $pMetadata->xmlElementCData = 'true' === (string)$colConfig->attributes()->cdata;
273
                        }
274
275 4
                        if (isset($colConfig->attributes()->namespace)) {
276 3
                            $pMetadata->xmlNamespace = (string)$colConfig->attributes()->namespace;
277
                        }
278
                    }
279
280 25
                    if (isset($pElem->attributes()->{'xml-attribute'})) {
281 4
                        $pMetadata->xmlAttribute = 'true' === (string)$pElem->attributes()->{'xml-attribute'};
282
                    }
283
284 25
                    if (isset($pElem->attributes()->{'xml-attribute-map'})) {
285
                        $pMetadata->xmlAttributeMap = 'true' === (string)$pElem->attributes()->{'xml-attribute-map'};
286
                    }
287
288 25
                    if (isset($pElem->attributes()->{'xml-value'})) {
289 2
                        $pMetadata->xmlValue = 'true' === (string)$pElem->attributes()->{'xml-value'};
290
                    }
291
292 25
                    if (isset($pElem->attributes()->{'xml-key-value-pairs'})) {
293 1
                        $pMetadata->xmlKeyValuePairs = 'true' === (string)$pElem->attributes()->{'xml-key-value-pairs'};
294
                    }
295
296 25
                    if (isset($pElem->attributes()->{'max-depth'})) {
297 1
                        $pMetadata->maxDepth = (int)$pElem->attributes()->{'max-depth'};
298
                    }
299
300
                    //we need read-only before setter and getter set, because that method depends on flag being set
301 25
                    if (null !== $readOnly = $pElem->attributes()->{'read-only'}) {
302 4
                        $pMetadata->readOnly = 'true' === strtolower($readOnly);
303
                    } else {
304 23
                        $pMetadata->readOnly = $pMetadata->readOnly || $readOnlyClass;
305
                    }
306
307 25
                    $getter = $pElem->attributes()->{'accessor-getter'};
308 25
                    $setter = $pElem->attributes()->{'accessor-setter'};
309 25
                    $pMetadata->setAccessor(
310 25
                        (string) $pElem->attributes()->{'access-type'},
311 25
                        $getter ? (string)$getter : null,
312 25
                        $setter ? (string) $setter : null,
313 25
                        (string) $pElem->attributes()->{'access-type-naming'}
314
                    );
315
316 25
                    if (null !== $inline = $pElem->attributes()->inline) {
317
                        $pMetadata->inline = 'true' === strtolower($inline);
318
                    }
319
320
                }
321
322 26
                if ((ExclusionPolicy::NONE === (string)$exclusionPolicy && !$isExclude)
323 26
                    || (ExclusionPolicy::ALL === (string)$exclusionPolicy && $isExpose)
324
                ) {
325 26
                    $metadata->addPropertyMetadata($pMetadata);
326
                }
327
            }
328
        }
329
330 29
        foreach ($elem->xpath('./callback-method') as $method) {
331 1
            if (!isset($method->attributes()->type)) {
332
                throw new RuntimeException('The type attribute must be set for all callback-method elements.');
333
            }
334 1
            if (!isset($method->attributes()->name)) {
335
                throw new RuntimeException('The name attribute must be set for all callback-method elements.');
336
            }
337
338 1
            switch ((string)$method->attributes()->type) {
339 1
                case 'pre-serialize':
340
                    $metadata->addPreSerializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
341
                    break;
342
343 1
                case 'post-serialize':
344
                    $metadata->addPostSerializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
345
                    break;
346
347 1
                case 'post-deserialize':
348
                    $metadata->addPostDeserializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
349
                    break;
350
351 1
                case 'handler':
352 1
                    if (!isset($method->attributes()->format)) {
353
                        throw new RuntimeException('The format attribute must be set for "handler" callback methods.');
354
                    }
355 1
                    if (!isset($method->attributes()->direction)) {
356
                        throw new RuntimeException('The direction attribute must be set for "handler" callback methods.');
357
                    }
358
359 1
                    $direction = GraphNavigator::parseDirection((string)$method->attributes()->direction);
360 1
                    $format = (string)$method->attributes()->format;
361 1
                    $metadata->addHandlerCallback($direction, $format, (string)$method->attributes()->name);
362
363 1
                    break;
364
365
                default:
366 1
                    throw new RuntimeException(sprintf('The type "%s" is not supported.', $method->attributes()->name));
367
            }
368
        }
369
370 29
        $this->classMetadataUpdater->update($metadata);
371
372 28
        return $metadata;
373
    }
374
375 29
    protected function getExtension()
376
    {
377 29
        return 'xml';
378
    }
379
}
380