Completed
Push — master ( c6000a...4ab32a )
by Maciej
15:09
created

XmlDriver::addIndex()   F

Complexity

Conditions 20
Paths 640

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 42.1225

Importance

Changes 0
Metric Value
dl 0
loc 69
ccs 26
cts 42
cp 0.619
rs 0.5
c 0
b 0
f 0
cc 20
nc 640
nop 2
crap 42.1225

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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