Completed
Push — master ( c0f1d0...6ded07 )
by Andreas
23:24 queued 11s
created

Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 InvalidArgumentException;
13
use LibXMLError;
14
use SimpleXMLElement;
15
use function array_keys;
16
use function array_map;
17
use function assert;
18
use function constant;
19
use function count;
20
use function current;
21
use function explode;
22
use function implode;
23
use function in_array;
24
use function is_numeric;
25
use function iterator_to_array;
26
use function libxml_clear_errors;
27
use function libxml_get_errors;
28
use function libxml_use_internal_errors;
29
use function next;
30
use function preg_match;
31
use function simplexml_load_file;
32
use function sprintf;
33
use function strtoupper;
34
use function trim;
35
36
/**
37
 * XmlDriver is a metadata driver that enables mapping through XML files.
38
 *
39
 * @method SimpleXMLElement getElement(string $className)
40
 */
41
class XmlDriver extends FileDriver
42
{
43
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
44
45
    private const DEFAULT_GRIDFS_MAPPINGS = [
46
        'length' => [
47
            'name' => 'length',
48
            'type' => 'int',
49
            'notSaved' => true,
50
        ],
51
        'chunk-size' => [
52
            'name' => 'chunkSize',
53
            'type' => 'int',
54
            'notSaved' => true,
55
        ],
56
        'filename' => [
57
            'name' => 'filename',
58
            'type' => 'string',
59
            'notSaved' => true,
60
        ],
61
        'upload-date' => [
62
            'name' => 'uploadDate',
63
            'type' => 'date',
64
            'notSaved' => true,
65
        ],
66
    ];
67
68
    /**
69
     * {@inheritDoc}
70
     */
71 16
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
72
    {
73 16
        parent::__construct($locator, $fileExtension);
74 16
    }
75
76
    /**
77
     * {@inheritDoc}
78
     */
79 11
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class)
80
    {
81 11
        assert($class instanceof ClassMetadata);
82 11
        $xmlRoot = $this->getElement($className);
83
84 9
        if ($xmlRoot->getName() === 'document') {
85 8
            if (isset($xmlRoot['repository-class'])) {
86 8
                $class->setCustomRepositoryClass((string) $xmlRoot['repository-class']);
87
            }
88 3
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
89 1
            $class->setCustomRepositoryClass(
90 1
                isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null
91
            );
92 1
            $class->isMappedSuperclass = true;
93 2
        } elseif ($xmlRoot->getName() === 'embedded-document') {
94 1
            $class->isEmbeddedDocument = true;
95 2
        } elseif ($xmlRoot->getName() === 'query-result-document') {
96 1
            $class->isQueryResultDocument = true;
97 1
        } elseif ($xmlRoot->getName() === 'gridfs-file') {
98 1
            $class->isFile = true;
99
100 1
            if (isset($xmlRoot['chunk-size-bytes'])) {
101 1
                $class->setChunkSizeBytes((int) $xmlRoot['chunk-size-bytes']);
102
            }
103
        }
104
105 9
        if (isset($xmlRoot['db'])) {
106 3
            $class->setDatabase((string) $xmlRoot['db']);
107
        }
108
109 9
        if (isset($xmlRoot['collection'])) {
110 5
            if (isset($xmlRoot['capped-collection'])) {
111
                $config           = ['name' => (string) $xmlRoot['collection']];
112
                $config['capped'] = (bool) $xmlRoot['capped-collection'];
113
                if (isset($xmlRoot['capped-collection-max'])) {
114
                    $config['max'] = (int) $xmlRoot['capped-collection-max'];
115
                }
116
                if (isset($xmlRoot['capped-collection-size'])) {
117
                    $config['size'] = (int) $xmlRoot['capped-collection-size'];
118
                }
119
                $class->setCollection($config);
120
            } else {
121 5
                $class->setCollection((string) $xmlRoot['collection']);
122
            }
123
        }
124 9
        if (isset($xmlRoot['bucket-name'])) {
125
            $class->setBucketName((string) $xmlRoot['bucket-name']);
126
        }
127 9
        if (isset($xmlRoot['write-concern'])) {
128
            $class->setWriteConcern((string) $xmlRoot['write-concern']);
129
        }
130 9
        if (isset($xmlRoot['inheritance-type'])) {
131
            $inheritanceType = (string) $xmlRoot['inheritance-type'];
132
            $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $inheritanceType));
