Completed
Push — master ( bb3219...cc367c )
by Andreas
15:44
created

XmlDriver   F

Complexity

Total Complexity 163

Size/Duplication

Total Lines 614
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 72.62%

Importance

Changes 0
Metric Value
wmc 163
lcom 2
cbo 1
dl 0
loc 614
ccs 252
cts 347
cp 0.7262
rs 1.986
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B addFieldMapping() 0 38 11
F addEmbedMapping() 0 36 11
F addReferenceMapping() 0 69 25
F addIndex() 0 69 20
B getPartialFilterExpression() 0 34 11
B setShardKey() 0 34 10
A transformReadPreference() 0 15 4
A loadMappingFile() 0 21 4
A validateSchema() 0 17 2
A formatErrors() 0 6 1
B addGridFSMappings() 0 25 6
F loadMetadataForClass() 0 189 57

How to fix   Complexity   

Complex Class

Complex classes like XmlDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlDriver, and based on these observations, apply Extract Interface, too.

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 constant;
18
use function count;
19
use function current;
20
use function explode;
21
use function implode;
22
use function in_array;
23
use function is_numeric;
24
use function iterator_to_array;
25
use function libxml_clear_errors;
26
use function libxml_get_errors;
27
use function libxml_use_internal_errors;
28
use function next;
29
use function preg_match;
30
use function simplexml_load_file;
31
use function sprintf;
32
use function strtoupper;
33
use function trim;
34
35
/**
36
 * XmlDriver is a metadata driver that enables mapping through XML files.
37
 */
38
class XmlDriver extends FileDriver
39
{
40
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
41
42
    private const DEFAULT_GRIDFS_MAPPINGS = [
43
        'length' => [
44
            'name' => 'length',
45
            'type' => 'int',
46
            'notSaved' => true,
47
        ],
48
        'chunk-size' => [
49
            'name' => 'chunkSize',
50
            'type' => 'int',
51
            'notSaved' => true,
52
        ],
53
        'filename' => [
54
            'name' => 'filename',
55
            'type' => 'string',
56
            'notSaved' => true,
57
        ],
58
        'upload-date' => [
59
            'name' => 'uploadDate',
60
            'type' => 'date',
61
            'notSaved' => true,
62
        ],
63
    ];
64
65
    /**
66
     * {@inheritDoc}
67
     */
68 16
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
69
    {
70 16
        parent::__construct($locator, $fileExtension);
71 16
    }
72
73
    /**
74
     * {@inheritDoc}
75
     */
76 11
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class)
77
    {
78
        /** @var ClassMetadata $class */
79
        /** @var SimpleXMLElement $xmlRoot */
80 11
        $xmlRoot = $this->getElement($className);
81 9
        if (! $xmlRoot) {
82
            return;
83
        }
84
85 9
        if ($xmlRoot->getName() === 'document') {
86 8
            if (isset($xmlRoot['repository-class'])) {
87 8
                $class->setCustomRepositoryClass((string) $xmlRoot['repository-class']);
88
            }
89 3
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
90 1
            $class->setCustomRepositoryClass(
91 1
                isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null
92
            );
93 1
            $class->isMappedSuperclass = true;
94 2
        } elseif ($xmlRoot->getName() === 'embedded-document') {
95 1
            $class->isEmbeddedDocument = true;
96 2
        } elseif ($xmlRoot->getName() === 'query-result-document') {
97 1
            $class->isQueryResultDocument = true;
98 1
        } elseif ($xmlRoot->getName() === 'gridfs-file') {
99 1
            $class->isFile = true;
100
101 1
            if (isset($xmlRoot['chunk-size-bytes'])) {
102 1
                $class->setChunkSizeBytes((int) $xmlRoot['chunk-size-bytes']);
103
            }
104
        }
105
106 9
        if (isset($xmlRoot['db'])) {
107 3
            $class->setDatabase((string) $xmlRoot['db']);
108
        }
109
110 9
        if (isset($xmlRoot['collection'])) {
111 5
            if (isset($xmlRoot['capped-collection'])) {
112
                $config           = ['name' => (string) $xmlRoot['collection']];
113
                $config['capped'] = (bool) $xmlRoot['capped-collection'];
114
                if (isset($xmlRoot['capped-collection-max'])) {
115
                    $config['max'] = (int) $xmlRoot['capped-collection-max'];
116
                }
117
                if (isset($xmlRoot['capped-collection-size'])) {
118
                    $config['size'] = (int) $xmlRoot['capped-collection-size'];
119
                }
120
                $class->setCollection($config);
121
            } else {
122 5
                $class->setCollection((string) $xmlRoot['collection']);
123
            }
124
        }
125 9
        if (isset($xmlRoot['bucket-name'])) {
126
            $class->setBucketName((string) $xmlRoot['bucket-name']);
127
        }
128 9
        if (isset($xmlRoot['write-concern'])) {
129
            $class->setWriteConcern((string) $xmlRoot['write-concern']);
130
        }
131 9
        if (isset($xmlRoot['inheritance-type'])) {
132
            $inheritanceType = (string) $xmlRoot['inheritance-type'];
133
            $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $inheritanceType));
