Completed
Push — master ( 0defe1...697e55 )
by Grégoire
14s queued 10s
created

XmlDriver::setShardKey()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 16.8468

Importance

Changes 0
Metric Value
dl 0
loc 34
ccs 13
cts 22
cp 0.5909
rs 7.6666
c 0
b 0
f 0
cc 10
nc 16
nop 2
crap 16.8468

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