133
        }
134 9
        if (isset($xmlRoot['change-tracking-policy'])) {
135 1
            $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . strtoupper((string) $xmlRoot['change-tracking-policy'])));
136
        }
137 9
        if (isset($xmlRoot->{'discriminator-field'})) {
138
            $discrField = $xmlRoot->{'discriminator-field'};
139
            $class->setDiscriminatorField((string) $discrField['name']);
140
        }
141 9
        if (isset($xmlRoot->{'discriminator-map'})) {
142
            $map = [];
143
            foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
144
                $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
145
            }
146
            $class->setDiscriminatorMap($map);
147
        }
148 9
        if (isset($xmlRoot->{'default-discriminator-value'})) {
149
            $class->setDefaultDiscriminatorValue((string) $xmlRoot->{'default-discriminator-value'}['value']);
150
        }
151 9
        if (isset($xmlRoot->{'indexes'})) {
152 1
            foreach ($xmlRoot->{'indexes'}->{'index'} as $index) {
153 1
                $this->addIndex($class, $index);
154
            }
155
        }
156 9
        if (isset($xmlRoot->{'shard-key'})) {
157
            $this->setShardKey($class, $xmlRoot->{'shard-key'}[0]);
158
        }
159 9
        if (isset($xmlRoot['read-only']) && (string) $xmlRoot['read-only'] === 'true') {
160
            $class->markReadOnly();
161
        }
162 9
        if (isset($xmlRoot->{'read-preference'})) {
163
            $class->setReadPreference(...$this->transformReadPreference($xmlRoot->{'read-preference'}));
0 ignored issues
show
The call to setReadPreference() misses a required argument $tags.

This check looks for function calls that miss required arguments.

Loading history...
164
        }
165
166 9
        if (isset($xmlRoot->id)) {
167 7
            $field   = $xmlRoot->id;
168
            $mapping = [
169 7
                'id' => true,
170
                'fieldName' => 'id',
171
            ];
172
173
            /** @var SimpleXMLElement $attributes */
174 7
            $attributes = $field->attributes();
175 7
            foreach ($attributes as $key => $value) {
176 2
                $mapping[$key] = (string) $value;
177
            }
178
179 7
            if (isset($mapping['strategy'])) {
180 2
                $mapping['options'] = [];
181 2
                if (isset($field->{'generator-option'})) {
182 1
                    foreach ($field->{'generator-option'} as $generatorOptions) {
183 1
                        $attributesGenerator = iterator_to_array($generatorOptions->attributes());
184 1
                        if (! isset($attributesGenerator['name']) || ! isset($attributesGenerator['value'])) {
185
                            continue;
186
                        }
187
188 1
                        $mapping['options'][(string) $attributesGenerator['name']] = (string) $attributesGenerator['value'];
189
                    }
190
                }
191
            }
192
193 7
            $this->addFieldMapping($class, $mapping);
194
        }
195
196 9
        if (isset($xmlRoot->field)) {
197 4
            foreach ($xmlRoot->field as $field) {
198 4
                $mapping    = [];
199 4
                $attributes = $field->attributes();
200 4
                foreach ($attributes as $key => $value) {
201 4
                    $mapping[$key]     = (string) $value;
202 4
                    $booleanAttributes = ['reference', 'embed', 'unique', 'sparse'];
203 4
                    if (! in_array($key, $booleanAttributes)) {
204 4
                        continue;
205
                    }
206
207 1
                    $mapping[$key] = ($mapping[$key] === 'true');
208
                }
209
210 4
                if (isset($attributes['not-saved'])) {
211 1
                    $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
212
                }
213
214 4
                if (isset($attributes['field-name'])) {
215 2
                    $mapping['fieldName'] = (string) $attributes['field-name'];
216
                }
217
218 4
                if (isset($attributes['also-load'])) {
219
                    $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
220 4
                } elseif (isset($attributes['version'])) {
221
                    $mapping['version'] = ((string) $attributes['version'] === 'true');
222 4
                } elseif (isset($attributes['lock'])) {
223
                    $mapping['lock'] = ((string) $attributes['lock'] === 'true');
224
                }
225
226 4
                $this->addFieldMapping($class, $mapping);
227
            }
228
        }
