Completed
Push — master ( 0d7a9a...ac7eed )
by Asmir
07:43
created

XmlDriver   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 9

Test Coverage

Coverage 80.35%

Importance

Changes 0
Metric Value
wmc 83
lcom 0
cbo 9
dl 0
loc 323
c 0
b 0
f 0
ccs 184
cts 229
cp 0.8035
rs 1.5789

2 Methods

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