Completed
Push — master ( ecf063...c6000a )
by Andreas
19:02 queued 14:24
created

XmlDriver::formatErrors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping\Driver;
6
7
use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
8
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
9
use Doctrine\ODM\MongoDB\Mapping\MappingException;
10
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
11
use DOMDocument;
12
use LibXMLError;
13
use function array_keys;
14
use function array_map;
15
use function constant;
16
use function count;
17
use function current;
18
use function explode;
19
use function implode;
20
use function in_array;
21
use function is_numeric;
22
use function iterator_to_array;
23
use function libxml_clear_errors;
24
use function libxml_get_errors;
25
use function libxml_use_internal_errors;
26
use function next;
27
use function preg_match;
28
use function simplexml_load_file;
29
use function sprintf;
30
use function strtoupper;
31
use function trim;
32
33
/**
34
 * XmlDriver is a metadata driver that enables mapping through XML files.
35
 *
36
 */
37
class XmlDriver extends FileDriver
38
{
39
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
40
41
    /**
42
     * {@inheritDoc}
43
     */
44 14
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
45
    {
46 14
        parent::__construct($locator, $fileExtension);
47 14
    }
48
49
    /**
50
     * {@inheritDoc}
51
     */
52 8
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class)
53
    {
54
        /** @var ClassMetadata $class */
55
        /** @var \SimpleXMLElement $xmlRoot */
56 8
        $xmlRoot = $this->getElement($className);
57 6
        if (! $xmlRoot) {
58
            return;
59
        }
60
61 6
        if ($xmlRoot->getName() === 'document') {
62 6
            if (isset($xmlRoot['repository-class'])) {
63 6
                $class->setCustomRepositoryClass((string) $xmlRoot['repository-class']);
64
            }
65 2
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
66 1
            $class->setCustomRepositoryClass(
67 1
                isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null
68
            );
69 1
            $class->isMappedSuperclass = true;
70 1
        } elseif ($xmlRoot->getName() === 'embedded-document') {
71 1
            $class->isEmbeddedDocument = true;
72 1
        } elseif ($xmlRoot->getName() === 'query-result-document') {
73 1
            $class->isQueryResultDocument = true;
74
        }
75 6
        if (isset($xmlRoot['db'])) {
76 3
            $class->setDatabase((string) $xmlRoot['db']);
77
        }
78 6
        if (isset($xmlRoot['collection'])) {
79 5
            if (isset($xmlRoot['capped-collection'])) {
80
                $config = ['name' => (string) $xmlRoot['collection']];
81
                $config['capped'] = (bool) $xmlRoot['capped-collection'];
82
                if (isset($xmlRoot['capped-collection-max'])) {
83
                    $config['max'] = (int) $xmlRoot['capped-collection-max'];
84
                }
85
                if (isset($xmlRoot['capped-collection-size'])) {
86
                    $config['size'] = (int) $xmlRoot['capped-collection-size'];
87
                }
88
                $class->setCollection($config);
89
            } else {
90 5
                $class->setCollection((string) $xmlRoot['collection']);
91
            }
92
        }
93 6
        if (isset($xmlRoot['writeConcern'])) {
94
            $class->setWriteConcern((string) $xmlRoot['writeConcern']);
95
        }
96 6
        if (isset($xmlRoot['inheritance-type'])) {
97
            $inheritanceType = (string) $xmlRoot['inheritance-type'];
98
            $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $inheritanceType));
99
        }
100 6
        if (isset($xmlRoot['change-tracking-policy'])) {
101 1
            $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . strtoupper((string) $xmlRoot['change-tracking-policy'])));
102
        }
103 6
        if (isset($xmlRoot->{'discriminator-field'})) {
104
            $discrField = $xmlRoot->{'discriminator-field'};
105
            /* XSD only allows for "name", which is consistent with association
106
             * configurations, but fall back to "fieldName" for BC.
107
             */
108
            $class->setDiscriminatorField(
109
                (string) ($discrField['name'] ?? $discrField['fieldName'])
110
            );
111
        }