134
        }
135 9
        if (isset($xmlRoot['change-tracking-policy'])) {
136 1
            $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . strtoupper((string) $xmlRoot['change-tracking-policy'])));
137
        }
138 9
        if (isset($xmlRoot->{'discriminator-field'})) {
139
            $discrField = $xmlRoot->{'discriminator-field'};
140
            $class->setDiscriminatorField((string) $discrField['name']);
141
        }
142 9
        if (isset($xmlRoot->{'discriminator-map'})) {
143
            $map = [];
144
            foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
145
                $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
146
            }
147
            $class->setDiscriminatorMap($map);
148
        }
149 9
        if (isset($xmlRoot->{'default-discriminator-value'})) {
150
            $class->setDefaultDiscriminatorValue((string) $xmlRoot->{'default-discriminator-value'}['value']);
151
        }
152 9
        if (isset($xmlRoot->{'indexes'})) {
153 1
            foreach ($xmlRoot->{'indexes'}->{'index'} as $index) {
154 1
                $this->addIndex($class, $index);
155
            }
156
        }
157 9
        if (isset($xmlRoot->{'shard-key'})) {
158
            $this->setShardKey($class, $xmlRoot->{'shard-key'}[0]);
159
        }
160 9
        if (isset($xmlRoot['read-only']) && (string) $xmlRoot['read-only'] === 'true') {
161
            $class->markReadOnly();
162
        }
163 9
        if (isset($xmlRoot->{'read-preference'})) {
164
            $class->setReadPreference(...$this->transformReadPreference($xmlRoot->{'read-preference'}));
165
        }
166
167 9
        if (isset($xmlRoot->id)) {
168 7
            $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...
169
            $mapping = [
170 7
                'id' => true,
171
                'fieldName' => 'id',
172
            ];
173
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['drop-dups'])) {
290
            $options['dropDups'] = (bool) $mapping['drop-dups'];
291
        }
292 1
        if (isset($mapping['index-name'])) {
293
            $options['name'] = (string) $mapping['index-name'];
294
        }
295 1
        if (isset($mapping['sparse'])) {
296 1
            $options['sparse'] = (bool) $mapping['sparse'];
297
        }
298 1
        if (isset($mapping['unique'])) {
299 1
            $options['unique'] = (bool) $mapping['unique'];
300
        }
301
302 1
        $class->addIndex($keys, $options);
303 1
    }
304
305 2
    private function addEmbedMapping(ClassMetadata $class, SimpleXMLElement $embed, string $type) : void
306
    {
307 2
        $attributes      = $embed->attributes();
308 2
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
309
        $mapping         = [
310 2
            'type'            => $type,
311
            'embedded'        => true,
312 2
            'targetDocument'  => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
313 2
            'collectionClass' => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
314 2
            'name'            => (string) $attributes['field'],
315 2
            'strategy'        => (string) ($attributes['strategy'] ?? $defaultStrategy),
316
        ];
317 2
        if (isset($attributes['field-name'])) {
318
            $mapping['fieldName'] = (string) $attributes['field-name'];
319
        }
320 2
        if (isset($embed->{'discriminator-field'})) {
321
            $attr                          = $embed->{'discriminator-field'};
322
            $mapping['discriminatorField'] = (string) $attr['name'];
323
        }
324 2
        if (isset($embed->{'discriminator-map'})) {
325
            foreach ($embed->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
326
                $attr                                                 = $discriminatorMapping->attributes();
327
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
328
            }
329
        }
330 2
        if (isset($embed->{'default-discriminator-value'})) {
331
            $mapping['defaultDiscriminatorValue'] = (string) $embed->{'default-discriminator-value'}['value'];
332
        }
333 2
        if (isset($attributes['not-saved'])) {
334
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
335
        }
336 2
        if (isset($attributes['also-load'])) {
337
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
338
        }
339 2
        $this->addFieldMapping($class, $mapping);
340 2
    }
