Completed
Pull Request — master (#1815)
by Vitalijs
13:50
created

AnnotationDriver::addIndex()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 12

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.2
c 0
b 0
f 0
cc 4
eloc 12
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\ClassMetadata;
13
use Doctrine\ODM\MongoDB\Mapping\MappingException;
14
use const E_USER_DEPRECATED;
15
use function array_merge;
16
use function array_replace;
17
use function constant;
18
use function get_class;
19
use function is_array;
20
use function ksort;
21
use function reset;
22
use function trigger_error;
23
24
/**
25
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
26
 *
27
 */
28
class AnnotationDriver extends AbstractAnnotationDriver
29
{
30
    /** @var int[] */
31
    protected $entityAnnotationClasses = [
32
        ODM\Document::class            => 1,
33
        ODM\MappedSuperclass::class    => 2,
34
        ODM\EmbeddedDocument::class    => 3,
35
        ODM\QueryResultDocument::class => 4,
36
    ];
37
38
    /**
39
     * {@inheritdoc}
40
     */
41 1370
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class)
42
    {
43
        /** @var ClassMetadata $class */
44 1370
        $reflClass = $class->getReflectionClass();
45
46 1370
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
47
48 1370
        $documentAnnots = [];
49 1370
        foreach ($classAnnotations as $annot) {
50 1368
            $classAnnotations[get_class($annot)] = $annot;
51
52 1368
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
53 1368
                if ($annot instanceof $annotClass) {
54 1368
                    $documentAnnots[$i] = $annot;
55 1368
                    continue 2;
56
                }
57
            }
58
59
            // non-document class annotations
60 910
            if ($annot instanceof ODM\AbstractIndex) {
61 6
                $this->addIndex($class, $annot);
62
            }
63 910
            if ($annot instanceof ODM\Indexes) {
64 61
                foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
65 61
                    $this->addIndex($class, $index);
66
                }
67 895
            } elseif ($annot instanceof ODM\InheritanceType) {
68 834
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
69 893
            } elseif ($annot instanceof ODM\DiscriminatorField) {
70 111
                $class->setDiscriminatorField($annot->value);
71 892
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
72 111
                $class->setDiscriminatorMap($annot->value);
73 831
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
74
                $class->setDiscriminatorValue($annot->value);
75 831
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
76 46
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
77 829
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
78 48
                $class->setDefaultDiscriminatorValue($annot->value);
79 824
            } elseif ($annot instanceof ODM\ReadPreference) {
80 910
                $class->setReadPreference($annot->value, $annot->tags);
81
            }
82
        }
83
84 1370
        if (! $documentAnnots) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $documentAnnots 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...
85 3
            throw MappingException::classIsNotAValidDocument($className);
86
        }
87
88
        // find the winning document annotation
89 1368
        ksort($documentAnnots);
90 1368
        $documentAnnot = reset($documentAnnots);
91
92 1368
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
93 824
            $class->isMappedSuperclass = true;
94 1367
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
95 236
            $class->isEmbeddedDocument = true;
96 1360
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
97 47
            $class->isQueryResultDocument = true;
98
        }
99 1368
        if (isset($documentAnnot->db)) {
100 1
            $class->setDatabase($documentAnnot->db);
101
        }
102 1368
        if (isset($documentAnnot->collection)) {
103 870
            $class->setCollection($documentAnnot->collection);
104
        }
105 1368
        if (isset($documentAnnot->repositoryClass)) {
106 57
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
107
        }
108 1368
        if (isset($documentAnnot->writeConcern)) {
109 10
            $class->setWriteConcern($documentAnnot->writeConcern);
110
        }
111 1368
        if (isset($documentAnnot->indexes)) {
112 1367
            foreach ($documentAnnot->indexes as $index) {
113
                $this->addIndex($class, $index);
114
            }
115
        }
116 1368
        if (! empty($documentAnnot->readOnly)) {
117 5
            $class->markReadOnly();
118
        }
