Completed
Pull Request — master (#1790)
by Andreas
15:40 queued 17s
created

XmlDriver::addGridFSMappings()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.105

Importance

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