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