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

XmlDriver   F

Complexity

Total Complexity 160

Size/Duplication

Total Lines 605
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 73.1%

Importance

Changes 0
Metric Value
wmc 160
lcom 1
cbo 2
dl 0
loc 605
ccs 250
cts 342
cp 0.731
rs 1.995
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
F loadMetadataForClass() 0 186 56
F addEmbedMapping() 0 36 11
F addReferenceMapping() 0 69 25
B getPartialFilterExpression() 0 34 11
B addFieldMapping() 0 35 10
F addIndex() 0 66 19
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

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 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
Bug introduced by
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;
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...
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) {
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...
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) {
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...
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