Completed
Push — master ( f3ed76...7db4b5 )
by Andreas
14s queued 10s
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 ReflectionMethod;
16
use const E_USER_DEPRECATED;
17
use function array_merge;
18
use function array_replace;
19
use function constant;
20
use function get_class;
21
use function is_array;
22
use function ksort;
23
use function reset;
24
use function trigger_error;
25
26
/**
27
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
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 1436
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class) : void
44
    {
45
        /** @var ClassMetadata $class */
46 1436
        $reflClass = $class->getReflectionClass();
47
48 1436
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
49
50 1436
        $documentAnnots = [];
51 1436
        foreach ($classAnnotations as $annot) {
52 1434
            $classAnnotations[get_class($annot)] = $annot;
53
54 1434
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
55 1434
                if ($annot instanceof $annotClass) {
56 1434
                    $documentAnnots[$i] = $annot;
57 1434
                    continue 2;
58
                }
59
            }
60
61
            // non-document class annotations
62 951
            if ($annot instanceof ODM\AbstractIndex) {
63 6
                $this->addIndex($class, $annot);
64
            }
65 951
            if ($annot instanceof ODM\Indexes) {
66 95
                foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
67 95
                    $this->addIndex($class, $index);
68
                }
69 936
            } elseif ($annot instanceof ODM\InheritanceType) {
70 862
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
71 934
            } elseif ($annot instanceof ODM\DiscriminatorField) {
72 131
                $class->setDiscriminatorField($annot->value);
73 933
            } 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 872
            } 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 872
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
78 66
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
79 870
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
80 68
                $class->setDefaultDiscriminatorValue($annot->value);
81 865
            } elseif ($annot instanceof ODM\ReadPreference) {
82 951
                $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 string|integer|null, 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 1436
        if (! $documentAnnots) {
87 3
            throw MappingException::classIsNotAValidDocument($className);
88
        }
89
90
        // find the winning document annotation
91 1434
        ksort($documentAnnots);
92 1434
        $documentAnnot = reset($documentAnnots);
93
94 1434
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
95 852
            $class->isMappedSuperclass = true;
96 1433
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
97 280
            $class->isEmbeddedDocument = true;
98 1426
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
99 67
            $class->isQueryResultDocument = true;
100 1426
        } 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 1434
        if (isset($documentAnnot->db)) {
109 1
            $class->setDatabase($documentAnnot->db);
110
        }
111 1434
        if (isset($documentAnnot->collection)) {
112 907
            $class->setCollection($documentAnnot->collection);
113
        }
114
        // Store bucketName as collection name for GridFS files
115 1434
        if (isset($documentAnnot->bucketName)) {
116 78
            $class->setBucketName($documentAnnot->bucketName);
117
        }
118 1434
        if (isset($documentAnnot->repositoryClass)) {
119 77
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
120
        }
121 1434
        if (isset($documentAnnot->writeConcern)) {
122 10
            $class->setWriteConcern($documentAnnot->writeConcern);
123
        }
124 1434
        if (isset($documentAnnot->indexes)) {
125 1433
            foreach ($documentAnnot->indexes as $index) {
126
                $this->addIndex($class, $index);
127
            }
128
        }
129 1434
        if (! empty($documentAnnot->readOnly)) {
130 5
            $class->markReadOnly();
131
        }