112 6
        if (isset($xmlRoot->{'discriminator-map'})) {
113
            $map = [];
114
            foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
115
                $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
116
            }
117
            $class->setDiscriminatorMap($map);
118
        }
119 6
        if (isset($xmlRoot->{'default-discriminator-value'})) {
120
            $class->setDefaultDiscriminatorValue((string) $xmlRoot->{'default-discriminator-value'}['value']);
121
        }
122 6
        if (isset($xmlRoot->{'indexes'})) {
123 1
            foreach ($xmlRoot->{'indexes'}->{'index'} as $index) {
124 1
                $this->addIndex($class, $index);
125
            }
126
        }
127 6
        if (isset($xmlRoot->{'shard-key'})) {
128
            $this->setShardKey($class, $xmlRoot->{'shard-key'}[0]);
129
        }
130 6
        if (isset($xmlRoot['read-only']) && (string) $xmlRoot['read-only'] === 'true') {
131
            $class->markReadOnly();
132
        }
133 6
        if (isset($xmlRoot->{'read-preference'})) {
134
            $class->setReadPreference(...$this->transformReadPreference($xmlRoot->{'read-preference'}));
135
        }
136
137 6
        if (isset($xmlRoot->id)) {
138 6
            $field = $xmlRoot->id;
0 ignored issues
show
Bug introduced by
The property id does not seem to exist in SimpleXMLElement.

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...
139
            $mapping = [
140 6
                'id' => true,
141
                'fieldName' => 'id',
142
            ];
143
144 6
            $attributes = $field->attributes();
145 6
            foreach ($attributes as $key => $value) {
146 2
                $mapping[$key] = (string) $value;
147
            }
148
149 6
            if (isset($mapping['strategy'])) {
150 2
                $mapping['options'] = [];
151 2
                if (isset($field->{'generator-option'})) {
152 1
                    foreach ($field->{'generator-option'} as $generatorOptions) {
153 1
                        $attributesGenerator = iterator_to_array($generatorOptions->attributes());
154 1
                        if (! isset($attributesGenerator['name']) || ! isset($attributesGenerator['value'])) {
155
                            continue;
156
                        }
157
158 1
                        $mapping['options'][(string) $attributesGenerator['name']] = (string) $attributesGenerator['value'];
159
                    }
160
                }
161
            }
162
163 6
            $this->addFieldMapping($class, $mapping);
164
        }
165
166 6
        if (isset($xmlRoot->field)) {
167 2
            foreach ($xmlRoot->field as $field) {
168 2
                $mapping = [];
169 2
                $attributes = $field->attributes();
170 2
                foreach ($attributes as $key => $value) {
171 2
                    $mapping[$key] = (string) $value;
172 2
                    $booleanAttributes = ['reference', 'embed', 'unique', 'sparse'];
173 2
                    if (! in_array($key, $booleanAttributes)) {
174 2
                        continue;
175
                    }
176
177 1
                    $mapping[$key] = ($mapping[$key] === 'true');
178
                }
179
180 2
                if (isset($attributes['not-saved'])) {
181
                    $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
182
                }
183
184 2
                if (isset($attributes['also-load'])) {
185
                    $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
186 2
                } elseif (isset($attributes['version'])) {
187
                    $mapping['version'] = ((string) $attributes['version'] === 'true');
188 2
                } elseif (isset($attributes['lock'])) {
189
                    $mapping['lock'] = ((string) $attributes['lock'] === 'true');
190
                }
191
192 2
                $this->addFieldMapping($class, $mapping);
193
            }
194
        }
195 6
        if (isset($xmlRoot->{'embed-one'})) {
196 1
            foreach ($xmlRoot->{'embed-one'} as $embed) {
197 1
                $this->addEmbedMapping($class, $embed, 'one');
198
            }
199
        }