229
230 8
        $this->addGridFSMappings($class, $xmlRoot);
231
232 8
        if (isset($xmlRoot->{'embed-one'})) {
233 1
            foreach ($xmlRoot->{'embed-one'} as $embed) {
234 1
                $this->addEmbedMapping($class, $embed, 'one');
235
            }
236
        }
237 8
        if (isset($xmlRoot->{'embed-many'})) {
238 1
            foreach ($xmlRoot->{'embed-many'} as $embed) {
239 1
                $this->addEmbedMapping($class, $embed, 'many');
240
            }
241
        }
242 8
        if (isset($xmlRoot->{'reference-many'})) {
243 3
            foreach ($xmlRoot->{'reference-many'} as $reference) {
244 3
                $this->addReferenceMapping($class, $reference, 'many');
245
            }
246
        }
247 8
        if (isset($xmlRoot->{'reference-one'})) {
248 2
            foreach ($xmlRoot->{'reference-one'} as $reference) {
249 2
                $this->addReferenceMapping($class, $reference, 'one');
250
            }
251
        }
252 8
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
253 1
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
254 1
                $class->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ODM\MongoDB\Events::' . (string) $lifecycleCallback['type']));
255
            }
256
        }
257 8
        if (! isset($xmlRoot->{'also-load-methods'})) {
258 8
            return;
259
        }
260
261 1
        foreach ($xmlRoot->{'also-load-methods'}->{'also-load-method'} as $alsoLoadMethod) {
262 1
            $class->registerAlsoLoadMethod((string) $alsoLoadMethod['method'], (string) $alsoLoadMethod['field']);
263
        }
264 1
    }
265
266 9
    private function addFieldMapping(ClassMetadata $class, array $mapping) : void
267
    {
268 9
        if (isset($mapping['name'])) {
269 7
            $name = $mapping['name'];
270 9
        } elseif (isset($mapping['fieldName'])) {
271 9
            $name = $mapping['fieldName'];
272
        } else {
273
            throw new InvalidArgumentException('Cannot infer a MongoDB name from the mapping');
274
        }
275
276 9
        $class->mapField($mapping);
277
278
        // Index this field if either "index", "unique", or "sparse" are set
279 9
        if (! (isset($mapping['index']) || isset($mapping['unique']) || isset($mapping['sparse']))) {
280 9
            return;
281
        }
282
283 1
        $keys    = [$name => $mapping['order'] ?? 'asc'];
284 1
        $options = [];
285
286 1
        if (isset($mapping['background'])) {
287
            $options['background'] = (bool) $mapping['background'];
288
        }
289 1
        if (isset($mapping['index-name'])) {
290
            $options['name'] = (string) $mapping['index-name'];
291
        }
292 1
        if (isset($mapping['sparse'])) {
293 1
            $options['sparse'] = (bool) $mapping['sparse'];
294
        }
295 1
        if (isset($mapping['unique'])) {
296 1
            $options['unique'] = (bool) $mapping['unique'];
297
        }
298
299 1
        $class->addIndex($keys, $options);
300 1
    }
301
302 2
    private function addEmbedMapping(ClassMetadata $class, SimpleXMLElement $embed, string $type) : void
303
    {
304 2
        $attributes      = $embed->attributes();
305 2
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
306
        $mapping         = [
307 2
            'type'            => $type,
308
            'embedded'        => true,
309 2
            'targetDocument'  => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
310 2
            'collectionClass' => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
311 2
            'name'            => (string) $attributes['field'],
312 2
            'strategy'        => (string) ($attributes['strategy'] ?? $defaultStrategy),
313
        ];
314 2
        if (isset($attributes['field-name'])) {
315
            $mapping['fieldName'] = (string) $attributes['field-name'];
316
        }
317 2
        if (isset($embed->{'discriminator-field'})) {
318
            $attr                          = $embed->{'discriminator-field'};
319
            $mapping['discriminatorField'] = (string) $attr['name'];
320
        }
321 2
        if (isset($embed->{'discriminator-map'})) {
322
            foreach ($embed->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
323
                $attr                                                 = $discriminatorMapping->attributes();
324
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
325
            }
326
        }
327 2
        if (isset($embed->{'default-discriminator-value'})) {
328
            $mapping['defaultDiscriminatorValue'] = (string) $embed->{'default-discriminator-value'}['value'];
329
        }
330 2
        if (isset($attributes['not-saved'])) {
331
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
332
        }
333 2
        if (isset($attributes['also-load'])) {
334
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
335
        }
336 2
        $this->addFieldMapping($class, $mapping);
337 2
    }
