Completed
Pull Request — master (#863)
by Omar
15:33
created

XmlDriver   F

Complexity

Total Complexity 85

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 9

Test Coverage

Coverage 80.77%

Importance

Changes 0
Metric Value
wmc 85
lcom 0
cbo 9
dl 0
loc 330
ccs 189
cts 234
cp 0.8077
rs 1.5789
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
F loadMetadataFromFile() 0 322 84
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\Annotation\ExclusionPolicy;
22
use JMS\Serializer\Exception\RuntimeException;
23
use JMS\Serializer\Exception\XmlErrorException;
24
use JMS\Serializer\GraphNavigator;
25
use JMS\Serializer\Metadata\ClassMetadata;
26
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
27
use JMS\Serializer\Metadata\PropertyMetadata;
28
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
29
use Metadata\Driver\AbstractFileDriver;
30
use Metadata\MethodMetadata;
31
32
class XmlDriver extends AbstractFileDriver
33
{
34 28
    protected function loadMetadataFromFile(\ReflectionClass $class, $path)
35
    {
36 28
        $previous = libxml_use_internal_errors(true);
37 28
        libxml_clear_errors();
38
39 28
        $elem = simplexml_load_file($path);
40 28
        libxml_use_internal_errors($previous);
41
42 28
        if (false === $elem) {
43 1
            throw new XmlErrorException(libxml_get_last_error());
44
        }
45
46 27
        $metadata = new ClassMetadata($name = $class->name);
47 27
        if (!$elems = $elem->xpath("./class[@name = '" . $name . "']")) {
48
            throw new RuntimeException(sprintf('Could not find class %s inside XML element.', $name));
49
        }
50 27
        $elem = reset($elems);
51
52 27
        $metadata->fileResources[] = $path;
53 27
        $metadata->fileResources[] = $class->getFileName();
54 27
        $exclusionPolicy = strtoupper($elem->attributes()->{'exclusion-policy'}) ?: 'NONE';
55 27
        $excludeAll = null !== ($exclude = $elem->attributes()->exclude) ? 'true' === strtolower($exclude) : false;
56 27
        $classAccessType = (string)($elem->attributes()->{'access-type'} ?: PropertyMetadata::ACCESS_TYPE_PROPERTY);
57
58 27
        $propertiesMetadata = array();
59 27
        $propertiesNodes = array();
60
61 27
        if (null !== $accessorOrder = $elem->attributes()->{'accessor-order'}) {
62
            $metadata->setAccessorOrder((string)$accessorOrder, preg_split('/\s*,\s*/', (string)$elem->attributes()->{'custom-accessor-order'}));
63
        }
64
65 27
        if (null !== $xmlRootName = $elem->attributes()->{'xml-root-name'}) {
66 7
            $metadata->xmlRootName = (string)$xmlRootName;
67 7
        }
68
69 27
        if (null !== $xmlRootNamespace = $elem->attributes()->{'xml-root-namespace'}) {
70 1
            $metadata->xmlRootNamespace = (string)$xmlRootNamespace;
71 1
        }
72
73 27
        $readOnlyClass = 'true' === strtolower($elem->attributes()->{'read-only'});
74
75 27
        $discriminatorFieldName = (string)$elem->attributes()->{'discriminator-field-name'};
76 27
        $discriminatorMap = array();
77 27
        foreach ($elem->xpath('./discriminator-class') as $entry) {
78 4
            if (!isset($entry->attributes()->value)) {
79
                throw new RuntimeException('Each discriminator-class element must have a "value" attribute.');
80
            }
81
82 4
            $discriminatorMap[(string)$entry->attributes()->value] = (string)$entry;
83 27
        }
84
85 27
        if ('true' === (string)$elem->attributes()->{'discriminator-disabled'}) {
86
            $metadata->discriminatorDisabled = true;
87 27
        } elseif (!empty($discriminatorFieldName) || !empty($discriminatorMap)) {
88
89 4
            $discriminatorGroups = array();
90 4
            foreach ($elem->xpath('./discriminator-groups/group') as $entry) {
91 1
                $discriminatorGroups[] = (string)$entry;
92 4
            }
93 4
            $metadata->setDiscriminator($discriminatorFieldName, $discriminatorMap, $discriminatorGroups);
94 4
        }
95
96 27
        foreach ($elem->xpath('./xml-namespace') as $xmlNamespace) {
97 3
            if (!isset($xmlNamespace->attributes()->uri)) {
98
                throw new RuntimeException('The prefix attribute must be set for all xml-namespace elements.');
99
            }
100
101 3
            if (isset($xmlNamespace->attributes()->prefix)) {
102 3
                $prefix = (string)$xmlNamespace->attributes()->prefix;
103 3
            } else {
104 2
                $prefix = null;
105
            }
106
107 3
            $metadata->registerNamespace((string)$xmlNamespace->attributes()->uri, $prefix);
108 27
        }
109
110 27
        foreach ($elem->xpath('./xml-discriminator') as $xmlDiscriminator) {
111 2
            if (isset($xmlDiscriminator->attributes()->attribute)) {
112 1
                $metadata->xmlDiscriminatorAttribute = (string)$xmlDiscriminator->attributes()->attribute === 'true';
113 1
            }
114 2
            if (isset($xmlDiscriminator->attributes()->cdata)) {
115 1
                $metadata->xmlDiscriminatorCData = (string)$xmlDiscriminator->attributes()->cdata === 'true';
116 1
            }
117 2
            if (isset($xmlDiscriminator->attributes()->namespace)) {
118 1
                $metadata->xmlDiscriminatorNamespace = (string)$xmlDiscriminator->attributes()->namespace;
119 1
            }
120 27
        }
121
122 27
        foreach ($elem->xpath('./virtual-property') as $method) {
123
124 6
            if (isset($method->attributes()->expression)) {
125 2
                $virtualPropertyMetadata = new ExpressionPropertyMetadata($name, (string)$method->attributes()->name, (string)$method->attributes()->expression);
126 2
            } else {
127 5
                if (!isset($method->attributes()->method)) {
128
                    throw new RuntimeException('The method attribute must be set for all virtual-property elements.');
129
                }
130 5
                $virtualPropertyMetadata = new VirtualPropertyMetadata($name, (string)$method->attributes()->method);
131
            }
132
133 6
            $propertiesMetadata[] = $virtualPropertyMetadata;
134 6
            $propertiesNodes[] = $method;
135 27
        }
136
137 27
        if (!$excludeAll) {
138
139 27
            foreach ($class->getProperties() as $property) {
140 23
                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...
141 2
                    continue;
142
                }
143
144 22
                $propertiesMetadata[] = new PropertyMetadata($name, $pName = $property->getName());
145 22
                $pElems = $elem->xpath("./property[@name = '" . $pName . "']");
146
147 22
                $propertiesNodes[] = $pElems ? reset($pElems) : null;
148 27
            }
149
150 27
            foreach ($propertiesMetadata as $propertyKey => $pMetadata) {
151
152 24
                $isExclude = false;
153
                $isExpose = $pMetadata instanceof VirtualPropertyMetadata
154 24
                    || $pMetadata instanceof ExpressionPropertyMetadata;
155
156 24
                $pElem = $propertiesNodes[$propertyKey];
157 24
                if (!empty($pElem)) {
158
159 24
                    if (null !== $exclude = $pElem->attributes()->exclude) {
160 2
                        $isExclude = 'true' === strtolower($exclude);
161 2
                    }
162
163 24
                    if ($isExclude) {
164 2
                        continue;
165
                    }
166
167 22
                    if (null !== $expose = $pElem->attributes()->expose) {
168 2
                        $isExpose = 'true' === strtolower($expose);
169 2
                    }
170
171 22
                    if (null !== $excludeIf = $pElem->attributes()->{'exclude-if'}) {
172 1
                        $pMetadata->excludeIf = $excludeIf;
173 1
                    }
174
175 22
                    if (null !== $skip = $pElem->attributes()->{'skip-when-empty'}) {
176 1
                        $pMetadata->skipWhenEmpty = 'true' === strtolower($skip);
177 1
                    }
178
179 22
                    if (null !== $excludeIf = $pElem->attributes()->{'expose-if'}) {
180 1
                        $pMetadata->excludeIf = "!(" . $excludeIf . ")";
181 1
                        $isExpose = true;
182 1
                    }
183
184 22
                    if (null !== $version = $pElem->attributes()->{'since-version'}) {
185
                        $pMetadata->sinceVersion = (string)$version;
186
                    }
187
188 22
                    if (null !== $version = $pElem->attributes()->{'until-version'}) {
189
                        $pMetadata->untilVersion = (string)$version;
190
                    }
191
192 22
                    if (null !== $serializedName = $pElem->attributes()->{'serialized-name'}) {
193 5
                        $pMetadata->serializedName = (string)$serializedName;
194 5
                    }
195
196 22
                    if (null !== $type = $pElem->attributes()->type) {
197 16
                        $pMetadata->setType((string)$type);
198 22
                    } elseif (isset($pElem->type)) {
199 2
                        $pMetadata->setType((string)$pElem->type);
200 2
                    }
201
202 22
                    if (null !== $groups = $pElem->attributes()->groups) {
203 2
                        $pMetadata->groups = preg_split('/\s*,\s*/', (string) trim($groups));
204 22
                    } elseif (isset($pElem->groups)) {
205 1
                        $pMetadata->groups = (array) $pElem->groups->value;
206 1
                    }
207
208 22
                    if (isset($pElem->{'xml-list'})) {
209
210 2
                        $pMetadata->xmlCollection = true;
211
212 2
                        $colConfig = $pElem->{'xml-list'};
213 2
                        if (isset($colConfig->attributes()->inline)) {
214 1
                            $pMetadata->xmlCollectionInline = 'true' === (string)$colConfig->attributes()->inline;
215 1
                        }
216
217 2
                        if (isset($colConfig->attributes()->{'entry-name'})) {
218 1
                            $pMetadata->xmlEntryName = (string)$colConfig->attributes()->{'entry-name'};
219 1
                        }
220
221 2
                        if (isset($colConfig->attributes()->{'skip-when-empty'})) {
222 1
                            $pMetadata->xmlCollectionSkipWhenEmpty = 'true' === (string)$colConfig->attributes()->{'skip-when-empty'};
223 1
                        } else {
224 1
                            $pMetadata->xmlCollectionSkipWhenEmpty = true;
225
                        }
226
227 2
                        if (isset($colConfig->attributes()->namespace)) {
228
                            $pMetadata->xmlEntryNamespace = (string)$colConfig->attributes()->namespace;
229
                        }
230 2
                    }
231
232 22
                    if (isset($pElem->{'xml-map'})) {
233
                        $pMetadata->xmlCollection = true;
234
235
                        $colConfig = $pElem->{'xml-map'};
236
                        if (isset($colConfig->attributes()->inline)) {
237
                            $pMetadata->xmlCollectionInline = 'true' === (string)$colConfig->attributes()->inline;
238
                        }
239
240
                        if (isset($colConfig->attributes()->{'entry-name'})) {
241
                            $pMetadata->xmlEntryName = (string)$colConfig->attributes()->{'entry-name'};
242
                        }
243
244
                        if (isset($colConfig->attributes()->namespace)) {
245
                            $pMetadata->xmlEntryNamespace = (string)$colConfig->attributes()->namespace;
246
                        }
247
248
                        if (isset($colConfig->attributes()->{'key-attribute-name'})) {
249
                            $pMetadata->xmlKeyAttribute = (string)$colConfig->attributes()->{'key-attribute-name'};
250
                        }
251
                    }
252
253 22
                    if (isset($pElem->{'xml-element'})) {
254 4
                        $colConfig = $pElem->{'xml-element'};
255 4
                        if (isset($colConfig->attributes()->cdata)) {
256 2
                            $pMetadata->xmlElementCData = 'true' === (string)$colConfig->attributes()->cdata;
257 2
                        }
258
259 4
                        if (isset($colConfig->attributes()->namespace)) {
260 3
                            $pMetadata->xmlNamespace = (string)$colConfig->attributes()->namespace;
261 3
                        }
262 4
                    }
263
264 22
                    if (isset($pElem->attributes()->{'xml-attribute'})) {
265 4
                        $pMetadata->xmlAttribute = 'true' === (string)$pElem->attributes()->{'xml-attribute'};
266 4
                    }
267
268 22
                    if (isset($pElem->attributes()->{'xml-attribute-map'})) {
269
                        $pMetadata->xmlAttributeMap = 'true' === (string)$pElem->attributes()->{'xml-attribute-map'};
270
                    }
271
272 22
                    if (isset($pElem->attributes()->{'xml-value'})) {
273 2
                        $pMetadata->xmlValue = 'true' === (string)$pElem->attributes()->{'xml-value'};
274 2
                    }
275
276 22
                    if (isset($pElem->attributes()->{'xml-key-value-pairs'})) {
277 1
                        $pMetadata->xmlKeyValuePairs = 'true' === (string)$pElem->attributes()->{'xml-key-value-pairs'};
278 1
                    }
279
280 22
                    if (isset($pElem->attributes()->{'max-depth'})) {
281 1
                        $pMetadata->maxDepth = (int)$pElem->attributes()->{'max-depth'};
282 1
                    }
283
284
                    //we need read-only before setter and getter set, because that method depends on flag being set
285 22
                    if (null !== $readOnly = $pElem->attributes()->{'read-only'}) {
286 1
                        $pMetadata->readOnly = 'true' === strtolower($readOnly);
287 1
                    } else {
288 21
                        $pMetadata->readOnly = $pMetadata->readOnly || $readOnlyClass;
289
                    }
290
291 22
                    $getter = $pElem->attributes()->{'accessor-getter'};
292 22
                    $setter = $pElem->attributes()->{'accessor-setter'};
293 22
                    $pMetadata->setAccessor(
294 22
                        (string)($pElem->attributes()->{'access-type'} ?: $classAccessType),
295 22
                        $getter ? (string)$getter : null,
296 22
                        $setter ? (string)$setter : null
297 22
                    );
298
299 22
                    if (null !== $inline = $pElem->attributes()->inline) {
300
                        $pMetadata->inline = 'true' === strtolower($inline);
301
                    }
302
303 22
                }
304
305 24
                if ((ExclusionPolicy::NONE === (string)$exclusionPolicy && !$isExclude)
306 6
                    || (ExclusionPolicy::ALL === (string)$exclusionPolicy && $isExpose)
307 24
                ) {
308
309 24
                    $metadata->addPropertyMetadata($pMetadata);
310 24
                }
311 27
            }
312 27
        }
313
314 27
        foreach ($elem->xpath('./callback-method') as $method) {
315 1
            if (!isset($method->attributes()->type)) {
316
                throw new RuntimeException('The type attribute must be set for all callback-method elements.');
317
            }
318 1
            if (!isset($method->attributes()->name)) {
319
                throw new RuntimeException('The name attribute must be set for all callback-method elements.');
320
            }
321
322 1
            switch ((string)$method->attributes()->type) {
323 1
                case 'pre-serialize':
324
                    $metadata->addPreSerializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
325
                    break;
326
327 1
                case 'post-serialize':
328
                    $metadata->addPostSerializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
329
                    break;
330
331 1
                case 'post-deserialize':
332
                    $metadata->addPostDeserializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
333
                    break;
334
335 1
                case 'handler':
336 1
                    if (!isset($method->attributes()->format)) {
337
                        throw new RuntimeException('The format attribute must be set for "handler" callback methods.');
338
                    }
339 1
                    if (!isset($method->attributes()->direction)) {
340
                        throw new RuntimeException('The direction attribute must be set for "handler" callback methods.');
341
                    }
342
343 1
                    $direction = GraphNavigator::parseDirection((string)$method->attributes()->direction);
344 1
                    $format = (string)$method->attributes()->format;
345 1
                    $metadata->addHandlerCallback($direction, $format, (string)$method->attributes()->name);
346
347 1
                    break;
348
349
                default:
350
                    throw new RuntimeException(sprintf('The type "%s" is not supported.', $method->attributes()->name));
351
            }
352 27
        }
353
354 27
        return $metadata;
355
    }
356
357 27
    protected function getExtension()
358
    {
359 27
        return 'xml';
360
    }
361
}
362