200 6
        if (isset($xmlRoot->{'embed-many'})) {
201 1
            foreach ($xmlRoot->{'embed-many'} as $embed) {
202 1
                $this->addEmbedMapping($class, $embed, 'many');
203
            }
204
        }
205 6
        if (isset($xmlRoot->{'reference-many'})) {
206 3
            foreach ($xmlRoot->{'reference-many'} as $reference) {
207 3
                $this->addReferenceMapping($class, $reference, 'many');
208
            }
209
        }
210 6
        if (isset($xmlRoot->{'reference-one'})) {
211 2
            foreach ($xmlRoot->{'reference-one'} as $reference) {
212 2
                $this->addReferenceMapping($class, $reference, 'one');
213
            }
214
        }
215 6
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
216 1
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
217 1
                $class->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ODM\MongoDB\Events::' . (string) $lifecycleCallback['type']));
218
            }
219
        }
220 6
        if (! isset($xmlRoot->{'also-load-methods'})) {
221 6
            return;
222
        }
223
224 1
        foreach ($xmlRoot->{'also-load-methods'}->{'also-load-method'} as $alsoLoadMethod) {
225 1
            $class->registerAlsoLoadMethod((string) $alsoLoadMethod['method'], (string) $alsoLoadMethod['field']);
226
        }
227 1
    }
228
229 7
    private function addFieldMapping(ClassMetadata $class, $mapping)
230
    {
231 7
        if (isset($mapping['name'])) {
232 5
            $name = $mapping['name'];
233 6
        } elseif (isset($mapping['fieldName'])) {
234 6
            $name = $mapping['fieldName'];
235
        } else {
236
            throw new \InvalidArgumentException('Cannot infer a MongoDB name from the mapping');
237
        }
238
239 7
        $class->mapField($mapping);
240
241
        // Index this field if either "index", "unique", or "sparse" are set
242 7
        if (! (isset($mapping['index']) || isset($mapping['unique']) || isset($mapping['sparse']))) {
243 7
            return;
244
        }
245
246 1
        $keys = [$name => $mapping['order'] ?? 'asc'];
247 1
        $options = [];
248
249 1
        if (isset($mapping['background'])) {
250
            $options['background'] = (bool) $mapping['background'];
251
        }
252 1
        if (isset($mapping['drop-dups'])) {
253
            $options['dropDups'] = (bool) $mapping['drop-dups'];
254
        }
255 1
        if (isset($mapping['index-name'])) {
256
            $options['name'] = (string) $mapping['index-name'];
257
        }
258 1
        if (isset($mapping['sparse'])) {
259 1
            $options['sparse'] = (bool) $mapping['sparse'];
260
        }
261 1
        if (isset($mapping['unique'])) {
262 1
            $options['unique'] = (bool) $mapping['unique'];
263
        }
264
265 1
        $class->addIndex($keys, $options);
266 1
    }
267
268 1
    private function addEmbedMapping(ClassMetadata $class, $embed, $type)
269
    {
270 1
        $attributes = $embed->attributes();
271 1
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
272
        $mapping = [
273 1
            'type'            => $type,
274
            'embedded'        => true,
275 1
            'targetDocument'  => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
276 1
            'collectionClass' => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
277 1
            'name'            => (string) $attributes['field'],
278 1
            'strategy'        => (string) ($attributes['strategy'] ?? $defaultStrategy),
279
        ];
280 1
        if (isset($attributes['fieldName'])) {
281
            $mapping['fieldName'] = (string) $attributes['fieldName'];
282
        }
283 1
        if (isset($embed->{'discriminator-field'})) {
284
            $attr = $embed->{'discriminator-field'};
285
            $mapping['discriminatorField'] = (string) $attr['name'];
286
        }
287 1
        if (isset($embed->{'discriminator-map'})) {
288
            foreach ($embed->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
289
                $attr = $discriminatorMapping->attributes();
290
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
291
            }
292
        }
293 1
        if (isset($embed->{'default-discriminator-value'})) {
294
            $mapping['defaultDiscriminatorValue'] = (string) $embed->{'default-discriminator-value'}['value'];
295
        }
296 1
        if (isset($attributes['not-saved'])) {
297
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
298
        }
299 1
        if (isset($attributes['also-load'])) {
300
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
301
        }
302 1
        $this->addFieldMapping($class, $mapping);
303 1
    }
