Completed
Pull Request — master (#1790)
by Andreas
17:42
created

XmlDriver   D

Complexity

Total Complexity 152

Size/Duplication

Total Lines 514
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 69.81%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 152
lcom 1
cbo 2
dl 0
loc 514
ccs 215
cts 308
cp 0.6981
rs 4.8717
c 1
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
F addEmbedMapping() 0 36 11
F addIndex() 0 69 20
F loadMetadataForClass() 0 166 55
C addFieldMapping() 0 38 11
F addReferenceMapping() 0 69 25
C getPartialFilterExpression() 0 34 11
D setShardKey() 0 34 10
A transformReadPreference() 0 15 4
A loadMappingFile() 0 18 4

How to fix   Complexity   

Complex Class

Complex classes like XmlDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlDriver, and based on these observations, apply Extract Interface, too.

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