341
342 3
    private function addReferenceMapping(ClassMetadata $class, $reference, string $type) : void
343
    {
344 3
        $cascade = array_keys((array) $reference->cascade);
345 3
        if (count($cascade) === 1) {
346 1
            $cascade = current($cascade) ?: next($cascade);
347
        }
348 3
        $attributes      = $reference->attributes();
349 3
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
350
        $mapping         = [
351 3
            'cascade'          => $cascade,
352 3
            'orphanRemoval'    => isset($attributes['orphan-removal']) ? ((string) $attributes['orphan-removal'] === 'true') : false,
353 3
            'type'             => $type,
354
            'reference'        => true,
355 3
            'storeAs'          => (string) ($attributes['store-as'] ?? ClassMetadata::REFERENCE_STORE_AS_DB_REF),
356 3
            'targetDocument'   => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
357 3
            'collectionClass'  => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
358 3
            'name'             => (string) $attributes['field'],
359 3
            'strategy'         => (string) ($attributes['strategy'] ?? $defaultStrategy),
360 3
            'inversedBy'       => isset($attributes['inversed-by']) ? (string) $attributes['inversed-by'] : null,
361 3
            'mappedBy'         => isset($attributes['mapped-by']) ? (string) $attributes['mapped-by'] : null,
362 3
            'repositoryMethod' => isset($attributes['repository-method']) ? (string) $attributes['repository-method'] : null,
363 3
            'limit'            => isset($attributes['limit']) ? (int) $attributes['limit'] : null,
364 3
            'skip'             => isset($attributes['skip']) ? (int) $attributes['skip'] : null,
365
            'prime'            => [],
366
        ];
367
368 3
        if (isset($attributes['field-name'])) {
369
            $mapping['fieldName'] = (string) $attributes['field-name'];
370
        }
371 3
        if (isset($reference->{'discriminator-field'})) {
372
            $attr                          = $reference->{'discriminator-field'};
373
            $mapping['discriminatorField'] = (string) $attr['name'];
374
        }
375 3
        if (isset($reference->{'discriminator-map'})) {
376
            foreach ($reference->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
377
                $attr                                                 = $discriminatorMapping->attributes();
378
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
379
            }
380
        }
381 3
        if (isset($reference->{'default-discriminator-value'})) {
382
            $mapping['defaultDiscriminatorValue'] = (string) $reference->{'default-discriminator-value'}['value'];
383
        }
384 3
        if (isset($reference->{'sort'})) {
385
            foreach ($reference->{'sort'}->{'sort'} as $sort) {
386
                $attr                                     = $sort->attributes();
387
                $mapping['sort'][(string) $attr['field']] = (string) ($attr['order'] ?? 'asc');
388
            }
389
        }
390 3
        if (isset($reference->{'criteria'})) {
391
            foreach ($reference->{'criteria'}->{'criteria'} as $criteria) {
392
                $attr                                         = $criteria->attributes();
393
                $mapping['criteria'][(string) $attr['field']] = (string) $attr['value'];
394
            }
395
        }
396 3
        if (isset($attributes['not-saved'])) {
397
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
398
        }
399 3
        if (isset($attributes['also-load'])) {
400
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
401
        }
402 3
        if (isset($reference->{'prime'})) {
403 1
            foreach ($reference->{'prime'}->{'field'} as $field) {
404 1
                $attr               = $field->attributes();
405 1
                $mapping['prime'][] = (string) $attr['name'];
406
            }
407
        }
408
409 3
        $this->addFieldMapping($class, $mapping);
410 3
    }
411
412 1
    private function addIndex(ClassMetadata $class, SimpleXMLElement $xmlIndex) : void
