Completed
Push — master ( a8fe50...bce26f )
by Maciej
13s
created

ODM/MongoDB/Mapping/Driver/AnnotationDriver.php (4 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping\Driver;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\Reader;
9
use Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
10
use Doctrine\ODM\MongoDB\Events;
11
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
12
use Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractIndex;
13
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
14
use Doctrine\ODM\MongoDB\Mapping\MappingException;
15
use const E_USER_DEPRECATED;
16
use function array_merge;
17
use function array_replace;
18
use function constant;
19
use function get_class;
20
use function is_array;
21
use function ksort;
22
use function reset;
23
use function trigger_error;
24
25
/**
26
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
27
 *
28
 */
29
class AnnotationDriver extends AbstractAnnotationDriver
30
{
31
    /** @var int[] */
32
    protected $entityAnnotationClasses = [
33
        ODM\Document::class            => 1,
34
        ODM\MappedSuperclass::class    => 2,
35
        ODM\EmbeddedDocument::class    => 3,
36
        ODM\QueryResultDocument::class => 4,
37
        ODM\File::class                => 5,
38
    ];
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 1408
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class): void
44
    {
45
        /** @var ClassMetadata $class */
46 1408
        $reflClass = $class->getReflectionClass();
47
48 1408
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
49
50 1408
        $documentAnnots = [];
51 1408
        foreach ($classAnnotations as $annot) {
52 1406
            $classAnnotations[get_class($annot)] = $annot;
53
54 1406
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
55 1406
                if ($annot instanceof $annotClass) {
56 1406
                    $documentAnnots[$i] = $annot;
57 1406
                    continue 2;
58
                }
59
            }
60
61
            // non-document class annotations
62 937
            if ($annot instanceof ODM\AbstractIndex) {
63 6
                $this->addIndex($class, $annot);
64
            }
65 937
            if ($annot instanceof ODM\Indexes) {
66 89
                foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
67 89
                    $this->addIndex($class, $index);
68
                }
69 922
            } elseif ($annot instanceof ODM\InheritanceType) {
70 858
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
71 920
            } elseif ($annot instanceof ODM\DiscriminatorField) {
72 131
                $class->setDiscriminatorField($annot->value);
73 919
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
74 131
                $class->setDiscriminatorMap($annot->value);
0 ignored issues
show
It seems like $annot->value can also be of type string; however, Doctrine\ODM\MongoDB\Map...::setDiscriminatorMap() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
75 858
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
76
                $class->setDiscriminatorValue($annot->value);
0 ignored issues
show
It seems like $annot->value can also be of type array; however, Doctrine\ODM\MongoDB\Map...setDiscriminatorValue() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
77 858
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
78 66
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
79 856
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
80 68
                $class->setDefaultDiscriminatorValue($annot->value);
81 851
            } elseif ($annot instanceof ODM\ReadPreference) {
82 937
                $class->setReadPreference($annot->value, $annot->tags);
0 ignored issues
show
It seems like $annot->value can also be of type array; however, Doctrine\ODM\MongoDB\Map...ta::setReadPreference() does only seem to accept null|string|integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
83
            }
84
        }
85
86 1408
        if (! $documentAnnots) {
87 3
            throw MappingException::classIsNotAValidDocument($className);
88
        }
89
90
        // find the winning document annotation
91 1406
        ksort($documentAnnots);
92 1406
        $documentAnnot = reset($documentAnnots);
93
94 1406
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
95 848
            $class->isMappedSuperclass = true;
96 1405
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
97 260
            $class->isEmbeddedDocument = true;
98 1398
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
99 67
            $class->isQueryResultDocument = true;
100 1398
        } elseif ($documentAnnot instanceof ODM\File) {
101 78
            $class->isFile = true;
102
103 78
            if ($documentAnnot->chunkSizeBytes !== null) {
104 77
                $class->setChunkSizeBytes($documentAnnot->chunkSizeBytes);
105
            }
106
        }
107
108 1406
        if (isset($documentAnnot->db)) {
109 1
            $class->setDatabase($documentAnnot->db);
110
        }
111 1406
        if (isset($documentAnnot->collection)) {
112 897
            $class->setCollection($documentAnnot->collection);
113
        }
114
        // Store bucketName as collection name for GridFS files
115 1406
        if (isset($documentAnnot->bucketName)) {
116 78
            $class->setBucketName($documentAnnot->bucketName);
117
        }
118 1406
        if (isset($documentAnnot->repositoryClass)) {
119 77
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
120
        }
121 1406
        if (isset($documentAnnot->writeConcern)) {
122 10
            $class->setWriteConcern($documentAnnot->writeConcern);
123
        }
124 1406
        if (isset($documentAnnot->indexes)) {
125 1405
            foreach ($documentAnnot->indexes as $index) {
126
                $this->addIndex($class, $index);
127
            }
128
        }
129 1406
        if (! empty($documentAnnot->readOnly)) {
130 5
            $class->markReadOnly();
131
        }
