Completed
Pull Request — master (#2025)
by Andreas
17:58
created

AnnotationDriver::addIndex()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 13
cts 13
cp 1
rs 9.6666
c 0
b 0
f 0
cc 4
nc 6
nop 3
crap 4
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 ReflectionClass;
16
use ReflectionMethod;
17
use const E_USER_DEPRECATED;
18
use function array_merge;
19
use function array_replace;
20
use function assert;
21
use function constant;
22
use function get_class;
23
use function is_array;
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 1019
    public function isTransient($className)
32
    {
33 1019
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
34
35 1019
        foreach ($classAnnotations as $annot) {
36 1015
            if ($annot instanceof ODM\AbstractDocument) {
37 1015
                return false;
38
            }
39
        }
40 120
        return true;
41
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46 1555
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class) : void
47
    {
48 1555
        assert($class instanceof ClassMetadata);
49 1555
        $reflClass = $class->getReflectionClass();
50
51 1555
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
52
53 1555
        $documentAnnot = null;
54 1555
        foreach ($classAnnotations as $annot) {
55 1553
            $classAnnotations[get_class($annot)] = $annot;
56
57 1553
            if ($annot instanceof ODM\AbstractDocument) {
58 1553
                if ($documentAnnot !== null) {
59 5
                    throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot);
60
                }
61 1553
                $documentAnnot = $annot;
62
            }
63
64
            // non-document class annotations
65 1553
            if ($annot instanceof ODM\AbstractIndex) {
66 8
                $this->addIndex($class, $annot);
67
            }
68 1553
            if ($annot instanceof ODM\Indexes) {
69
                // Setting the type to mixed is a workaround until https://github.com/doctrine/annotations/pull/209 is released.
70
                /** @var mixed $value */
71 143
                $value = $annot->value;
72 143
                foreach (is_array($value) ? $value : [$value] as $index) {
73 143
                    $this->addIndex($class, $index);
74
                }
75 1553
            } elseif ($annot instanceof ODM\InheritanceType) {
76 962
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
77 1553
            } elseif ($annot instanceof ODM\DiscriminatorField) {
78 191
                $class->setDiscriminatorField($annot->value);
79 1553
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
80
                /** @var array $value */
81 183
                $value = $annot->value;
82 183
                $class->setDiscriminatorMap($value);
83 1553
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
84 1
                $class->setDiscriminatorValue($annot->value);
85 1553
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
86 114
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
87 1553
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
88 116
                $class->setDefaultDiscriminatorValue($annot->value);
89 1553
            } elseif ($annot instanceof ODM\ReadPreference) {
90 6
                $class->setReadPreference($annot->value, $annot->tags ?? []);
91
            }
92
        }
93
94 1550
        if ($documentAnnot === null) {
95 4
            throw MappingException::classIsNotAValidDocument($className);
96
        }
97
98 1548
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
99 945
            $class->isMappedSuperclass = true;
100 1545
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
101 337
            $class->isEmbeddedDocument = true;
102 1537
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
103 115
            $class->isQueryResultDocument = true;
104 1537
        } elseif ($documentAnnot instanceof ODM\File) {
105 128
            $class->isFile = true;
106
107 128
            if ($documentAnnot->chunkSizeBytes !== null) {
108 126
                $class->setChunkSizeBytes($documentAnnot->chunkSizeBytes);
109
            }
110
        }
111
112 1548
        if (isset($documentAnnot->db)) {
113 1
            $class->setDatabase($documentAnnot->db);
114
        }
115 1548
        if (isset($documentAnnot->collection)) {
116 1004
            $class->setCollection($documentAnnot->collection);
117
        }
118
        // Store bucketName as collection name for GridFS files
119 1548
        if (isset($documentAnnot->bucketName)) {
120 1
            $class->setBucketName($documentAnnot->bucketName);
121
        }
122 1548
        if (isset($documentAnnot->repositoryClass)) {
123 125
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
124
        }
125 1548
        if (isset($documentAnnot->writeConcern)) {
126 10
            $class->setWriteConcern($documentAnnot->writeConcern);
127
        }
128 1548
        if (isset($documentAnnot->indexes)) {
129 1545
            foreach ($documentAnnot->indexes as $index) {
130
                $this->addIndex($class, $index);
131
            }
132
        }
133 1548
        if (! empty($documentAnnot->readOnly)) {
134 5
            $class->markReadOnly();
135
        }