413
    {
414 1
        $attributes = $xmlIndex->attributes();
415
416 1
        $keys = [];
417
418 1
        foreach ($xmlIndex->{'key'} as $key) {
419 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
420
        }
421
422 1
        $options = [];
423
424 1
        if (isset($attributes['background'])) {
425
            $options['background'] = ((string) $attributes['background'] === 'true');
426
        }
427 1
        if (isset($attributes['drop-dups'])) {
428
            $options['dropDups'] = ((string) $attributes['drop-dups'] === 'true');
429
        }
430 1
        if (isset($attributes['name'])) {
431
            $options['name'] = (string) $attributes['name'];
432
        }
433 1
        if (isset($attributes['sparse'])) {
434
            $options['sparse'] = ((string) $attributes['sparse'] === 'true');
435
        }
436 1
        if (isset($attributes['unique'])) {
437
            $options['unique'] = ((string) $attributes['unique'] === 'true');
438
        }
439
440 1
        if (isset($xmlIndex->{'option'})) {
441
            foreach ($xmlIndex->{'option'} as $option) {
442
                $value = (string) $option['value'];
443
                if ($value === 'true') {
444
                    $value = true;
445
                } elseif ($value === 'false') {
446
                    $value = false;
447
                } elseif (is_numeric($value)) {
448
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
449
                }
450
                $options[(string) $option['name']] = $value;
451
            }
452
        }
453
454 1
        if (isset($xmlIndex->{'partial-filter-expression'})) {
455 1
            $partialFilterExpressionMapping = $xmlIndex->{'partial-filter-expression'};
456
457 1
            if (isset($partialFilterExpressionMapping->and)) {
458 1
                foreach ($partialFilterExpressionMapping->and as $and) {
459 1
                    if (! isset($and->field)) {
460
                        continue;
461
                    }
462
463 1
                    $partialFilterExpression = $this->getPartialFilterExpression($and->field);
464 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...
465
                        continue;
466
                    }
467
468 1
                    $options['partialFilterExpression']['$and'][] = $partialFilterExpression;
469
                }
470 1
            } elseif (isset($partialFilterExpressionMapping->field)) {
471 1
                $partialFilterExpression = $this->getPartialFilterExpression($partialFilterExpressionMapping->field);
472
473 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...
474 1
                    $options['partialFilterExpression'] = $partialFilterExpression;
475
                }
476
            }
477
        }
478
479 1
        $class->addIndex($keys, $options);
480 1
    }
481
482 1
    private function getPartialFilterExpression(SimpleXMLElement $fields) : array
483
    {
484 1
        $partialFilterExpression = [];
485 1
        foreach ($fields as $field) {
486 1
            $operator = (string) $field['operator'] ?: null;
487
488 1
            if (! isset($field['value'])) {
489 1
                if (! isset($field->field)) {
490
                    continue;
491
                }
492
493 1
                $nestedExpression = $this->getPartialFilterExpression($field->field);
494 1
                if (! $nestedExpression) {
495
                    continue;
496
                }
497
498 1
                $value = $nestedExpression;
499
            } else {
500 1
                $value = trim((string) $field['value']);
501
            }
502
503 1
            if ($value === 'true') {
504
                $value = true;
505 1
            } elseif ($value === 'false') {
506
                $value = false;
507 1
            } elseif (is_numeric($value)) {
508 1
                $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
509
            }
510
511 1
            $partialFilterExpression[(string) $field['name']] = $operator ? ['$' . $operator => $value] : $value;
512
        }
513
514 1
        return $partialFilterExpression;
515
    }
516
517 1
    private function setShardKey(ClassMetadata $class, SimpleXMLElement $xmlShardkey) : void