132
133 1406
        foreach ($reflClass->getProperties() as $property) {
134 1405
            if (($class->isMappedSuperclass && ! $property->isPrivate())
135
                ||
136 1405
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
137 890
                continue;
138
            }
139
140 1404
            $indexes = [];
141 1404
            $mapping = ['fieldName' => $property->getName()];
142 1404
            $fieldAnnot = null;
143
144 1404
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
145 1404
                if ($annot instanceof ODM\AbstractField) {
146 1404
                    $fieldAnnot = $annot;
147 1404
                    if ($annot->isDeprecated()) {
148
                        @trigger_error($annot->getDeprecationMessage(), E_USER_DEPRECATED);
149
                    }
150
                }
151 1404
                if ($annot instanceof ODM\AbstractIndex) {
152 179
                    $indexes[] = $annot;
153
                }
154 1404
                if ($annot instanceof ODM\Indexes) {
155
                    foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
156
                        $indexes[] = $index;
157
                    }
158 1404
                } elseif ($annot instanceof ODM\AlsoLoad) {
159 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
160 1404
                } elseif ($annot instanceof ODM\Version) {
161 87
                    $mapping['version'] = true;
162 1404
                } elseif ($annot instanceof ODM\Lock) {
163 1404
                    $mapping['lock'] = true;
164
                }
165
            }
166
167 1404
            if ($fieldAnnot) {
168 1404
                $mapping = array_replace($mapping, (array) $fieldAnnot);
169 1404
                $class->mapField($mapping);
170
            }
171
172 1404
            if (! $indexes) {
173 1404
                continue;
174
            }
175
176 179
            foreach ($indexes as $index) {
177 179
                $name = $mapping['name'] ?? $mapping['fieldName'];
178 179
                $keys = [$name => $index->order ?: 'asc'];
179 179
                $this->addIndex($class, $index, $keys);
180
            }
181
        }
182
183
        // Set shard key after all fields to ensure we mapped all its keys
184 1404
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
185 82
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
186
        }
187
188
        /** @var \ReflectionMethod $method */
189 1403
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
190
            /* Filter for the declaring class only. Callbacks from parent
191
             * classes will already be registered.
192
             */
193 1161
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
194 855
                continue;
195
            }
196
197 1161
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
198 845
                if ($annot instanceof ODM\AlsoLoad) {
199 13
                    $class->registerAlsoLoadMethod($method->getName(), $annot->value);
200
                }
201
202 845
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
203 84
                    continue;
204
                }
205
206 826
                if ($annot instanceof ODM\PrePersist) {
207 811
                    $class->addLifecycleCallback($method->getName(), Events::prePersist);
208 91
                } elseif ($annot instanceof ODM\PostPersist) {
209 10
                    $class->addLifecycleCallback($method->getName(), Events::postPersist);
210 91
                } elseif ($annot instanceof ODM\PreUpdate) {
211 15
                    $class->addLifecycleCallback($method->getName(), Events::preUpdate);
212 86
                } elseif ($annot instanceof ODM\PostUpdate) {
213 75
                    $class->addLifecycleCallback($method->getName(), Events::postUpdate);
214 84
                } elseif ($annot instanceof ODM\PreRemove) {
215 82
                    $class->addLifecycleCallback($method->getName(), Events::preRemove);
216 84
                } elseif ($annot instanceof ODM\PostRemove) {
217 82
                    $class->addLifecycleCallback($method->getName(), Events::postRemove);
218 84
                } elseif ($annot instanceof ODM\PreLoad) {
219 83
                    $class->addLifecycleCallback($method->getName(), Events::preLoad);
220 83
                } elseif ($annot instanceof ODM\PostLoad) {
221 82
                    $class->addLifecycleCallback($method->getName(), Events::postLoad);
222 11
                } elseif ($annot instanceof ODM\PreFlush) {
223 1161
                    $class->addLifecycleCallback($method->getName(), Events::preFlush);
224
                }
225
            }
226
        }
227 1403
    }
228
229 206
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []): void
230
    {
231 206
        $keys = array_merge($keys, $index->keys);
232 206
        $options = [];
233 206
        $allowed = ['name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
234 206
        foreach ($allowed as $name) {
235 206
            if (! isset($index->$name)) {
236 206
                continue;
237
            }
238
239 206
            $options[$name] = $index->$name;
240
        }
241 206
        if (! empty($index->partialFilterExpression)) {
242 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
243
        }
244 206
        $options = array_merge($options, $index->options);
245 206
        $class->addIndex($keys, $options);
246 206
    }
247
248
    /**
249
     *
250
     * @throws MappingException
251
     */
252 82
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey): void
253
    {
254 82
        $options = [];
255 82
        $allowed = ['unique', 'numInitialChunks'];
256 82
        foreach ($allowed as $name) {
257 82
            if (! isset($shardKey->$name)) {
258 82
                continue;
259
            }
260
261
            $options[$name] = $shardKey->$name;
262
        }
263
264 82
        $class->setShardKey($shardKey->keys, $options);
265 81
    }
266
267
    /**
268
     * Factory method for the Annotation Driver
269
     *
270
     * @param array|string $paths
271
     */
272 1667
    public static function create($paths = [], ?Reader $reader = null): AnnotationDriver
273
    {
274 1667
        if ($reader === null) {
275 1667
            $reader = new AnnotationReader();
276
        }
277 1667
        return new self($reader, $paths);
0 ignored issues
show
It seems like $paths defined by parameter $paths on line 272 can also be of type array; however, Doctrine\Common\Persiste...onDriver::__construct() does only seem to accept string|array<integer,string>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
278
    }
279
}
280