136
137 1548
        foreach ($reflClass->getProperties() as $property) {
138 1547
            if (($class->isMappedSuperclass && ! $property->isPrivate())
139
                ||
140 1547
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
141 990
                continue;
142
            }
143
144 1544
            $indexes    = [];
145 1544
            $mapping    = ['fieldName' => $property->getName()];
146 1544
            $fieldAnnot = null;
147
148 1544
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
149 1544
                if ($annot instanceof ODM\AbstractField) {
150 1544
                    $fieldAnnot = $annot;
151 1544
                    if ($annot->isDeprecated()) {
152
                        @trigger_error($annot->getDeprecationMessage(), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
153
                    }
154
                }
155 1544
                if ($annot instanceof ODM\AbstractIndex) {
156 230
                    $indexes[] = $annot;
157
                }
158 1544
                if ($annot instanceof ODM\Indexes) {
159
                    // Setting the type to mixed is a workaround until https://github.com/doctrine/annotations/pull/209 is released.
160
                    /** @var mixed $value */
161
                    $value = $annot->value;
162
                    foreach (is_array($value) ? $value : [$value] as $index) {
163
                        $indexes[] = $index;
164
                    }
165 1544
                } elseif ($annot instanceof ODM\AlsoLoad) {
166 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
167 1544
                } elseif ($annot instanceof ODM\Version) {
168 156
                    $mapping['version'] = true;
169 1544
                } elseif ($annot instanceof ODM\Lock) {
170 22
                    $mapping['lock'] = true;
171
                }
172
            }
173
174 1544
            if ($fieldAnnot) {
175 1544
                $mapping = array_replace($mapping, (array) $fieldAnnot);
176 1544
                $class->mapField($mapping);
177
            }
178
179 1544
            if (! $indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes 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...
180 1544
                continue;
181
            }
182
183 230
            foreach ($indexes as $index) {
184 230
                $name = $mapping['name'] ?? $mapping['fieldName'];
185 230
                $keys = [$name => $index->order ?: 'asc'];
186 230
                $this->addIndex($class, $index, $keys);
187
            }
188
        }
189
190
        // Set shard key after all fields to ensure we mapped all its keys
191 1545
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
192 137
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
193
        }
194
195
        /** @var ReflectionMethod $method */
196 1544
        foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
197
            /* Filter for the declaring class only. Callbacks from parent
198
             * classes will already be registered.
199
             */
200 1283
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
201 951
                continue;
202
            }
203
204 1283
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
205 942
                if ($annot instanceof ODM\AlsoLoad) {
206 13
                    $class->registerAlsoLoadMethod($method->getName(), $annot->value);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
207
                }
208
209 942
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
210 132
                    continue;
211
                }
212
213 923
                if ($annot instanceof ODM\PrePersist) {
214 904
                    $class->addLifecycleCallback($method->getName(), Events::prePersist);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
215 143
                } elseif ($annot instanceof ODM\PostPersist) {
216 10
                    $class->addLifecycleCallback($method->getName(), Events::postPersist);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
217 143
                } elseif ($annot instanceof ODM\PreUpdate) {
218 15
                    $class->addLifecycleCallback($method->getName(), Events::preUpdate);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
219 138
                } elseif ($annot instanceof ODM\PostUpdate) {
220 127
                    $class->addLifecycleCallback($method->getName(), Events::postUpdate);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
221 132
                } elseif ($annot instanceof ODM\PreRemove) {
222 130
                    $class->addLifecycleCallback($method->getName(), Events::preRemove);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
223 132
                } elseif ($annot instanceof ODM\PostRemove) {
224 130
                    $class->addLifecycleCallback($method->getName(), Events::postRemove);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
225 132
                } elseif ($annot instanceof ODM\PreLoad) {
226 131
                    $class->addLifecycleCallback($method->getName(), Events::preLoad);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
227 131
                } elseif ($annot instanceof ODM\PostLoad) {
228 130
                    $class->addLifecycleCallback($method->getName(), Events::postLoad);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
229 11
                } elseif ($annot instanceof ODM\PreFlush) {
230 11
                    $class->addLifecycleCallback($method->getName(), Events::preFlush);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
231
                }
232
            }
233
        }
234 1544
    }
235
236 265
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []) : void
237
    {
238 265
        $keys    = array_merge($keys, $index->keys);
239 265
        $options = [];
240 265
        $allowed = ['name', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
241 265
        foreach ($allowed as $name) {
242 265
            if (! isset($index->$name)) {
243 265
                continue;
244
            }
245
246 265
            $options[$name] = $index->$name;
247
        }
248 265
        if (! empty($index->partialFilterExpression)) {
249 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
250
        }
251 265
        $options = array_merge($options, $index->options);
252 265
        $class->addIndex($keys, $options);
253 265
    }
254
255
    /**
256
     * @throws MappingException
257
     */
258 137
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey) : void
259
    {
260 137
        $options = [];
261 137
        $allowed = ['unique', 'numInitialChunks'];
262 137
        foreach ($allowed as $name) {
263 137
            if (! isset($shardKey->$name)) {
264 137
                continue;
265
            }
266
267
            $options[$name] = $shardKey->$name;
268
        }
269
270 137
        $class->setShardKey($shardKey->keys, $options);
271 136
    }
272
273
    /**
274
     * Factory method for the Annotation Driver
275
     *
276
     * @param string[]|string $paths
277
     */
278 1771
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
279
    {
280 1771
        if ($reader === null) {
281 1771
            $reader = new AnnotationReader();
282
        }
283 1771
        return new self($reader, $paths);
284
    }
285
}
286