Passed
Branch master (bf85d9)
by Johannes
05:40
created

XmlDriver::__construct()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 1
nop 3
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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\Parser\AbstractParser;
22
use JMS\Serializer\Annotation\ExclusionPolicy;
23
use JMS\Serializer\Exception\RuntimeException;
24
use JMS\Serializer\Exception\XmlErrorException;
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 JMS\Serializer\Naming\PropertyNamingStrategyInterface;
30
use JMS\Serializer\TypeParser;
31
use Metadata\Driver\AbstractFileDriver;
32
use Metadata\Driver\FileLocatorInterface;
33
use Metadata\MethodMetadata;
34
35
class XmlDriver extends AbstractFileDriver
36
{
37
    private $typeParser;
38
    /**
39
     * @var PropertyNamingStrategyInterface
40
     */
41
    private $namingStrategy;
42
43 28
    public function __construct(FileLocatorInterface $locator, PropertyNamingStrategyInterface $namingStrategy,  AbstractParser $typeParser = null)
44
    {
45 28
        parent::__construct($locator);
46 28
        $this->typeParser = $typeParser ?: new TypeParser();
47 28
        $this->namingStrategy = $namingStrategy;
48 28
    }
49
50 28
    protected function loadMetadataFromFile(\ReflectionClass $class, $path)
51
    {
52 28
        $previous = libxml_use_internal_errors(true);
53 28
        libxml_clear_errors();
54
55 28
        $elem = simplexml_load_file($path);
56 28
        libxml_use_internal_errors($previous);
57
58 28
        if (false === $elem) {
59 1
            throw new XmlErrorException(libxml_get_last_error());
60
        }
61
62 27
        $metadata = new ClassMetadata($name = $class->name);
63 27
        if (!$elems = $elem->xpath("./class[@name = '" . $name . "']")) {
64
            throw new RuntimeException(sprintf('Could not find class %s inside XML element.', $name));
65
        }
66 27
        $elem = reset($elems);
67
68 27
        $metadata->fileResources[] = $path;
69 27
        $metadata->fileResources[] = $class->getFileName();
70 27
        $exclusionPolicy = strtoupper($elem->attributes()->{'exclusion-policy'}) ?: 'NONE';
71 27
        $excludeAll = null !== ($exclude = $elem->attributes()->exclude) ? 'true' === strtolower($exclude) : false;
72 27
        $classAccessType = (string)($elem->attributes()->{'access-type'} ?: PropertyMetadata::ACCESS_TYPE_PROPERTY);
73
74 27
        $propertiesMetadata = array();
75 27
        $propertiesNodes = array();
76
77 27
        if (null !== $accessorOrder = $elem->attributes()->{'accessor-order'}) {
78
            $metadata->setAccessorOrder((string)$accessorOrder, preg_split('/\s*,\s*/', (string)$elem->attributes()->{'custom-accessor-order'}));
0 ignored issues
show
Bug introduced by
It seems like preg_split('/\s*,\s*/', ...custom-accessor-order') can also be of type false; however, parameter $customOrder of JMS\Serializer\Metadata\...ata::setAccessorOrder() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
            $metadata->setAccessorOrder((string)$accessorOrder, /** @scrutinizer ignore-type */ preg_split('/\s*,\s*/', (string)$elem->attributes()->{'custom-accessor-order'}));
Loading history...
79
        }
80
81 27
        if (null !== $xmlRootName = $elem->attributes()->{'xml-root-name'}) {
82 7
            $metadata->xmlRootName = (string)$xmlRootName;
83
        }
84
85 27
        if (null !== $xmlRootNamespace = $elem->attributes()->{'xml-root-namespace'}) {
86 1
            $metadata->xmlRootNamespace = (string)$xmlRootNamespace;
87
        }
88
89 27
        $readOnlyClass = 'true' === strtolower($elem->attributes()->{'read-only'});
90
91 27
        $discriminatorFieldName = (string)$elem->attributes()->{'discriminator-field-name'};
92 27
        $discriminatorMap = array();
93 27
        foreach ($elem->xpath('./discriminator-class') as $entry) {
94 4
            if (!isset($entry->attributes()->value)) {
95
                throw new RuntimeException('Each discriminator-class element must have a "value" attribute.');
96
            }
97
98 4
            $discriminatorMap[(string)$entry->attributes()->value] = (string)$entry;
99
        }
100
101 27
        if ('true' === (string)$elem->attributes()->{'discriminator-disabled'}) {
102
            $metadata->discriminatorDisabled = true;
103 27
        } elseif (!empty($discriminatorFieldName) || !empty($discriminatorMap)) {
104
105 4
            $discriminatorGroups = array();
106 4
            foreach ($elem->xpath('./discriminator-groups/group') as $entry) {
107 1
                $discriminatorGroups[] = (string)$entry;
108
            }
109 4
            $metadata->setDiscriminator($discriminatorFieldName, $discriminatorMap, $discriminatorGroups);
110
        }
111
112 27
        foreach ($elem->xpath('./xml-namespace') as $xmlNamespace) {
113 3
            if (!isset($xmlNamespace->attributes()->uri)) {
114
                throw new RuntimeException('The prefix attribute must be set for all xml-namespace elements.');
115
            }
116
117 3
            if (isset($xmlNamespace->attributes()->prefix)) {
118 3
                $prefix = (string)$xmlNamespace->attributes()->prefix;
119
            } else {
120 2
                $prefix = null;
121
            }
122
123 3
            $metadata->registerNamespace((string)$xmlNamespace->attributes()->uri, $prefix);
124
        }
125
126 27
        foreach ($elem->xpath('./xml-discriminator') as $xmlDiscriminator) {
127 2
            if (isset($xmlDiscriminator->attributes()->attribute)) {
128 1
                $metadata->xmlDiscriminatorAttribute = (string)$xmlDiscriminator->attributes()->attribute === 'true';
129
            }
130 2
            if (isset($xmlDiscriminator->attributes()->cdata)) {
131 1
                $metadata->xmlDiscriminatorCData = (string)$xmlDiscriminator->attributes()->cdata === 'true';
132
            }
133 2
            if (isset($xmlDiscriminator->attributes()->namespace)) {
134 2
                $metadata->xmlDiscriminatorNamespace = (string)$xmlDiscriminator->attributes()->namespace;
135
            }
136
        }
137
138 27
        foreach ($elem->xpath('./virtual-property') as $method) {
139
140 7
            if (isset($method->attributes()->expression)) {
141 2
                $virtualPropertyMetadata = new ExpressionPropertyMetadata($name, (string)$method->attributes()->name, (string)$method->attributes()->expression);
142
            } else {
143 6
                if (!isset($method->attributes()->method)) {
144
                    throw new RuntimeException('The method attribute must be set for all virtual-property elements.');
145
                }
146 6
                $virtualPropertyMetadata = new VirtualPropertyMetadata($name, (string)$method->attributes()->method);
147
            }
148
149 7
            $propertiesMetadata[] = $virtualPropertyMetadata;
150 7
            $propertiesNodes[] = $method;
151
        }
152
153 27
        if (!$excludeAll) {
154
155 27
            foreach ($class->getProperties() as $property) {
156 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 on ReflectionProperty.
Loading history...
157 2
                    continue;
158
                }
159
160 22
                $propertiesMetadata[] = new PropertyMetadata($name, $pName = $property->getName());
161 22
                $pElems = $elem->xpath("./property[@name = '" . $pName . "']");
162
163 22
                $propertiesNodes[] = $pElems ? reset($pElems) : null;
164
            }
165
166 27
            foreach ($propertiesMetadata as $propertyKey => $pMetadata) {
167
168 24
                $isExclude = false;
169 24
                $isExpose = $pMetadata instanceof VirtualPropertyMetadata
170 24
                    || $pMetadata instanceof ExpressionPropertyMetadata;
171
172 24
                $pElem = $propertiesNodes[$propertyKey];
173 24
                if (!empty($pElem)) {
174
175 24
                    if (null !== $exclude = $pElem->attributes()->exclude) {
176 2
                        $isExclude = 'true' === strtolower($exclude);
177
                    }
178
179 24
                    if ($isExclude) {
180 2
                        continue;
181
                    }
182
183 22
                    if (null !== $expose = $pElem->attributes()->expose) {
184 2
                        $isExpose = 'true' === strtolower($expose);
185
                    }
186
187 22
                    if (null !== $excludeIf = $pElem->attributes()->{'exclude-if'}) {
188 1
                        $pMetadata->excludeIf = $excludeIf;
189
                    }
190
191 22
                    if (null !== $skip = $pElem->attributes()->{'skip-when-empty'}) {
192 1
                        $pMetadata->skipWhenEmpty = 'true' === strtolower($skip);
193
                    }
194
195 22
                    if (null !== $excludeIf = $pElem->attributes()->{'expose-if'}) {
196 1
                        $pMetadata->excludeIf = "!(" . $excludeIf . ")";
197 1
                        $isExpose = true;
198
                    }
199
200 22
                    if (null !== $version = $pElem->attributes()->{'since-version'}) {
201
                        $pMetadata->sinceVersion = (string)$version;
202
                    }
203
204 22
                    if (null !== $version = $pElem->attributes()->{'until-version'}) {
205
                        $pMetadata->untilVersion = (string)$version;
206
                    }
207
208 22
                    if (null !== $serializedName = $pElem->attributes()->{'serialized-name'}) {
209 6
                        $pMetadata->serializedName = (string)$serializedName;
210
                    }
211
212 22
                    if (null !== $type = $pElem->attributes()->type) {
213 15
                        $pMetadata->setType($this->typeParser->parse((string)$type));
214 10
                    } elseif (isset($pElem->type)) {
215 2
                        $pMetadata->setType($this->typeParser->parse((string)$pElem->type));
216
                    }
217
218 22
                    if (null !== $groups = $pElem->attributes()->groups) {
219 2
                        $pMetadata->groups = preg_split('/\s*,\s*/', (string) trim($groups));
220 21
                    } elseif (isset($pElem->groups)) {
221 1
                        $pMetadata->groups = (array) $pElem->groups->value;
222
                    }
223
224 22
                    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 22
                    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 22
                    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 22
                    if (isset($pElem->attributes()->{'xml-attribute'})) {
281 4
                        $pMetadata->xmlAttribute = 'true' === (string)$pElem->attributes()->{'xml-attribute'};
282
                    }
283
284 22
                    if (isset($pElem->attributes()->{'xml-attribute-map'})) {
285
                        $pMetadata->xmlAttributeMap = 'true' === (string)$pElem->attributes()->{'xml-attribute-map'};
286
                    }
287
288 22
                    if (isset($pElem->attributes()->{'xml-value'})) {
289 2
                        $pMetadata->xmlValue = 'true' === (string)$pElem->attributes()->{'xml-value'};
290
                    }
291
292 22
                    if (isset($pElem->attributes()->{'xml-key-value-pairs'})) {
293 1
                        $pMetadata->xmlKeyValuePairs = 'true' === (string)$pElem->attributes()->{'xml-key-value-pairs'};
294
                    }
295
296 22
                    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 22
                    if (null !== $readOnly = $pElem->attributes()->{'read-only'}) {
302 1
                        $pMetadata->readOnly = 'true' === strtolower($readOnly);
303
                    } else {
304 21
                        $pMetadata->readOnly = $pMetadata->readOnly || $readOnlyClass;
305
                    }
306
307 22
                    $getter = $pElem->attributes()->{'accessor-getter'};
308 22
                    $setter = $pElem->attributes()->{'accessor-setter'};
309 22
                    $pMetadata->setAccessor(
310 22
                        (string)($pElem->attributes()->{'access-type'} ?: $classAccessType),
311 22
                        $getter ? (string)$getter : null,
312 22
                        $setter ? (string)$setter : null
313
                    );
314
315 22
                    if (null !== $inline = $pElem->attributes()->inline) {
316
                        $pMetadata->inline = 'true' === strtolower($inline);
317
                    }
318
                }
319
320 24
                if (!$pMetadata->serializedName) {
321 24
                    $pMetadata->serializedName = $this->namingStrategy->translateName($pMetadata);
322
                }
323
324 24
                if (!empty($pElem) && null !== $name = $pElem->attributes()->name) {
325 19
                    $pMetadata->name = (string)$name;
326
                }
327
328 24
                if ((ExclusionPolicy::NONE === (string)$exclusionPolicy && !$isExclude)
329 24
                    || (ExclusionPolicy::ALL === (string)$exclusionPolicy && $isExpose)
330
                ) {
331
332
333 24
                    $metadata->addPropertyMetadata($pMetadata);
334
                }
335
            }
336
        }
337
338 27
        foreach ($elem->xpath('./callback-method') as $method) {
339
            if (!isset($method->attributes()->type)) {
340
                throw new RuntimeException('The type attribute must be set for all callback-method elements.');
341
            }
342
            if (!isset($method->attributes()->name)) {
343
                throw new RuntimeException('The name attribute must be set for all callback-method elements.');
344
            }
345
346
            switch ((string)$method->attributes()->type) {
347
                case 'pre-serialize':
348
                    $metadata->addPreSerializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
349
                    break;
350
351
                case 'post-serialize':
352
                    $metadata->addPostSerializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
353
                    break;
354
355
                case 'post-deserialize':
356
                    $metadata->addPostDeserializeMethod(new MethodMetadata($name, (string)$method->attributes()->name));
357
                    break;
358
359
                case 'handler':
360
                    if (!isset($method->attributes()->format)) {
361
                        throw new RuntimeException('The format attribute must be set for "handler" callback methods.');
362
                    }
363
                    if (!isset($method->attributes()->direction)) {
364
                        throw new RuntimeException('The direction attribute must be set for "handler" callback methods.');
365
                    }
366
367
                    break;
368
369
                default:
370
                    throw new RuntimeException(sprintf('The type "%s" is not supported.', $method->attributes()->name));
371
            }
372
        }
373
374 27
        return $metadata;
375
    }
376
377 27
    protected function getExtension()
378
    {
379 27
        return 'xml';
380
    }
381
}
382