119
120 1368
        foreach ($reflClass->getProperties() as $property) {
121 1367
            if (($class->isMappedSuperclass && ! $property->isPrivate())
122
                ||
123 1367
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
124 866
                continue;
125
            }
126
127 1366
            $indexes = [];
128 1366
            $mapping = ['fieldName' => $property->getName()];
129 1366
            $fieldAnnot = null;
130
131 1366
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
132 1366
                if ($annot instanceof ODM\AbstractField) {
133 1366
                    $fieldAnnot = $annot;
134 1366
                    if ($annot->isDeprecated()) {
135
                        @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...
136
                    }
137
                }
138 1366
                if ($annot instanceof ODM\AbstractIndex) {
139 159
                    $indexes[] = $annot;
140
                }
141 1366
                if ($annot instanceof ODM\Indexes) {
142
                    foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
143
                        $indexes[] = $index;
144
                    }
145 1366
                } elseif ($annot instanceof ODM\AlsoLoad) {
146 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
147 1366
                } elseif ($annot instanceof ODM\Version) {
148 67
                    $mapping['version'] = true;
149 1366
                } elseif ($annot instanceof ODM\Lock) {
150 1366
                    $mapping['lock'] = true;
151
                }
152
            }
153
154 1366
            if ($fieldAnnot) {
155 1366
                $mapping = array_replace($mapping, (array) $fieldAnnot);
156 1366
                $class->mapField($mapping);
157
            }
158
159 1366
            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...
160 1366
                continue;
161
            }
162
163 159
            foreach ($indexes as $index) {
164 159
                $name = $mapping['name'] ?? $mapping['fieldName'];
165 159
                $keys = [$name => $index->order ?: 'asc'];
166 159
                $this->addIndex($class, $index, $keys);
167
            }
168
        }
169
170
        // Set shard key after all fields to ensure we mapped all its keys
171 1366
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
172 59
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
173
        }
174
175
        /** @var \ReflectionMethod $method */
176 1365
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
177
            /* Filter for the declaring class only. Callbacks from parent
178
             * classes will already be registered.
179
             */
180 1127
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
181 831
                continue;
182
            }
183
184 1127
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
185 821
                if ($annot instanceof ODM\AlsoLoad) {
186 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...
187
                }
188
189 821
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
190 64
                    continue;
191
                }
192
193 802
                if ($annot instanceof ODM\PrePersist) {
194 787
                    $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...
195 71
                } elseif ($annot instanceof ODM\PostPersist) {
196 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...
197 71
                } elseif ($annot instanceof ODM\PreUpdate) {
198 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...
199 66
                } elseif ($annot instanceof ODM\PostUpdate) {
200 55
                    $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...
201 64
                } elseif ($annot instanceof ODM\PreRemove) {
202 62
                    $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...
203 64
                } elseif ($annot instanceof ODM\PostRemove) {
204 62
                    $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...
205 64
                } elseif ($annot instanceof ODM\PreLoad) {
206 63
                    $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...
207 63
                } elseif ($annot instanceof ODM\PostLoad) {
208 62
                    $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...
209 11
                } elseif ($annot instanceof ODM\PreFlush) {
210 1127
                    $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...
211
                }
212
            }
213
        }
214 1365
    }
215
216 180
    private function addIndex(ClassMetadata $class, $index, array $keys = [])
217
    {
218 180
        $keys = array_merge($keys, $index->keys);
219 180
        $options = [];
220 180
        $allowed = ['name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
221 180
        foreach ($allowed as $name) {
222 180
            if (! isset($index->$name)) {
223 180
                continue;
224
            }
225
226 180
            $options[$name] = $index->$name;
227
        }
228 180
        if (! empty($index->partialFilterExpression)) {
229 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
230
        }
231 180
        $options = array_merge($options, $index->options);
232 180
        $class->addIndex($keys, $options);
233 180
    }
234
235
    /**
236
     *
237
     * @throws MappingException
238
     */
239 59
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey)
240
    {
241 59
        $options = [];
242 59
        $allowed = ['unique', 'numInitialChunks'];
243 59
        foreach ($allowed as $name) {
244 59
            if (! isset($shardKey->$name)) {
245 59
                continue;
246
            }
247
248
            $options[$name] = $shardKey->$name;
249
        }
250
251 59
        $class->setShardKey($shardKey->keys, $options);
252 58
    }
253
254
    /**
255
     * Factory method for the Annotation Driver
256
     *
257
     * @param array|string $paths
258
     * @return AnnotationDriver
259
     */
260 1626
    public static function create($paths = [], ?Reader $reader = null)
261
    {
262 1626
        if ($reader === null) {
263 1626
            $reader = new AnnotationReader();
264
        }
265 1626
        return new self($reader, $paths);
266
    }
267
}
268