Completed
Pull Request — master (#2116)
by Andreas
17:41
created

AnnotationDriver   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 96.27%

Importance

Changes 0
Metric Value
wmc 79
lcom 1
cbo 5
dl 0
loc 272
c 0
b 0
f 0
ccs 155
cts 161
cp 0.9627
rs 2.08

5 Methods

Rating   Name   Duplication   Size   Complexity  
A isTransient() 0 12 3
F loadMetadataForClass() 0 202 67
A addIndex() 0 18 4
A setShardKey() 0 14 3
A create() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like AnnotationDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationDriver, and based on these observations, apply Extract Interface, too.

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 1038
    public function isTransient($className)
34
    {
35 1038
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
36
37 1038
        foreach ($classAnnotations as $annot) {
38 1034
            if ($annot instanceof ODM\AbstractDocument) {
39 1034
                return false;
40
            }
41
        }
42
43 133
        return true;
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 1599
    public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $class) : void
50
    {
51 1599
        assert($class instanceof ClassMetadata);
52 1599
        $reflClass = $class->getReflectionClass();
53
54 1599
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
55
56 1599
        $documentAnnot = null;
57 1599
        foreach ($classAnnotations as $annot) {
58 1597
            $classAnnotations[get_class($annot)] = $annot;
59
60 1597
            if ($annot instanceof ODM\AbstractDocument) {
61 1597
                if ($documentAnnot !== null) {
62 6
                    throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot);
63
                }
64 1597
                $documentAnnot = $annot;
65
            }
66
67
            // non-document class annotations
68 1597
            if ($annot instanceof ODM\AbstractIndex) {
69 8
                $this->addIndex($class, $annot);
70
            }
71 1597
            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 1597
            } elseif ($annot instanceof ODM\InheritanceType) {
79 976
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
80 1597
            } elseif ($annot instanceof ODM\DiscriminatorField) {
81 204
                $class->setDiscriminatorField($annot->value);
82 1597
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
83
                /** @var array $value */
84 196
                $value = $annot->value;
85 196
                $class->setDiscriminatorMap($value);
86 1597
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
87 1
                $class->setDiscriminatorValue($annot->value);
88 1597
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
89 127
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
90 1597
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
91 129
                $class->setDefaultDiscriminatorValue($annot->value);
92 1597
            } elseif ($annot instanceof ODM\ReadPreference) {
93 6
                $class->setReadPreference($annot->value, $annot->tags ?? []);
94
            }
95
        }
96
97 1593
        if ($documentAnnot === null) {
98 4
            throw MappingException::classIsNotAValidDocument($className);
99
        }
100
101 1591
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
102 964
            $class->isMappedSuperclass = true;
103 1588
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
104 351
            $class->isEmbeddedDocument = true;
105 1580
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
106 128
            $class->isQueryResultDocument = true;
107 1580
        } elseif ($documentAnnot instanceof ODM\View) {
108 132
            if (! $documentAnnot->rootClass) {
109 1
                throw MappingException::viewWithoutRootClass($className);
110
            }
111
112 131
            if (! class_exists($documentAnnot->rootClass)) {
113 1
                throw MappingException::viewRootClassNotFound($className, $documentAnnot->rootClass);
114
            }
115
116 130
            $class->markViewOf($documentAnnot->rootClass);
117 1575
        } 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 1589
        if (isset($documentAnnot->db)) {
126 1
            $class->setDatabase($documentAnnot->db);
127
        }
128 1589
        if (isset($documentAnnot->collection)) {
129 1018
            $class->setCollection($documentAnnot->collection);
130
        }
131 1589
        if (isset($documentAnnot->view)) {
132 128
            $class->setCollection($documentAnnot->view);
133
        }
134
        // Store bucketName as collection name for GridFS files
135 1589
        if (isset($documentAnnot->bucketName)) {
136 1
            $class->setBucketName($documentAnnot->bucketName);
137
        }
138 1589
        if (isset($documentAnnot->repositoryClass)) {
139 144
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
140
        }
141 1589
        if (isset($documentAnnot->writeConcern)) {
142 10
            $class->setWriteConcern($documentAnnot->writeConcern);
143
        }
144 1589
        if (isset($documentAnnot->indexes)) {
145 1583
            foreach ($documentAnnot->indexes as $index) {
146
                $this->addIndex($class, $index);
147
            }
148
        }
149 1589
        if (! empty($documentAnnot->readOnly)) {
150 5
            $class->markReadOnly();
151
        }
152
153 1589
        foreach ($reflClass->getProperties() as $property) {
154 1588
            if (($class->isMappedSuperclass && ! $property->isPrivate())
155
                ||
156 1588
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
157 1009
                continue;
158
            }
159
160 1585
            $indexes    = [];
161 1585
            $mapping    = ['fieldName' => $property->getName()];
162 1585
            $fieldAnnot = null;
163
164 1585
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
165 1585
                if ($annot instanceof ODM\AbstractField) {
166 1585
                    $fieldAnnot = $annot;
167 1585
                    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 1585
                if ($annot instanceof ODM\AbstractIndex) {
172 243
                    $indexes[] = $annot;
173
                }
174 1585
                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 1585
                } elseif ($annot instanceof ODM\AlsoLoad) {
182 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
183 1585
                } elseif ($annot instanceof ODM\Version) {
184 172
                    $mapping['version'] = true;
185 1585
                } elseif ($annot instanceof ODM\Lock) {
186 25
                    $mapping['lock'] = true;
187
                }
188
            }
189
190 1585
            if ($fieldAnnot) {
191 1585
                $mapping = array_replace($mapping, (array) $fieldAnnot);
192 1585
                $class->mapField($mapping);
193
            }
194
195 1585
            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 1585
                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 1587
        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 1586
        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 1313
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
217 968
                continue;
218
            }
219
220 1313
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
221 956
                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 956
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
226 145
                    continue;
227
                }
228
229 937
                if ($annot instanceof ODM\PrePersist) {
230 918
                    $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 1586
    }
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 1743
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
295
    {
296 1743
        if ($reader === null) {
297 1743
            $reader = new AnnotationReader();
298
        }
299
300 1743
        return new self($reader, $paths);
301
    }
302
}
303
304
interface_exists(\Doctrine\Persistence\Mapping\ClassMetadata::class);
305