304
305 4
    private function addReferenceMapping(ClassMetadata $class, $reference, $type)
306
    {
307 4
        $cascade = array_keys((array) $reference->cascade);
308 4
        if (count($cascade) === 1) {
309 1
            $cascade = current($cascade) ?: next($cascade);
310
        }
311 4
        $attributes = $reference->attributes();
312 4
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
313
        $mapping = [
314 4
            'cascade'          => $cascade,
315 4
            'orphanRemoval'    => isset($attributes['orphan-removal']) ? ((string) $attributes['orphan-removal'] === 'true') : false,
316 4
            'type'             => $type,
317
            'reference'        => true,
318 4
            'storeAs'          => (string) ($attributes['store-as'] ?? ClassMetadata::REFERENCE_STORE_AS_DB_REF),
319 4
            'targetDocument'   => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
320 4
            'collectionClass'  => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
321 4
            'name'             => (string) $attributes['field'],
322 4
            'strategy'         => (string) ($attributes['strategy'] ?? $defaultStrategy),
323 4
            'inversedBy'       => isset($attributes['inversed-by']) ? (string) $attributes['inversed-by'] : null,
324 4
            'mappedBy'         => isset($attributes['mapped-by']) ? (string) $attributes['mapped-by'] : null,
325 4
            'repositoryMethod' => isset($attributes['repository-method']) ? (string) $attributes['repository-method'] : null,
326 4
            'limit'            => isset($attributes['limit']) ? (int) $attributes['limit'] : null,
327 4
            'skip'             => isset($attributes['skip']) ? (int) $attributes['skip'] : null,
328
            'prime'            => [],
329
        ];
330
331 4
        if (isset($attributes['fieldName'])) {
332
            $mapping['fieldName'] = (string) $attributes['fieldName'];
333
        }
334 4
        if (isset($reference->{'discriminator-field'})) {
335
            $attr = $reference->{'discriminator-field'};
336
            $mapping['discriminatorField'] = (string) $attr['name'];
337
        }
338 4
        if (isset($reference->{'discriminator-map'})) {
339
            foreach ($reference->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
340
                $attr = $discriminatorMapping->attributes();
341
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
342
            }
343
        }
344 4
        if (isset($reference->{'default-discriminator-value'})) {
345
            $mapping['defaultDiscriminatorValue'] = (string) $reference->{'default-discriminator-value'}['value'];
346
        }
347 4
        if (isset($reference->{'sort'})) {
348
            foreach ($reference->{'sort'}->{'sort'} as $sort) {
349
                $attr = $sort->attributes();
350
                $mapping['sort'][(string) $attr['field']] = (string) ($attr['order'] ?? 'asc');
351
            }
352
        }
353 4
        if (isset($reference->{'criteria'})) {
354
            foreach ($reference->{'criteria'}->{'criteria'} as $criteria) {
355
                $attr = $criteria->attributes();
356
                $mapping['criteria'][(string) $attr['field']] = (string) $attr['value'];
357
            }
358
        }
359 4
        if (isset($attributes['not-saved'])) {
360
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
361
        }
362 4
        if (isset($attributes['also-load'])) {
363
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
364
        }
365 4
        if (isset($reference->{'prime'})) {
366 1
            foreach ($reference->{'prime'}->{'field'} as $field) {
367 1
                $attr = $field->attributes();
368 1
                $mapping['prime'][] = (string) $attr['name'];
369
            }
370
        }
371
372 4
        $this->addFieldMapping($class, $mapping);
373 4
    }