338
339 3
    private function addReferenceMapping(ClassMetadata $class, $reference, string $type) : void
340
    {
341 3
        $cascade = array_keys((array) $reference->cascade);
342 3
        if (count($cascade) === 1) {
343 1
            $cascade = current($cascade) ?: next($cascade);
344
        }
345 3
        $attributes      = $reference->attributes();
346 3
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
347
        $mapping         = [
348 3
            'cascade'          => $cascade,
349 3
            'orphanRemoval'    => isset($attributes['orphan-removal']) ? ((string) $attributes['orphan-removal'] === 'true') : false,
350 3
            'type'             => $type,
351
            'reference'        => true,
352 3
            'storeAs'          => (string) ($attributes['store-as'] ?? ClassMetadata::REFERENCE_STORE_AS_DB_REF),
353 3
            'targetDocument'   => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
354 3
            'collectionClass'  => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
355 3
            'name'             => (string) $attributes['field'],
356 3
            'strategy'         => (string) ($attributes['strategy'] ?? $defaultStrategy),
357 3
            'inversedBy'       => isset($attributes['inversed-by']) ? (string) $attributes['inversed-by'] : null,
358 3
            'mappedBy'         => isset($attributes['mapped-by']) ? (string) $attributes['mapped-by'] : null,
359 3
            'repositoryMethod' => isset($attributes['repository-method']) ? (string) $attributes['repository-method'] : null,
360 3
            'limit'            => isset($attributes['limit']) ? (int) $attributes['limit'] : null,
361 3
            'skip'             => isset($attributes['skip']) ? (int) $attributes['skip'] : null,
362
            'prime'            => [],
363
        ];
364
365 3
        if (isset($attributes['field-name'])) {
366
            $mapping['fieldName'] = (string) $attributes['field-name'];
367
        }
368 3
        if (isset($reference->{'discriminator-field'})) {
369
            $attr                          = $reference->{'discriminator-field'};
370
            $mapping['discriminatorField'] = (string) $attr['name'];
371
        }
372 3
        if (isset($reference->{'discriminator-map'})) {
373
            foreach ($reference->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
374
                $attr                                                 = $discriminatorMapping->attributes();
375
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
376
            }
377
        }
378 3
        if (isset($reference->{'default-discriminator-value'})) {
379
            $mapping['defaultDiscriminatorValue'] = (string) $reference->{'default-discriminator-value'}['value'];
380
        }
381 3
        if (isset($reference->{'sort'})) {
382
            foreach ($reference->{'sort'}->{'sort'} as $sort) {
383
                $attr                                     = $sort->attributes();
384
                $mapping['sort'][(string) $attr['field']] = (string) ($attr['order'] ?? 'asc');
385
            }
386
        }
387 3
        if (isset($reference->{'criteria'})) {
388
            foreach ($reference->{'criteria'}->{'criteria'} as $criteria) {
389
                $attr                                         = $criteria->attributes();
390
                $mapping['criteria'][(string) $attr['field']] = (string) $attr['value'];
391
            }
392
        }
393 3
        if (isset($attributes['not-saved'])) {
394
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
395
        }
396 3
        if (isset($attributes['also-load'])) {
397
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
398
        }
399 3
        if (isset($reference->{'prime'})) {
400 1
            foreach ($reference->{'prime'}->{'field'} as $field) {
401 1
                $attr               = $field->attributes();
402 1
                $mapping['prime'][] = (string) $attr['name'];
403
            }
404
        }
405
406 3
        $this->addFieldMapping($class, $mapping);
407 3
    }
408
409 1
    private function addIndex(ClassMetadata $class, SimpleXMLElement $xmlIndex) : void
