Completed
Pull Request — master (#1790)
by Andreas
17:27 queued 13:11
created

AnnotationDriver::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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