Completed
Push — master ( 4ab32a...22189c )
by Maciej
17s queued 10s
created

Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
    /**
42
     * {@inheritDoc}
43
     */
44 14
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
45
    {
46 14
        parent::__construct($locator, $fileExtension);
47 14
    }
48
49
    /**
50
     * {@inheritDoc}
51
     */
52 8
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class)
53
    {
54
        /** @var ClassMetadata $class */
55
        /** @var \SimpleXMLElement $xmlRoot */
56 8
        $xmlRoot = $this->getElement($className);
57 6
        if (! $xmlRoot) {
58
            return;
59
        }
60
61 6
        if ($xmlRoot->getName() === 'document') {
62 6
            if (isset($xmlRoot['repository-class'])) {
63 6
                $class->setCustomRepositoryClass((string) $xmlRoot['repository-class']);
64
            }
65 2
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
66 1
            $class->setCustomRepositoryClass(
67 1
                isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null
68
            );
69 1
            $class->isMappedSuperclass = true;
0 ignored issues
show
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
70 1
        } elseif ($xmlRoot->getName() === 'embedded-document') {
71 1
            $class->isEmbeddedDocument = true;
0 ignored issues
show
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
72 1
        } elseif ($xmlRoot->getName() === 'query-result-document') {
73 1
            $class->isQueryResultDocument = true;
0 ignored issues
show
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
74
        }
75 6
        if (isset($xmlRoot['db'])) {
76 3
            $class->setDatabase((string) $xmlRoot['db']);
77
        }
78 6
        if (isset($xmlRoot['collection'])) {
79 5
            if (isset($xmlRoot['capped-collection'])) {
80
                $config = ['name' => (string) $xmlRoot['collection']];
81
                $config['capped'] = (bool) $xmlRoot['capped-collection'];
82
                if (isset($xmlRoot['capped-collection-max'])) {
83
                    $config['max'] = (int) $xmlRoot['capped-collection-max'];
84
                }
85
                if (isset($xmlRoot['capped-collection-size'])) {
86
                    $config['size'] = (int) $xmlRoot['capped-collection-size'];
87
                }
88
                $class->setCollection($config);
89
            } else {
90 5
                $class->setCollection((string) $xmlRoot['collection']);
91
            }
92
        }
93 6
        if (isset($xmlRoot['write-concern'])) {
94
            $class->setWriteConcern((string) $xmlRoot['write-concern']);
95
        }
96 6
        if (isset($xmlRoot['inheritance-type'])) {
97
            $inheritanceType = (string) $xmlRoot['inheritance-type'];
98
            $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $inheritanceType));
99
        }
100 6
        if (isset($xmlRoot['change-tracking-policy'])) {
101 1
            $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . strtoupper((string) $xmlRoot['change-tracking-policy'])));
102
        }
103 6
        if (isset($xmlRoot->{'discriminator-field'})) {
104
            $discrField = $xmlRoot->{'discriminator-field'};
105
            $class->setDiscriminatorField((string) $discrField['name']);
106
        }
107 6
        if (isset($xmlRoot->{'discriminator-map'})) {
108
            $map = [];
109
            foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
110
                $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
111
            }
112
            $class->setDiscriminatorMap($map);
113
        }
114 6
        if (isset($xmlRoot->{'default-discriminator-value'})) {
115
            $class->setDefaultDiscriminatorValue((string) $xmlRoot->{'default-discriminator-value'}['value']);
116
        }
117 6
        if (isset($xmlRoot->{'indexes'})) {
118 1
            foreach ($xmlRoot->{'indexes'}->{'index'} as $index) {
119 1
                $this->addIndex($class, $index);
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
120
            }
121
        }
122 6
        if (isset($xmlRoot->{'shard-key'})) {
123
            $this->setShardKey($class, $xmlRoot->{'shard-key'}[0]);
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
124
        }
125 6
        if (isset($xmlRoot['read-only']) && (string) $xmlRoot['read-only'] === 'true') {
126
            $class->markReadOnly();
127
        }
128 6
        if (isset($xmlRoot->{'read-preference'})) {
129
            $class->setReadPreference(...$this->transformReadPreference($xmlRoot->{'read-preference'}));
130
        }
