Completed
Pull Request — master (#708)
by Asmir
11:37
created

XmlDriver   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 315
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 9

Test Coverage

Coverage 79.73%

Importance

Changes 0
Metric Value
wmc 80
lcom 0
cbo 9
dl 0
loc 315
ccs 177
cts 222
cp 0.7973
rs 1.5789
c 0
b 0
f 0

2 Methods

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