374
375 1
    private function addIndex(ClassMetadata $class, \SimpleXmlElement $xmlIndex)
376
    {
377 1
        $attributes = $xmlIndex->attributes();
378
379 1
        $keys = [];
380
381 1
        foreach ($xmlIndex->{'key'} as $key) {
382 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
383
        }
384
385 1
        $options = [];
386
387 1
        if (isset($attributes['background'])) {
388
            $options['background'] = ((string) $attributes['background'] === 'true');
389
        }
390 1
        if (isset($attributes['drop-dups'])) {
391
            $options['dropDups'] = ((string) $attributes['drop-dups'] === 'true');
392
        }
393 1
        if (isset($attributes['name'])) {
394
            $options['name'] = (string) $attributes['name'];
395
        }
396 1
        if (isset($attributes['sparse'])) {
397
            $options['sparse'] = ((string) $attributes['sparse'] === 'true');
398
        }
399 1
        if (isset($attributes['unique'])) {
400
            $options['unique'] = ((string) $attributes['unique'] === 'true');
401
        }
402
403 1
        if (isset($xmlIndex->{'option'})) {
404
            foreach ($xmlIndex->{'option'} as $option) {
405
                $value = (string) $option['value'];
406
                if ($value === 'true') {
407
                    $value = true;
408
                } elseif ($value === 'false') {
409
                    $value = false;
410
                } elseif (is_numeric($value)) {
411
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
412
                }
413
                $options[(string) $option['name']] = $value;
414
            }
415
        }
416
417 1
        if (isset($xmlIndex->{'partial-filter-expression'})) {
418 1
            $partialFilterExpressionMapping = $xmlIndex->{'partial-filter-expression'};
419
420 1
            if (isset($partialFilterExpressionMapping->and)) {
421 1
                foreach ($partialFilterExpressionMapping->and as $and) {
422 1
                    if (! isset($and->field)) {
423
                        continue;
424
                    }
425
426 1
                    $partialFilterExpression = $this->getPartialFilterExpression($and->field);
427 1
                    if (! $partialFilterExpression) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $partialFilterExpression of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
428
                        continue;
429
                    }
430
431 1
                    $options['partialFilterExpression']['$and'][] = $partialFilterExpression;
432
                }
433 1
            } elseif (isset($partialFilterExpressionMapping->field)) {
434 1
                $partialFilterExpression = $this->getPartialFilterExpression($partialFilterExpressionMapping->field);
435
436 1
                if ($partialFilterExpression) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $partialFilterExpression of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
437 1
                    $options['partialFilterExpression'] = $partialFilterExpression;
438
                }
439
            }
440
        }
441
442 1
        $class->addIndex($keys, $options);
443 1
    }
444
445 1
    private function getPartialFilterExpression(\SimpleXMLElement $fields)
446
    {
447 1
        $partialFilterExpression = [];
448 1
        foreach ($fields as $field) {
449 1
            $operator = (string) $field['operator'] ?: null;
450
451 1
            if (! isset($field['value'])) {
452 1
                if (! isset($field->field)) {
453
                    continue;
454
                }
455
456 1
                $nestedExpression = $this->getPartialFilterExpression($field->field);
457 1
                if (! $nestedExpression) {
458
                    continue;
459
                }
460
461 1
                $value = $nestedExpression;
462
            } else {
463 1
                $value = trim((string) $field['value']);
464
            }
465
466 1
            if ($value === 'true') {
467
                $value = true;
468 1
            } elseif ($value === 'false') {
469
                $value = false;
470 1
            } elseif (is_numeric($value)) {
471 1
                $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
472
            }
473
474 1
            $partialFilterExpression[(string) $field['name']] = $operator ? ['$' . $operator => $value] : $value;
475
        }
476
477 1
        return $partialFilterExpression;
478
    }