131
132 6
        if (isset($xmlRoot->id)) {
133 6
            $field = $xmlRoot->id;
134
            $mapping = [
135 6
                'id' => true,
136
                'fieldName' => 'id',
137
            ];
138
139 6
            $attributes = $field->attributes();
140 6
            foreach ($attributes as $key => $value) {
141 2
                $mapping[$key] = (string) $value;
142
            }
143
144 6
            if (isset($mapping['strategy'])) {
145 2
                $mapping['options'] = [];
146 2
                if (isset($field->{'generator-option'})) {
147 1
                    foreach ($field->{'generator-option'} as $generatorOptions) {
148 1
                        $attributesGenerator = iterator_to_array($generatorOptions->attributes());
149 1
                        if (! isset($attributesGenerator['name']) || ! isset($attributesGenerator['value'])) {
150
                            continue;
151
                        }
152
153 1
                        $mapping['options'][(string) $attributesGenerator['name']] = (string) $attributesGenerator['value'];
154
                    }
155
                }
156
            }
157
158 6
            $this->addFieldMapping($class, $mapping);
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
159
        }
160
161 6
        if (isset($xmlRoot->field)) {
162 2
            foreach ($xmlRoot->field as $field) {
163 2
                $mapping = [];
164 2
                $attributes = $field->attributes();
165 2
                foreach ($attributes as $key => $value) {
166 2
                    $mapping[$key] = (string) $value;
167 2
                    $booleanAttributes = ['reference', 'embed', 'unique', 'sparse'];
168 2
                    if (! in_array($key, $booleanAttributes)) {
169 2
                        continue;
170
                    }
171
172 1
                    $mapping[$key] = ($mapping[$key] === 'true');
173
                }
174
175 2
                if (isset($attributes['not-saved'])) {
176
                    $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
177
                }
178
179 2
                if (isset($attributes['field-name'])) {
180
                    $mapping['fieldName'] = (string) $attributes['field-name'];
181
                }
182
183 2
                if (isset($attributes['also-load'])) {
184
                    $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
185 2
                } elseif (isset($attributes['version'])) {
186
                    $mapping['version'] = ((string) $attributes['version'] === 'true');
187 2
                } elseif (isset($attributes['lock'])) {
188
                    $mapping['lock'] = ((string) $attributes['lock'] === 'true');
189
                }
190
191 2
                $this->addFieldMapping($class, $mapping);
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
192
            }
193
        }
194 6
        if (isset($xmlRoot->{'embed-one'})) {
195 1
            foreach ($xmlRoot->{'embed-one'} as $embed) {
196 1
                $this->addEmbedMapping($class, $embed, 'one');
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
197
            }
198
        }
199 6
        if (isset($xmlRoot->{'embed-many'})) {
200 1
            foreach ($xmlRoot->{'embed-many'} as $embed) {
201 1
                $this->addEmbedMapping($class, $embed, 'many');
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
202
            }
203
        }
204 6
        if (isset($xmlRoot->{'reference-many'})) {
205 3
            foreach ($xmlRoot->{'reference-many'} as $reference) {
206 3
                $this->addReferenceMapping($class, $reference, 'many');
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
207
            }
208
        }
209 6
        if (isset($xmlRoot->{'reference-one'})) {
210 2
            foreach ($xmlRoot->{'reference-one'} as $reference) {
211 2
                $this->addReferenceMapping($class, $reference, 'one');
0 ignored issues
show
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
212
            }
213
        }
214 6
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
215 1
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
216 1
                $class->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ODM\MongoDB\Events::' . (string) $lifecycleCallback['type']));
217
            }
218
        }
219 6
        if (! isset($xmlRoot->{'also-load-methods'})) {
220 6
            return;
221
        }
222
223 1
        foreach ($xmlRoot->{'also-load-methods'}->{'also-load-method'} as $alsoLoadMethod) {
224 1
            $class->registerAlsoLoadMethod((string) $alsoLoadMethod['method'], (string) $alsoLoadMethod['field']);
225
        }
226 1
    }
227
228 7
    private function addFieldMapping(ClassMetadata $class, $mapping)
229
    {
230 7
        if (isset($mapping['name'])) {
231 5
            $name = $mapping['name'];
232 6
        } elseif (isset($mapping['fieldName'])) {
233 6
            $name = $mapping['fieldName'];
234
        } else {
235
            throw new \InvalidArgumentException('Cannot infer a MongoDB name from the mapping');
236
        }
237
238 7
        $class->mapField($mapping);
239
240
        // Index this field if either "index", "unique", or "sparse" are set
241 7
        if (! (isset($mapping['index']) || isset($mapping['unique']) || isset($mapping['sparse']))) {
242 7
            return;
243
        }
244
245 1
        $keys = [$name => $mapping['order'] ?? 'asc'];
246 1
        $options = [];
247
248 1
        if (isset($mapping['background'])) {
249
            $options['background'] = (bool) $mapping['background'];
250
        }
251 1
        if (isset($mapping['drop-dups'])) {
252
            $options['dropDups'] = (bool) $mapping['drop-dups'];
253
        }
254 1
        if (isset($mapping['index-name'])) {
255
            $options['name'] = (string) $mapping['index-name'];
256
        }
257 1
        if (isset($mapping['sparse'])) {
258 1
            $options['sparse'] = (bool) $mapping['sparse'];
259
        }
260 1
        if (isset($mapping['unique'])) {
261 1
            $options['unique'] = (bool) $mapping['unique'];
262
        }
263
264 1
        $class->addIndex($keys, $options);
265 1
    }
