Completed
Pull Request — master (#1757)
by Maciej
10:01
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 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 1365
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class)
42
    {
43
        /** @var ClassMetadata $class */
44 1365
        $reflClass = $class->getReflectionClass();
45
46 1365
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
47
48 1365
        $documentAnnots = [];
49 1365
        foreach ($classAnnotations as $annot) {
50 1363
            $classAnnotations[get_class($annot)] = $annot;
51
52 1363
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
53 1363
                if ($annot instanceof $annotClass) {
54 1363
                    $documentAnnots[$i] = $annot;
55 1363
                    continue 2;
56
                }
57
            }
58
59
            // non-document class annotations
60 909
            if ($annot instanceof ODM\AbstractIndex) {
61 6
                $this->addIndex($class, $annot);
62
            }
63 909
            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 894
            } elseif ($annot instanceof ODM\InheritanceType) {
68 833
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
69 892
            } elseif ($annot instanceof ODM\DiscriminatorField) {
70 111
                $class->setDiscriminatorField($annot->value);
71 891
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
72 111
                $class->setDiscriminatorMap($annot->value);
73 830
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
74
                $class->setDiscriminatorValue($annot->value);
75 830
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
76 46
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
77 828
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
78 48
                $class->setDefaultDiscriminatorValue($annot->value);
79 823
            } elseif ($annot instanceof ODM\ReadPreference) {
80 909
                $class->setReadPreference($annot->value, $annot->tags);
81
            }
82
        }
83
84 1365
        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 1363
        ksort($documentAnnots);
90 1363
        $documentAnnot = reset($documentAnnots);
91
92 1363
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
93 822
            $class->isMappedSuperclass = true;
94 1362
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
95 234
            $class->isEmbeddedDocument = true;
96 1355
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
97 47
            $class->isQueryResultDocument = true;
98
        }
99 1363
        if (isset($documentAnnot->db)) {
100 1
            $class->setDatabase($documentAnnot->db);
101
        }
102 1363
        if (isset($documentAnnot->collection)) {
103 868
            $class->setCollection($documentAnnot->collection);
104
        }
105 1363
        if (isset($documentAnnot->repositoryClass)) {
106 57
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
107
        }
108 1363
        if (isset($documentAnnot->writeConcern)) {
109 10
            $class->setWriteConcern($documentAnnot->writeConcern);
110
        }
111 1363
        if (isset($documentAnnot->indexes)) {
112 1362
            foreach ($documentAnnot->indexes as $index) {
113
                $this->addIndex($class, $index);
114
            }
115
        }
116 1363
        if (! empty($documentAnnot->readOnly)) {
117 5
            $class->markReadOnly();
118
        }
119
120 1363
        foreach ($reflClass->getProperties() as $property) {
121 1362
            if (($class->isMappedSuperclass && ! $property->isPrivate())
122
                ||
123 1362
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
124 864
                continue;
125
            }
126
127 1361
            $indexes = [];
128 1361
            $mapping = ['fieldName' => $property->getName()];
129 1361
            $fieldAnnot = null;
130
131 1361
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
132 1361
                if ($annot instanceof ODM\AbstractField) {
133 1361
                    $fieldAnnot = $annot;
134 1361
                    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 1361
                if ($annot instanceof ODM\AbstractIndex) {
139 159
                    $indexes[] = $annot;
140
                }
141 1361
                if ($annot instanceof ODM\Indexes) {
142
                    foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
143
                        $indexes[] = $index;
144
                    }
145 1361
                } elseif ($annot instanceof ODM\AlsoLoad) {
146 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
147 1361
                } elseif ($annot instanceof ODM\Version) {
148 67
                    $mapping['version'] = true;
149 1361
                } elseif ($annot instanceof ODM\Lock) {
150 1361
                    $mapping['lock'] = true;
151
                }
152
            }
153
154 1361
            if ($fieldAnnot) {
155 1361
                $mapping = array_replace($mapping, (array) $fieldAnnot);
156 1361
                $class->mapField($mapping);
157
            }
158
159 1361
            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 1361
                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 1361
        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 1360
        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 1124
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
181 830
                continue;
182
            }
183
184 1124
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
185 820
                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 820
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
190 64
                    continue;
191
                }
192
193 801
                if ($annot instanceof ODM\PrePersist) {
194 786
                    $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 1124
                    $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 1360
    }
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 1618
    public static function create($paths = [], ?Reader $reader = null)
261
    {
262 1618
        if ($reader === null) {
263 1618
            $reader = new AnnotationReader();
264
        }
265 1618
        return new self($reader, $paths);
266
    }
267
}
268