479
480 1
    private function setShardKey(ClassMetadata $class, \SimpleXmlElement $xmlShardkey)
481
    {
482 1
        $attributes = $xmlShardkey->attributes();
483
484 1
        $keys = [];
485 1
        $options = [];
486 1
        foreach ($xmlShardkey->{'key'} as $key) {
487 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
488
        }
489
490 1
        if (isset($attributes['unique'])) {
491 1
            $options['unique'] = ((string) $attributes['unique'] === 'true');
492
        }
493
494 1
        if (isset($attributes['numInitialChunks'])) {
495 1
            $options['numInitialChunks'] = (int) $attributes['numInitialChunks'];
496
        }
497
498 1
        if (isset($xmlShardkey->{'option'})) {
499
            foreach ($xmlShardkey->{'option'} as $option) {
500
                $value = (string) $option['value'];
501
                if ($value === 'true') {
502
                    $value = true;
503
                } elseif ($value === 'false') {
504
                    $value = false;
505
                } elseif (is_numeric($value)) {
506
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
507
                }
508
                $options[(string) $option['name']] = $value;
509
            }
510
        }
511
512 1
        $class->setShardKey($keys, $options);
513 1
    }
514
515
    /**
516
     * Parses <read-preference> to a format suitable for the underlying driver.
517
     *
518
     * list($readPreference, $tags) = $this->transformReadPreference($xml->{read-preference});
519
     *
520
     * @param \SimpleXMLElement $xmlReadPreference
521
     * @return array
522
     */
523
    private function transformReadPreference($xmlReadPreference)
524
    {
525
        $tags = null;
526
        if (isset($xmlReadPreference->{'tag-set'})) {
527
            $tags = [];
528
            foreach ($xmlReadPreference->{'tag-set'} as $tagSet) {
529
                $set = [];
530
                foreach ($tagSet->tag as $tag) {
531
                    $set[(string) $tag['name']] = (string) $tag['value'];
532
                }
533
                $tags[] = $set;
534
            }
535
        }
536
        return [(string) $xmlReadPreference['mode'], $tags];
537
    }
538
539
    /**
540
     * {@inheritDoc}
541
     */
542 8
    protected function loadMappingFile($file)
543
    {
544 8
        $result = [];
545
546 8
        $this->validateSchema($file);
547
548 6
        $xmlElement = simplexml_load_file($file);
549
550 6
        foreach (['document', 'embedded-document', 'mapped-superclass', 'query-result-document'] as $type) {
551 6
            if (! isset($xmlElement->$type)) {
552 6
                continue;
553
            }
554
555 6
            foreach ($xmlElement->$type as $documentElement) {
556 6
                $documentName = (string) $documentElement['name'];
557 6
                $result[$documentName] = $documentElement;
558
            }
559
        }
560
561 6
        return $result;
562
    }
563
564 8
    private function validateSchema(string $filename): void
565
    {
566 8
        $document = new DOMDocument();
567 8
        $document->load($filename);
568
569 8
        $previousUseErrors = libxml_use_internal_errors(true);
570
571
        try {
572 8
            libxml_clear_errors();
573
574 8
            if (! $document->schemaValidate(__DIR__ . '/../../../../../../doctrine-mongo-mapping.xsd')) {
575 2
                throw MappingException::xmlMappingFileInvalid($filename, $this->formatErrors(libxml_get_errors()));
576
            }
577 6
        } finally {
578 8
            libxml_use_internal_errors($previousUseErrors);
579
        }
580 6
    }
581
582
    /**
583
     * @param LibXMLError[] $xmlErrors
584
     */
585 2
    private function formatErrors(array $xmlErrors): string
586
    {
587
        return implode("\n", array_map(function (LibXMLError $error): string {
588 2
            return sprintf('Line %d:%d: %s', $error->line, $error->column, $error->message);
589 2
        }, $xmlErrors));
590
    }
591
}
592