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

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

missing required arguments.

Bug Major

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