Completed
Pull Request — master (#1803)
by Andreas
16:48 queued 09:05
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 918
    public function isTransient($className)
32
    {
33 918
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
34
35 918
        foreach ($classAnnotations as $annot) {
36 917
            if ($annot instanceof ODM\AbstractDocument) {
37 917
                return false;
38
            }
39
        }
40 69
        return true;
41
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46 1448
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class) : void
47
    {
48 1448
        assert($class instanceof ClassMetadata);
49 1448
        $reflClass = $class->getReflectionClass();
50
51 1448
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
52
53 1448
        $documentAnnot = null;
54 1448
        foreach ($classAnnotations as $annot) {
55 1446
            $classAnnotations[get_class($annot)] = $annot;
56
57 1446
            if ($annot instanceof ODM\AbstractDocument) {
58 1446
                if ($documentAnnot !== null) {
59 5
                    throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot);
60
                }
61 1446
                $documentAnnot = $annot;
62
            }
63
64
            // non-document class annotations
65 1446
            if ($annot instanceof ODM\AbstractIndex) {
66 6
                $this->addIndex($class, $annot);
67
            }
68 1446
            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 94
                $value = $annot->value;
72 94
                foreach (is_array($value) ? $value : [$value] as $index) {
73 94
                    $this->addIndex($class, $index);
74
                }
75 1446
            } elseif ($annot instanceof ODM\InheritanceType) {
76 859
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
77 1446
            } elseif ($annot instanceof ODM\DiscriminatorField) {
78 132
                $class->setDiscriminatorField($annot->value);
79 1446
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
80
                /** @var array $value */
81 131
                $value = $annot->value;
82 131
                $class->setDiscriminatorMap($value);
83 1446
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
84
                $class->setDiscriminatorValue($annot->value);
85 1446
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
86 66
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
87 1446
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
88 68
                $class->setDefaultDiscriminatorValue($annot->value);
89 1446
            } elseif ($annot instanceof ODM\ReadPreference) {
90 1446
                $class->setReadPreference($annot->value, $annot->tags ?? []);
91
            }
92
        }
93
94 1443
        if ($documentAnnot === null) {
95 3
            throw MappingException::classIsNotAValidDocument($className);
96
        }
97
98 1441
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
99 849
            $class->isMappedSuperclass = true;
100 1440
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
101 287
            $class->isEmbeddedDocument = true;
102 1433
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
103 67
            $class->isQueryResultDocument = true;
104 1433
        } elseif ($documentAnnot instanceof ODM\File) {
105 79
            $class->isFile = true;
106
107 79
            if ($documentAnnot->chunkSizeBytes !== null) {
108 78
                $class->setChunkSizeBytes($documentAnnot->chunkSizeBytes);
109
            }
110
        }
111
112 1441
        if (isset($documentAnnot->db)) {
113 1
            $class->setDatabase($documentAnnot->db);
114
        }
115 1441
        if (isset($documentAnnot->collection)) {
116 911
            $class->setCollection($documentAnnot->collection);
117
        }
118
        // Store bucketName as collection name for GridFS files
119 1441
        if (isset($documentAnnot->bucketName)) {
120 1
            $class->setBucketName($documentAnnot->bucketName);
121
        }
122 1441
        if (isset($documentAnnot->repositoryClass)) {
123 77
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
124
        }
125 1441
        if (isset($documentAnnot->writeConcern)) {
126 10
            $class->setWriteConcern($documentAnnot->writeConcern);
127
        }
128 1441
        if (isset($documentAnnot->indexes)) {
129 1440
            foreach ($documentAnnot->indexes as $index) {
130
                $this->addIndex($class, $index);
131
            }
132
        }
133 1441
        if (! empty($documentAnnot->readOnly)) {
134 5
            $class->markReadOnly();
135
        }
136
137 1441
        foreach ($reflClass->getProperties() as $property) {
138 1440
            if (($class->isMappedSuperclass && ! $property->isPrivate())
139
                ||
140 1440
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
141 891
                continue;
142
            }
143
144 1439
            $indexes    = [];
145 1439
            $mapping    = ['fieldName' => $property->getName()];
146 1439
            $fieldAnnot = null;
147
148 1439
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
149 1439
                if ($annot instanceof ODM\AbstractField) {
150 1439
                    $fieldAnnot = $annot;
151 1439
                    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 1439
                if ($annot instanceof ODM\AbstractIndex) {
156 182
                    $indexes[] = $annot;
157
                }
158 1439
                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 1439
                } elseif ($annot instanceof ODM\AlsoLoad) {
166 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
167 1439
                } elseif ($annot instanceof ODM\Version) {
168 108
                    $mapping['version'] = true;
169 1439
                } elseif ($annot instanceof ODM\Lock) {
170 1439
                    $mapping['lock'] = true;
171
                }
172
            }
173
174 1439
            if ($fieldAnnot) {
175 1439
                $mapping = array_replace($mapping, (array) $fieldAnnot);
176 1439
                $class->mapField($mapping);
177
            }
178
179 1439
            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 1439
                continue;
181
            }
182
183 182
            foreach ($indexes as $index) {
184 182
                $name = $mapping['name'] ?? $mapping['fieldName'];
185 182
                $keys = [$name => $index->order ?: 'asc'];
186 182
                $this->addIndex($class, $index, $keys);
187
            }
188
        }
189
190
        // Set shard key after all fields to ensure we mapped all its keys
191 1438
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
192 88
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
193
        }
194
195
        /** @var ReflectionMethod $method */
196 1437
        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 1185
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
201 856
                continue;
202
            }
203
204 1185
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
205 850
                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 850
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
210 84
                    continue;
211
                }
212
213 831
                if ($annot instanceof ODM\PrePersist) {
214 812
                    $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 95
                } 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 95
                } 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 90
                } elseif ($annot instanceof ODM\PostUpdate) {
220 79
                    $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 84
                } elseif ($annot instanceof ODM\PreRemove) {
222 82
                    $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 84
                } elseif ($annot instanceof ODM\PostRemove) {
224 82
                    $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 84
                } elseif ($annot instanceof ODM\PreLoad) {
226 83
                    $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 83
                } elseif ($annot instanceof ODM\PostLoad) {
228 82
                    $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 1185
                    $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 1437
    }
235
236 214
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []) : void
237
    {
238 214
        $keys    = array_merge($keys, $index->keys);
239 214
        $options = [];
240 214
        $allowed = ['name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
241 214
        foreach ($allowed as $name) {
242 214
            if (! isset($index->$name)) {
243 214
                continue;
244
            }
245
246 214
            $options[$name] = $index->$name;
247
        }
248 214
        if (! empty($index->partialFilterExpression)) {
249 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
250
        }
251 214
        $options = array_merge($options, $index->options);
252 214
        $class->addIndex($keys, $options);
253 214
    }
254
255
    /**
256
     * @throws MappingException
257
     */
258 88
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey) : void
259
    {
260 88
        $options = [];
261 88
        $allowed = ['unique', 'numInitialChunks'];
262 88
        foreach ($allowed as $name) {
263 88
            if (! isset($shardKey->$name)) {
264 88
                continue;
265
            }
266
267
            $options[$name] = $shardKey->$name;
268
        }
269
270 88
        $class->setShardKey($shardKey->keys, $options);
271 87
    }
272
273
    /**
274
     * Factory method for the Annotation Driver
275
     *
276
     * @param string[]|string $paths
277
     */
278 1711
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
279
    {
280 1711
        if ($reader === null) {
281 1711
            $reader = new AnnotationReader();
282
        }
283 1711
        return new self($reader, $paths);
284
    }
285
}
286