Completed
Pull Request — master (#2166)
by Andreas
18:35
created

AnnotationDriver::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
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\ODM\MongoDB\Events;
10
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
11
use Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractIndex;
12
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
13
use Doctrine\ODM\MongoDB\Mapping\MappingException;
14
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
15
use ReflectionClass;
16
use ReflectionMethod;
17
use const E_USER_DEPRECATED;
18
use function array_merge;
19
use function array_replace;
20
use function assert;
21
use function class_exists;
22
use function constant;
23
use function get_class;
24
use function interface_exists;
25
use function is_array;
26
use function trigger_error;
27
28
/**
29
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
30
 */
31
class AnnotationDriver extends AbstractAnnotationDriver
32
{
33 1037
    public function isTransient($className)
34
    {
35 1037
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
36
37 1037
        foreach ($classAnnotations as $annot) {
38 1033
            if ($annot instanceof ODM\AbstractDocument) {
39 1033
                return false;
40
            }
41
        }
42
43 133
        return true;
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 1596
    public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $class) : void
50
    {
51 1596
        assert($class instanceof ClassMetadata);
52 1596
        $reflClass = $class->getReflectionClass();
53
54 1596
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
55
56 1596
        $documentAnnot = null;
57 1596
        foreach ($classAnnotations as $annot) {
58 1594
            $classAnnotations[get_class($annot)] = $annot;
59
60 1594
            if ($annot instanceof ODM\AbstractDocument) {
61 1594
                if ($documentAnnot !== null) {
62 6
                    throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot);
63
                }
64 1594
                $documentAnnot = $annot;
65
            }
66
67
            // non-document class annotations
68 1594
            if ($annot instanceof ODM\AbstractIndex) {
69 8
                $this->addIndex($class, $annot);
70
            }
71 1594
            if ($annot instanceof ODM\Indexes) {
72
                // Setting the type to mixed is a workaround until https://github.com/doctrine/annotations/pull/209 is released.
73
                /** @var mixed $value */
74 157
                $value = $annot->value;
75 157
                foreach (is_array($value) ? $value : [$value] as $index) {
76 157
                    $this->addIndex($class, $index);
77
                }
78 1594
            } elseif ($annot instanceof ODM\InheritanceType) {
79 976
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
80 1594
            } elseif ($annot instanceof ODM\DiscriminatorField) {
81 205
                $class->setDiscriminatorField($annot->value);
82 1594
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
83
                /** @var array $value */
84 197
                $value = $annot->value;
85 197
                $class->setDiscriminatorMap($value);
86 1594
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
87 1
                $class->setDiscriminatorValue($annot->value);
88 1594
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
89 127
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
90 1594
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
91 129
                $class->setDefaultDiscriminatorValue($annot->value);
92 1594
            } elseif ($annot instanceof ODM\ReadPreference) {
93 6
                $class->setReadPreference($annot->value, $annot->tags ?? []);
94
            }
95
        }
96
97 1590
        if ($documentAnnot === null) {
98 4
            throw MappingException::classIsNotAValidDocument($className);
99
        }
100
101 1588
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
102 963
            $class->isMappedSuperclass = true;
103 1585
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
104 351
            $class->isEmbeddedDocument = true;
105 1577
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
106 128
            $class->isQueryResultDocument = true;
107 1577
        } elseif ($documentAnnot instanceof ODM\View) {
108 131
            if (! $documentAnnot->rootClass) {
109 1
                throw MappingException::viewWithoutRootClass($className);
110
            }
111
112 130
            if (! class_exists($documentAnnot->rootClass)) {
113 1
                throw MappingException::viewRootClassNotFound($className, $documentAnnot->rootClass);
114
            }
115
116 129
            $class->markViewOf($documentAnnot->rootClass);
117 1572
        } elseif ($documentAnnot instanceof ODM\File) {
118 143
            $class->isFile = true;
119
120 143
            if ($documentAnnot->chunkSizeBytes !== null) {
121 139
                $class->setChunkSizeBytes($documentAnnot->chunkSizeBytes);
122
            }
123
        }
124
125 1586
        if (isset($documentAnnot->db)) {
126 1
            $class->setDatabase($documentAnnot->db);
127
        }
128 1586
        if (isset($documentAnnot->collection)) {
129 1017
            $class->setCollection($documentAnnot->collection);
130
        }
131 1586
        if (isset($documentAnnot->view)) {
132 127
            $class->setCollection($documentAnnot->view);
133
        }
134
        // Store bucketName as collection name for GridFS files
135 1586
        if (isset($documentAnnot->bucketName)) {
136 1
            $class->setBucketName($documentAnnot->bucketName);
137
        }
138 1586
        if (isset($documentAnnot->repositoryClass)) {
139 143
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
140
        }
141 1586
        if (isset($documentAnnot->writeConcern)) {
142 10
            $class->setWriteConcern($documentAnnot->writeConcern);
143
        }
144 1586
        if (isset($documentAnnot->indexes)) {
145 1580
            foreach ($documentAnnot->indexes as $index) {
146
                $this->addIndex($class, $index);
147
            }
148
        }
149 1586
        if (! empty($documentAnnot->readOnly)) {
150 5
            $class->markReadOnly();
151
        }
152
153 1586
        foreach ($reflClass->getProperties() as $property) {
154 1585
            if (($class->isMappedSuperclass && ! $property->isPrivate())
155
                ||
156 1585
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
157 1008
                continue;
158
            }
159
160 1582
            $indexes    = [];
161 1582
            $mapping    = ['fieldName' => $property->getName()];
162 1582
            $fieldAnnot = null;
163
164 1582
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
165 1582
                if ($annot instanceof ODM\AbstractField) {
166 1582
                    $fieldAnnot = $annot;
167 1582
                    if ($annot->isDeprecated()) {
168
                        @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...
169
                    }
170
                }
171 1582
                if ($annot instanceof ODM\AbstractIndex) {
172 243
                    $indexes[] = $annot;
173
                }
174 1582
                if ($annot instanceof ODM\Indexes) {
175
                    // Setting the type to mixed is a workaround until https://github.com/doctrine/annotations/pull/209 is released.
176
                    /** @var mixed $value */
177
                    $value = $annot->value;
178
                    foreach (is_array($value) ? $value : [$value] as $index) {
179
                        $indexes[] = $index;
180
                    }
181 1582
                } elseif ($annot instanceof ODM\AlsoLoad) {
182 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
183 1582
                } elseif ($annot instanceof ODM\Version) {
184 172
                    $mapping['version'] = true;
185 1582
                } elseif ($annot instanceof ODM\Lock) {
186 25
                    $mapping['lock'] = true;
187
                }
188
            }
189
190 1582
            if ($fieldAnnot) {
191 1582
                $mapping = array_replace($mapping, (array) $fieldAnnot);
192 1582
                $class->mapField($mapping);
193
            }
194
195 1582
            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...
196 1582
                continue;
197
            }
