Completed
Pull Request — master (#781)
by
unknown
06:01
created

XmlDriver   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 9

Test Coverage

Coverage 80.6%

Importance

Changes 0
Metric Value
wmc 84
lcom 0
cbo 9
dl 0
loc 326
ccs 187
cts 232
cp 0.806
rs 1.5789
c 0
b 0
f 0

2 Methods

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