Completed
Pull Request — master (#1803)
by Maciej
20:22
created

AnnotationDriver::setShardKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

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