518
    {
519 1
        $attributes = $xmlShardkey->attributes();
520
521 1
        $keys    = [];
522 1
        $options = [];
523 1
        foreach ($xmlShardkey->{'key'} as $key) {
524 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
525
        }
526
527 1
        if (isset($attributes['unique'])) {
528 1
            $options['unique'] = ((string) $attributes['unique'] === 'true');
529
        }
530
531 1
        if (isset($attributes['numInitialChunks'])) {
532 1
            $options['numInitialChunks'] = (int) $attributes['numInitialChunks'];
533
        }
534
535 1
        if (isset($xmlShardkey->{'option'})) {
536
            foreach ($xmlShardkey->{'option'} as $option) {
537
                $value = (string) $option['value'];
538
                if ($value === 'true') {
539
                    $value = true;
540
                } elseif ($value === 'false') {
541
                    $value = false;
542
                } elseif (is_numeric($value)) {
543
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
544
                }
545
                $options[(string) $option['name']] = $value;
546
            }
547
        }
548
549 1
        $class->setShardKey($keys, $options);
550 1
    }
551
552
    /**
553
     * Parses <read-preference> to a format suitable for the underlying driver.
554
     *
555
     * list($readPreference, $tags) = $this->transformReadPreference($xml->{read-preference});
556
     */
557
    private function transformReadPreference(SimpleXMLElement $xmlReadPreference) : array
558
    {
559
        $tags = null;
560
        if (isset($xmlReadPreference->{'tag-set'})) {
561
            $tags = [];
562
            foreach ($xmlReadPreference->{'tag-set'} as $tagSet) {
563
                $set = [];
564
                foreach ($tagSet->tag as $tag) {
565
                    $set[(string) $tag['name']] = (string) $tag['value'];
566
                }
567
                $tags[] = $set;
568
            }
569
        }
570
        return [(string) $xmlReadPreference['mode'], $tags];
571
    }
572
573
    /**
574
     * {@inheritDoc}
575
     */
576 11
    protected function loadMappingFile($file) : array
577
    {
578 11
        $result = [];
579
580 11
        $this->validateSchema($file);
581
582 9
        $xmlElement = simplexml_load_file($file);
583
584 9
        foreach (['document', 'embedded-document', 'mapped-superclass', 'query-result-document', 'gridfs-file'] as $type) {
585 9
            if (! isset($xmlElement->$type)) {
586 9
                continue;
587
            }
588
589 9
            foreach ($xmlElement->$type as $documentElement) {
590 9
                $documentName          = (string) $documentElement['name'];
591 9
                $result[$documentName] = $documentElement;
592
            }
593
        }
594
595 9
        return $result;
596
    }
597
598 11
    private function validateSchema(string $filename) : void
599
    {
600 11
        $document = new DOMDocument();
601 11
        $document->load($filename);
602
603 11
        $previousUseErrors = libxml_use_internal_errors(true);
604
605
        try {
606 11
            libxml_clear_errors();
607
608 11
            if (! $document->schemaValidate(__DIR__ . '/../../../../../../doctrine-mongo-mapping.xsd')) {
609 2
                throw MappingException::xmlMappingFileInvalid($filename, $this->formatErrors(libxml_get_errors()));
610
            }
611 9
        } finally {
612 11
            libxml_use_internal_errors($previousUseErrors);
613
        }
614 9
    }
615
616
    /**
617
     * @param LibXMLError[] $xmlErrors
618
     */
619 2
    private function formatErrors(array $xmlErrors) : string
620
    {
621
        return implode("\n", array_map(static function (LibXMLError $error) : string {
622 2
            return sprintf('Line %d:%d: %s', $error->line, $error->column, $error->message);
623 2
        }, $xmlErrors));
624
    }
625
626 8
    private function addGridFSMappings(ClassMetadata $class, SimpleXMLElement $xmlRoot) : void
627
    {
628 8
        if (! $class->isFile) {
629 7
            return;
630
        }
631
632 1
        foreach (self::DEFAULT_GRIDFS_MAPPINGS as $name => $mapping) {
633 1
            if (! isset($xmlRoot->{$name})) {
634
                continue;
635
            }
636
637 1
            if (isset($xmlRoot->{$name}->attributes()['fieldName'])) {
638 1
                $mapping['fieldName'] = (string) $xmlRoot->{$name}->attributes()['fieldName'];
639
            }
640
641 1
            $this->addFieldMapping($class, $mapping);
642
        }
643
644 1
        if (! isset($xmlRoot->metadata)) {
645
            return;
646
        }
647
648 1
        $xmlRoot->metadata->addAttribute('field', 'metadata');
649 1
        $this->addEmbedMapping($class, $xmlRoot->metadata, 'one');
650 1
    }
651
}
652