266
267 1
    private function addEmbedMapping(ClassMetadata $class, $embed, $type)
268
    {
269 1
        $attributes = $embed->attributes();
270 1
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
271
        $mapping = [
272 1
            'type'            => $type,
273
            'embedded'        => true,
274 1
            'targetDocument'  => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
275 1
            'collectionClass' => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
276 1
            'name'            => (string) $attributes['field'],
277 1
            'strategy'        => (string) ($attributes['strategy'] ?? $defaultStrategy),
278
        ];
279 1
        if (isset($attributes['field-name'])) {
280
            $mapping['fieldName'] = (string) $attributes['field-name'];
281
        }
282 1
        if (isset($embed->{'discriminator-field'})) {
283
            $attr = $embed->{'discriminator-field'};
284
            $mapping['discriminatorField'] = (string) $attr['name'];
285
        }
286 1
        if (isset($embed->{'discriminator-map'})) {
287
            foreach ($embed->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
288
                $attr = $discriminatorMapping->attributes();
289
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
290
            }
291
        }
292 1
        if (isset($embed->{'default-discriminator-value'})) {
293
            $mapping['defaultDiscriminatorValue'] = (string) $embed->{'default-discriminator-value'}['value'];
294
        }
295 1
        if (isset($attributes['not-saved'])) {
296
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
297
        }
298 1
        if (isset($attributes['also-load'])) {
299
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
300
        }
301 1
        $this->addFieldMapping($class, $mapping);
302 1
    }
303
304 4
    private function addReferenceMapping(ClassMetadata $class, $reference, $type)
305
    {
306 4
        $cascade = array_keys((array) $reference->cascade);
307 4
        if (count($cascade) === 1) {
308 1
            $cascade = current($cascade) ?: next($cascade);
309
        }
310 4
        $attributes = $reference->attributes();
311 4
        $defaultStrategy = $type === 'one' ? ClassMetadata::STORAGE_STRATEGY_SET : CollectionHelper::DEFAULT_STRATEGY;
312
        $mapping = [
313 4
            'cascade'          => $cascade,
314 4
            'orphanRemoval'    => isset($attributes['orphan-removal']) ? ((string) $attributes['orphan-removal'] === 'true') : false,
315 4
            'type'             => $type,
316
            'reference'        => true,
317 4
            'storeAs'          => (string) ($attributes['store-as'] ?? ClassMetadata::REFERENCE_STORE_AS_DB_REF),
318 4
            'targetDocument'   => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
319 4
            'collectionClass'  => isset($attributes['collection-class']) ? (string) $attributes['collection-class'] : null,
320 4
            'name'             => (string) $attributes['field'],
321 4
            'strategy'         => (string) ($attributes['strategy'] ?? $defaultStrategy),
322 4
            'inversedBy'       => isset($attributes['inversed-by']) ? (string) $attributes['inversed-by'] : null,
323 4
            'mappedBy'         => isset($attributes['mapped-by']) ? (string) $attributes['mapped-by'] : null,
324 4
            'repositoryMethod' => isset($attributes['repository-method']) ? (string) $attributes['repository-method'] : null,
325 4
            'limit'            => isset($attributes['limit']) ? (int) $attributes['limit'] : null,
326 4
            'skip'             => isset($attributes['skip']) ? (int) $attributes['skip'] : null,
327
            'prime'            => [],
328
        ];
329
330 4
        if (isset($attributes['field-name'])) {
331
            $mapping['fieldName'] = (string) $attributes['field-name'];
332
        }
333 4
        if (isset($reference->{'discriminator-field'})) {
334
            $attr = $reference->{'discriminator-field'};
335
            $mapping['discriminatorField'] = (string) $attr['name'];
336
        }
337 4
        if (isset($reference->{'discriminator-map'})) {
338
            foreach ($reference->{'discriminator-map'}->{'discriminator-mapping'} as $discriminatorMapping) {
339
                $attr = $discriminatorMapping->attributes();
340
                $mapping['discriminatorMap'][(string) $attr['value']] = (string) $attr['class'];
341
            }
342
        }
343 4
        if (isset($reference->{'default-discriminator-value'})) {
344
            $mapping['defaultDiscriminatorValue'] = (string) $reference->{'default-discriminator-value'}['value'];
345
        }
346 4
        if (isset($reference->{'sort'})) {
347
            foreach ($reference->{'sort'}->{'sort'} as $sort) {
348
                $attr = $sort->attributes();
349
                $mapping['sort'][(string) $attr['field']] = (string) ($attr['order'] ?? 'asc');
350
            }
351
        }
352 4
        if (isset($reference->{'criteria'})) {
353
            foreach ($reference->{'criteria'}->{'criteria'} as $criteria) {
354
                $attr = $criteria->attributes();
355
                $mapping['criteria'][(string) $attr['field']] = (string) $attr['value'];
356
            }
357
        }
358 4
        if (isset($attributes['not-saved'])) {
359
            $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true');
360
        }
361 4
        if (isset($attributes['also-load'])) {
362
            $mapping['alsoLoadFields'] = explode(',', $attributes['also-load']);
363
        }
364 4
        if (isset($reference->{'prime'})) {
365 1
            foreach ($reference->{'prime'}->{'field'} as $field) {
366 1
                $attr = $field->attributes();
367 1
                $mapping['prime'][] = (string) $attr['name'];
368
            }
369
        }
370
371 4
        $this->addFieldMapping($class, $mapping);
372 4
    }