410
    {
411 1
        $attributes = $xmlIndex->attributes();
412
413 1
        $keys = [];
414
415 1
        foreach ($xmlIndex->{'key'} as $key) {
416 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
417
        }
418
419 1
        $options = [];
420
421 1
        if (isset($attributes['background'])) {
422
            $options['background'] = ((string) $attributes['background'] === 'true');
423
        }
424 1
        if (isset($attributes['name'])) {
425
            $options['name'] = (string) $attributes['name'];
426
        }
427 1
        if (isset($attributes['sparse'])) {
428
            $options['sparse'] = ((string) $attributes['sparse'] === 'true');
429
        }
430 1
        if (isset($attributes['unique'])) {
431
            $options['unique'] = ((string) $attributes['unique'] === 'true');
432
        }
433
434 1
        if (isset($xmlIndex->{'option'})) {
435
            foreach ($xmlIndex->{'option'} as $option) {
436
                $value = (string) $option['value'];
437
                if ($value === 'true') {
438
                    $value = true;
439
                } elseif ($value === 'false') {
440
                    $value = false;
441
                } elseif (is_numeric($value)) {
442
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
443
                }
444
                $options[(string) $option['name']] = $value;
445
            }
446
        }
447
448 1
        if (isset($xmlIndex->{'partial-filter-expression'})) {
449 1
            $partialFilterExpressionMapping = $xmlIndex->{'partial-filter-expression'};
450
451 1
            if (isset($partialFilterExpressionMapping->and)) {
452 1
                foreach ($partialFilterExpressionMapping->and as $and) {
453 1
                    if (! isset($and->field)) {
454
                        continue;
455
                    }
456
457 1
                    $partialFilterExpression = $this->getPartialFilterExpression($and->field);
458 1
                    if (! $partialFilterExpression) {
459
                        continue;
460
                    }
461
462 1
                    $options['partialFilterExpression']['$and'][] = $partialFilterExpression;
463
                }
464 1
            } elseif (isset($partialFilterExpressionMapping->field)) {
465 1
                $partialFilterExpression = $this->getPartialFilterExpression($partialFilterExpressionMapping->field);
466
467 1
                if ($partialFilterExpression) {
468 1
                    $options['partialFilterExpression'] = $partialFilterExpression;
469
                }
470
            }
471
        }
472
473 1
        $class->addIndex($keys, $options);
474 1
    }
475
476 1
    private function getPartialFilterExpression(SimpleXMLElement $fields) : array
477
    {
478 1
        $partialFilterExpression = [];
479 1
        foreach ($fields as $field) {
480 1
            $operator = (string) $field['operator'] ?: null;
481
482 1
            if (! isset($field['value'])) {
483 1
                if (! isset($field->field)) {
484
                    continue;
485
                }
486
487 1
                $nestedExpression = $this->getPartialFilterExpression($field->field);
488 1
                if (! $nestedExpression) {
489
                    continue;
490
                }
491
492 1
                $value = $nestedExpression;
493
            } else {
494 1
                $value = trim((string) $field['value']);
495
            }
496
497 1
            if ($value === 'true') {
498
                $value = true;
499 1
            } elseif ($value === 'false') {
500
                $value = false;
501 1
            } elseif (is_numeric($value)) {
502 1
                $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
503
            }
504
505 1
            $partialFilterExpression[(string) $field['name']] = $operator ? ['$' . $operator => $value] : $value;
506
        }
507
508 1
        return $partialFilterExpression;
509
    }
510
511 1
    private function setShardKey(ClassMetadata $class, SimpleXMLElement $xmlShardkey) : void
512
    {
513 1
        $attributes = $xmlShardkey->attributes();
514
515 1
        $keys    = [];
516 1
        $options = [];
517 1
        foreach ($xmlShardkey->{'key'} as $key) {
518 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
519
        }
520
521 1
        if (isset($attributes['unique'])) {
522 1
            $options['unique'] = ((string) $attributes['unique'] === 'true');
523
        }
524
525 1
        if (isset($attributes['numInitialChunks'])) {
526 1
            $options['numInitialChunks'] = (int) $attributes['numInitialChunks'];
527
        }
528
529 1
        if (isset($xmlShardkey->{'option'})) {
530
            foreach ($xmlShardkey->{'option'} as $option) {
531
                $value = (string) $option['value'];
532
                if ($value === 'true') {
533
                    $value = true;
534
                } elseif ($value === 'false') {
535
                    $value = false;
536
                } elseif (is_numeric($value)) {
537
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
538
                }
539
                $options[(string) $option['name']] = $value;
540
            }
541
        }
542
543 1
        $class->setShardKey($keys, $options);
544 1
    }