132
133 1434
        foreach ($reflClass->getProperties() as $property) {
134 1433
            if (($class->isMappedSuperclass && ! $property->isPrivate())
135
                ||
136 1433
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
137 894
                continue;
138
            }
139
140 1432
            $indexes    = [];
141 1432
            $mapping    = ['fieldName' => $property->getName()];
142 1432
            $fieldAnnot = null;
143
144 1432
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
145 1432
                if ($annot instanceof ODM\AbstractField) {
146 1432
                    $fieldAnnot = $annot;
147 1432
                    if ($annot->isDeprecated()) {
148
                        @trigger_error($annot->getDeprecationMessage(), E_USER_DEPRECATED);
149
                    }
150
                }
151 1432
                if ($annot instanceof ODM\AbstractIndex) {
152 182
                    $indexes[] = $annot;
153
                }
154 1432
                if ($annot instanceof ODM\Indexes) {
155
                    foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
156
                        $indexes[] = $index;
157
                    }
158 1432
                } elseif ($annot instanceof ODM\AlsoLoad) {
159 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
160 1432
                } elseif ($annot instanceof ODM\Version) {
161 106
                    $mapping['version'] = true;
162 1432
                } elseif ($annot instanceof ODM\Lock) {
163 1432
                    $mapping['lock'] = true;
164
                }
165
            }
166
167 1432
            if ($fieldAnnot) {
168 1432
                $mapping = array_replace($mapping, (array) $fieldAnnot);
169 1432
                $class->mapField($mapping);
170
            }
171
172 1432
            if (! $indexes) {
173 1432
                continue;
174
            }
175
176 182
            foreach ($indexes as $index) {
177 182
                $name = $mapping['name'] ?? $mapping['fieldName'];
178 182
                $keys = [$name => $index->order ?: 'asc'];
179 182
                $this->addIndex($class, $index, $keys);
180
            }
181
        }
182
183
        // Set shard key after all fields to ensure we mapped all its keys
184 1432
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
185 88
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
186
        }
187
188
        /** @var ReflectionMethod $method */
189 1431
        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 1182
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
194 859
                continue;
195
            }
196
197 1182
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
198 853
                if ($annot instanceof ODM\AlsoLoad) {
199 13
                    $class->registerAlsoLoadMethod($method->getName(), $annot->value);
200
                }
201
202 853
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
203 84
                    continue;
204
                }
205
206 834
                if ($annot instanceof ODM\PrePersist) {
207 815
                    $class->addLifecycleCallback($method->getName(), Events::prePersist);
208 95
                } elseif ($annot instanceof ODM\PostPersist) {
209 10
                    $class->addLifecycleCallback($method->getName(), Events::postPersist);
210 95
                } elseif ($annot instanceof ODM\PreUpdate) {
211 15
                    $class->addLifecycleCallback($method->getName(), Events::preUpdate);
212 90
                } elseif ($annot instanceof ODM\PostUpdate) {
213 79
                    $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 1182
                    $class->addLifecycleCallback($method->getName(), Events::preFlush);
224
                }
225
            }
226
        }
227 1431
    }
228
229 215
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []) : void
230
    {
231 215
        $keys    = array_merge($keys, $index->keys);
232 215
        $options = [];
233 215
        $allowed = ['name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
234 215
        foreach ($allowed as $name) {
235 215
            if (! isset($index->$name)) {
236 215
                continue;
237
            }
238
239 215
            $options[$name] = $index->$name;
240
        }
241 215
        if (! empty($index->partialFilterExpression)) {
242 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
243
        }
244 215
        $options = array_merge($options, $index->options);
245 215
        $class->addIndex($keys, $options);
246 215
    }
247
248
    /**
249
     * @throws MappingException
250
     */
251 88
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey) : void
252
    {
253 88
        $options = [];
254 88
        $allowed = ['unique', 'numInitialChunks'];
255 88
        foreach ($allowed as $name) {
256 88
            if (! isset($shardKey->$name)) {
257 88
                continue;
258
            }
259
260
            $options[$name] = $shardKey->$name;
261
        }
262
263 88
        $class->setShardKey($shardKey->keys, $options);
264 87
    }
265
266
    /**
267
     * Factory method for the Annotation Driver
268
     *
269
     * @param array|string $paths
270
     */
271 1695
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
272
    {
273 1695
        if ($reader === null) {
274 1695
            $reader = new AnnotationReader();
275
        }
276 1695
        return new self($reader, $paths);
0 ignored issues
show
It seems like $paths defined by parameter $paths on line 271 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...
277
    }
278
}
279