373
374 1
    private function addIndex(ClassMetadata $class, \SimpleXmlElement $xmlIndex)
375
    {
376 1
        $attributes = $xmlIndex->attributes();
377
378 1
        $keys = [];
379
380 1
        foreach ($xmlIndex->{'key'} as $key) {
381 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
382
        }
383
384 1
        $options = [];
385
386 1
        if (isset($attributes['background'])) {
387
            $options['background'] = ((string) $attributes['background'] === 'true');
388
        }
389 1
        if (isset($attributes['drop-dups'])) {
390
            $options['dropDups'] = ((string) $attributes['drop-dups'] === 'true');
391
        }
392 1
        if (isset($attributes['name'])) {
393
            $options['name'] = (string) $attributes['name'];
394
        }
395 1
        if (isset($attributes['sparse'])) {
396
            $options['sparse'] = ((string) $attributes['sparse'] === 'true');
397
        }
398 1
        if (isset($attributes['unique'])) {
399
            $options['unique'] = ((string) $attributes['unique'] === 'true');
400
        }
401
402 1
        if (isset($xmlIndex->{'option'})) {
403
            foreach ($xmlIndex->{'option'} as $option) {
404
                $value = (string) $option['value'];
405
                if ($value === 'true') {
406
                    $value = true;
407
                } elseif ($value === 'false') {
408
                    $value = false;
409
                } elseif (is_numeric($value)) {
410
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
411
                }
412
                $options[(string) $option['name']] = $value;
413
            }
414
        }
415
416 1
        if (isset($xmlIndex->{'partial-filter-expression'})) {
417 1
            $partialFilterExpressionMapping = $xmlIndex->{'partial-filter-expression'};
418
419 1
            if (isset($partialFilterExpressionMapping->and)) {
420 1
                foreach ($partialFilterExpressionMapping->and as $and) {
421 1
                    if (! isset($and->field)) {
422
                        continue;
423
                    }
424
425 1
                    $partialFilterExpression = $this->getPartialFilterExpression($and->field);
426 1
                    if (! $partialFilterExpression) {
427
                        continue;
428
                    }
429
430 1
                    $options['partialFilterExpression']['$and'][] = $partialFilterExpression;
431
                }
432 1
            } elseif (isset($partialFilterExpressionMapping->field)) {
433 1
                $partialFilterExpression = $this->getPartialFilterExpression($partialFilterExpressionMapping->field);
434
435 1
                if ($partialFilterExpression) {
436 1
                    $options['partialFilterExpression'] = $partialFilterExpression;
437
                }
438
            }
439
        }
440
441 1
        $class->addIndex($keys, $options);
442 1
    }
443
444 1
    private function getPartialFilterExpression(\SimpleXMLElement $fields)