545
546
    /**
547
     * Parses <read-preference> to a format suitable for the underlying driver.
548
     *
549
     * list($readPreference, $tags) = $this->transformReadPreference($xml->{read-preference});
550
     */
551
    private function transformReadPreference(SimpleXMLElement $xmlReadPreference) : array
552
    {
553
        $tags = null;
554
        if (isset($xmlReadPreference->{'tag-set'})) {
555
            $tags = [];
556
            foreach ($xmlReadPreference->{'tag-set'} as $tagSet) {
557
                $set = [];
558
                foreach ($tagSet->tag as $tag) {
559
                    $set[(string) $tag['name']] = (string) $tag['value'];
560
                }
561
                $tags[] = $set;
562
            }
563
        }
564
565
        return [(string) $xmlReadPreference['mode'], $tags];
566
    }
567
568
    /**
569
     * {@inheritDoc}
570
     */
571 11
    protected function loadMappingFile($file) : array
572
    {
573 11
        $result = [];
574
575 11
        $this->validateSchema($file);
576
577 9
        $xmlElement = simplexml_load_file($file);
578
579 9
        foreach (['document', 'embedded-document', 'mapped-superclass', 'query-result-document', 'gridfs-file'] as $type) {
580 9
            if (! isset($xmlElement->$type)) {
581 9
                continue;
582
            }
583
584 9
            foreach ($xmlElement->$type as $documentElement) {
585 9
                $documentName          = (string) $documentElement['name'];
586 9
                $result[$documentName] = $documentElement;
587
            }
588
        }
589
590 9
        return $result;
591
    }
592
593 11
    private function validateSchema(string $filename) : void
594
    {
595 11
        $document = new DOMDocument();
596 11
        $document->load($filename);
597
598 11
        $previousUseErrors = libxml_use_internal_errors(true);
599
600
        try {
601 11
            libxml_clear_errors();
602
603 11
            if (! $document->schemaValidate(__DIR__ . '/../../../../../../doctrine-mongo-mapping.xsd')) {
604 2
                throw MappingException::xmlMappingFileInvalid($filename, $this->formatErrors(libxml_get_errors()));
605
            }
606 9
        } finally {
607 11
            libxml_use_internal_errors($previousUseErrors);
608
        }
609 9
    }
610
611
    /**
612
     * @param LibXMLError[] $xmlErrors
613
     */
614 2
    private function formatErrors(array $xmlErrors) : string
615
    {
616
        return implode("\n", array_map(static function (LibXMLError $error) : string {
617 2
            return sprintf('Line %d:%d: %s', $error->line, $error->column, $error->message);
618 2
        }, $xmlErrors));
619
    }
620
621 8
    private function addGridFSMappings(ClassMetadata $class, SimpleXMLElement $xmlRoot) : void
622
    {
623 8
        if (! $class->isFile) {
624 7
            return;
625
        }
626
627 1
        foreach (self::DEFAULT_GRIDFS_MAPPINGS as $name => $mapping) {
628 1
            if (! isset($xmlRoot->{$name})) {
629
                continue;
630
            }
631
632 1
            if (isset($xmlRoot->{$name}->attributes()['field-name'])) {
633 1
                $mapping['fieldName'] = (string) $xmlRoot->{$name}->attributes()['field-name'];
634
            }
635
636 1
            $this->addFieldMapping($class, $mapping);
637
        }
638
639 1
        if (! isset($xmlRoot->metadata)) {
640
            return;
641
        }
642
643 1
        $xmlRoot->metadata->addAttribute('field', 'metadata');
644 1
        $this->addEmbedMapping($class, $xmlRoot->metadata, 'one');
645 1
    }
646
}
647