Completed
Pull Request — master (#1803)
by Maciej
15:26 queued 06:04
created

AnnotationDriver::setShardKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

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