Completed
Pull Request — master (#1903)
by Andreas
20:58
created

XmlDriver::addFieldMapping()   C

Complexity

Conditions 12
Paths 69

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 12.4202

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 24
cts 28
cp 0.8571
rs 6.9666
c 0
b 0
f 0
cc 12
nc 69
nop 3
crap 12.4202

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