198
199 243
            foreach ($indexes as $index) {
200 243
                $name = $mapping['name'] ?? $mapping['fieldName'];
201 243
                $keys = [$name => $index->order ?: 'asc'];
202 243
                $this->addIndex($class, $index, $keys);
203
            }
204
        }
205
206
        // Set shard key after all fields to ensure we mapped all its keys
207 1584
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
208 150
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
209
        }
210
211
        /** @var ReflectionMethod $method */
212 1583
        foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
213
            /* Filter for the declaring class only. Callbacks from parent
214
             * classes will already be registered.
215
             */
216 1310
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
217 967
                continue;
218
            }
219
220 1310
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
221 955
                if ($annot instanceof ODM\AlsoLoad) {
222 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...
223
                }
224
225 955
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
226 145
                    continue;
227
                }
228
229 936
                if ($annot instanceof ODM\PrePersist) {
230 917
                    $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...
231 156
                } elseif ($annot instanceof ODM\PostPersist) {
232 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...
233 156
                } elseif ($annot instanceof ODM\PreUpdate) {
234 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...
235 151
                } elseif ($annot instanceof ODM\PostUpdate) {
236 140
                    $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...
237 145
                } elseif ($annot instanceof ODM\PreRemove) {
238 143
                    $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...
239 145
                } elseif ($annot instanceof ODM\PostRemove) {
240 143
                    $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...
241 145
                } elseif ($annot instanceof ODM\PreLoad) {
242 144
                    $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...
243 144
                } elseif ($annot instanceof ODM\PostLoad) {
244 143
                    $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...
245 11
                } elseif ($annot instanceof ODM\PreFlush) {
246 11
                    $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...
247
                }
248
            }
249
        }
250 1583
    }
251
252 279
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []) : void
253
    {
254 279
        $keys    = array_merge($keys, $index->keys);
255 279
        $options = [];
256 279
        $allowed = ['name', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
257 279
        foreach ($allowed as $name) {
258 279
            if (! isset($index->$name)) {
259 279
                continue;
260
            }
261
262 279
            $options[$name] = $index->$name;
263
        }
264 279
        if (! empty($index->partialFilterExpression)) {
265 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
266
        }
267 279
        $options = array_merge($options, $index->options);
268 279
        $class->addIndex($keys, $options);
269 279
    }
270
271
    /**
272
     * @throws MappingException
273
     */
274 150
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey) : void
275
    {
276 150
        $options = [];
277 150
        $allowed = ['unique', 'numInitialChunks'];
278 150
        foreach ($allowed as $name) {
279 150
            if (! isset($shardKey->$name)) {
280 150
                continue;
281
            }
282
283
            $options[$name] = $shardKey->$name;
284
        }
285
286 150
        $class->setShardKey($shardKey->keys, $options);
287 149
    }
288
289
    /**
290
     * Factory method for the Annotation Driver
291
     *
292
     * @param string[]|string $paths
293
     */
294 1739
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
295
    {
296 1739
        if ($reader === null) {
297 1739
            $reader = new AnnotationReader();
298
        }
299
300 1739
        return new self($reader, $paths);
301
    }
302
}
303
304
interface_exists(\Doctrine\Persistence\Mapping\ClassMetadata::class);
305