445
    {
446 1
        $partialFilterExpression = [];
447 1
        foreach ($fields as $field) {
448 1
            $operator = (string) $field['operator'] ?: null;
449
450 1
            if (! isset($field['value'])) {
451 1
                if (! isset($field->field)) {
452
                    continue;
453
                }
454
455 1
                $nestedExpression = $this->getPartialFilterExpression($field->field);
456 1
                if (! $nestedExpression) {
457
                    continue;
458
                }
459
460 1
                $value = $nestedExpression;
461
            } else {
462 1
                $value = trim((string) $field['value']);
463
            }
464
465 1
            if ($value === 'true') {
466
                $value = true;
467 1
            } elseif ($value === 'false') {
468
                $value = false;
469 1
            } elseif (is_numeric($value)) {
470 1
                $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
471
            }
472
473 1
            $partialFilterExpression[(string) $field['name']] = $operator ? ['$' . $operator => $value] : $value;
474
        }
475
476 1
        return $partialFilterExpression;
477
    }
478
479 1
    private function setShardKey(ClassMetadata $class, \SimpleXmlElement $xmlShardkey)
480
    {
481 1
        $attributes = $xmlShardkey->attributes();
482
483 1
        $keys = [];
484 1
        $options = [];
485 1
        foreach ($xmlShardkey->{'key'} as $key) {
486 1
            $keys[(string) $key['name']] = (string) ($key['order'] ?? 'asc');
487
        }
488
489 1
        if (isset($attributes['unique'])) {
490 1
            $options['unique'] = ((string) $attributes['unique'] === 'true');
491
        }
492
493 1
        if (isset($attributes['numInitialChunks'])) {
494 1
            $options['numInitialChunks'] = (int) $attributes['numInitialChunks'];
495
        }
496
497 1
        if (isset($xmlShardkey->{'option'})) {
498
            foreach ($xmlShardkey->{'option'} as $option) {
499
                $value = (string) $option['value'];
500
                if ($value === 'true') {
501
                    $value = true;
502
                } elseif ($value === 'false') {
503
                    $value = false;
504
                } elseif (is_numeric($value)) {
505
                    $value = preg_match('/^[-]?\d+$/', $value) ? (int) $value : (float) $value;
506
                }
507
                $options[(string) $option['name']] = $value;
508
            }
509
        }
510
511 1
        $class->setShardKey($keys, $options);
512 1
    }
513
514
    /**
515
     * Parses <read-preference> to a format suitable for the underlying driver.
516
     *
517
     * list($readPreference, $tags) = $this->transformReadPreference($xml->{read-preference});
518
     *
519
     * @param \SimpleXMLElement $xmlReadPreference
520
     * @return array
521
     */
522
    private function transformReadPreference($xmlReadPreference)
523
    {
524
        $tags = null;
525
        if (isset($xmlReadPreference->{'tag-set'})) {
526
            $tags = [];
527
            foreach ($xmlReadPreference->{'tag-set'} as $tagSet) {
528
                $set = [];
529
                foreach ($tagSet->tag as $tag) {
530
                    $set[(string) $tag['name']] = (string) $tag['value'];
531
                }
532
                $tags[] = $set;
533
            }
534
        }
535
        return [(string) $xmlReadPreference['mode'], $tags];
536
    }
537
538
    /**
539
     * {@inheritDoc}
540
     */
541 8
    protected function loadMappingFile($file)
542
    {
543 8
        $result = [];
544
545 8
        $this->validateSchema($file);
546
547 6
        $xmlElement = simplexml_load_file($file);
548
549 6
        foreach (['document', 'embedded-document', 'mapped-superclass', 'query-result-document'] as $type) {
550 6
            if (! isset($xmlElement->$type)) {
551 6
                continue;
552
            }
553
554 6
            foreach ($xmlElement->$type as $documentElement) {
555 6
                $documentName = (string) $documentElement['name'];
556 6
                $result[$documentName] = $documentElement;
557
            }
558
        }
559
560 6
        return $result;
561
    }
562
563 8
    private function validateSchema(string $filename): void
564
    {
565 8
        $document = new DOMDocument();
566 8
        $document->load($filename);
567
568 8
        $previousUseErrors = libxml_use_internal_errors(true);
569
570
        try {
571 8
            libxml_clear_errors();
572
573 8
            if (! $document->schemaValidate(__DIR__ . '/../../../../../../doctrine-mongo-mapping.xsd')) {
574 2
                throw MappingException::xmlMappingFileInvalid($filename, $this->formatErrors(libxml_get_errors()));
575
            }
576 6
        } finally {
577 8
            libxml_use_internal_errors($previousUseErrors);
578
        }
579 6
    }
580
581
    /**
582
     * @param LibXMLError[] $xmlErrors
583
     */
584 2
    private function formatErrors(array $xmlErrors): string
585
    {
586
        return implode("\n", array_map(function (LibXMLError $error): string {
587 2
            return sprintf('Line %d:%d: %s', $error->line, $error->column, $error->message);
588 2
        }, $xmlErrors));
589
    }
590
}
591