Completed
Push — master ( 7b9f4b...1cd743 )
by Andreas
13s queued 10s
created

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

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
        return [(string) $xmlReadPreference['mode'], $tags];
565
    }
566
567
    /**
568
     * {@inheritDoc}
569
     */
570 11
    protected function loadMappingFile($file) : array
571
    {
572 11
        $result = [];
573
574 11
        $this->validateSchema($file);
575
576 9
        $xmlElement = simplexml_load_file($file);
577
578 9
        foreach (['document', 'embedded-document', 'mapped-superclass', 'query-result-document', 'gridfs-file'] as $type) {
579 9
            if (! isset($xmlElement->$type)) {
580 9
                continue;
581
            }
582
583 9
            foreach ($xmlElement->$type as $documentElement) {
584 9
                $documentName          = (string) $documentElement['name'];
585 9
                $result[$documentName] = $documentElement;
586
            }
587
        }
588
589 9
        return $result;
590
    }
591
592 11
    private function validateSchema(string $filename) : void
593
    {
594 11
        $document = new DOMDocument();
595 11
        $document->load($filename);
596
597 11
        $previousUseErrors = libxml_use_internal_errors(true);
598
599
        try {
600 11
            libxml_clear_errors();
601
602 11
            if (! $document->schemaValidate(__DIR__ . '/../../../../../../doctrine-mongo-mapping.xsd')) {
603 2
                throw MappingException::xmlMappingFileInvalid($filename, $this->formatErrors(libxml_get_errors()));
604
            }
605 9
        } finally {
606 11
            libxml_use_internal_errors($previousUseErrors);
607
        }
608 9
    }
609
610
    /**
611
     * @param LibXMLError[] $xmlErrors
612
     */
613 2
    private function formatErrors(array $xmlErrors) : string
614
    {
615
        return implode("\n", array_map(static function (LibXMLError $error) : string {
616 2
            return sprintf('Line %d:%d: %s', $error->line, $error->column, $error->message);
617 2
        }, $xmlErrors));
618
    }
619
620 8
    private function addGridFSMappings(ClassMetadata $class, SimpleXMLElement $xmlRoot) : void
621
    {
622 8
        if (! $class->isFile) {
623 7
            return;
624
        }
625
626 1
        foreach (self::DEFAULT_GRIDFS_MAPPINGS as $name => $mapping) {
627 1
            if (! isset($xmlRoot->{$name})) {
628
                continue;
629
            }
630
631 1
            if (isset($xmlRoot->{$name}->attributes()['field-name'])) {
632 1
                $mapping['fieldName'] = (string) $xmlRoot->{$name}->attributes()['field-name'];
633
            }
634
635 1
            $this->addFieldMapping($class, $mapping);
636
        }
637
638 1
        if (! isset($xmlRoot->metadata)) {
639
            return;
640
        }
641
642 1
        $xmlRoot->metadata->addAttribute('field', 'metadata');
643 1
        $this->addEmbedMapping($class, $xmlRoot->metadata, 'one');
644 1
    }
645
}
646