Completed
Push — master ( f0fe3a...13ea99 )
by Andreas
12s
created

XmlDriver   F

Complexity

Total Complexity 163

Size/Duplication

Total Lines 617
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 72.05%

Importance

Changes 0
Metric Value
wmc 163
lcom 2
cbo 1
dl 0
loc 617
ccs 250
cts 347
cp 0.7205
rs 1.983
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
F loadMetadataForClass() 0 189 57
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

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