AnnotationDriver   F
last analyzed

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 1043
    public function isTransient($className)
34
    {
35 1043
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
36
37 1043
        foreach ($classAnnotations as $annot) {
38 1039
            if ($annot instanceof ODM\AbstractDocument) {
39 1039
                return false;
40
            }
41
        }
42
43 133
        return true;
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 1604
    public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $class) : void
50
    {
51 1604
        assert($class instanceof ClassMetadata);
52 1604
        $reflClass = $class->getReflectionClass();
53
54 1604
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
55
56 1604
        $documentAnnot = null;
57 1604
        foreach ($classAnnotations as $annot) {
58 1602
            $classAnnotations[get_class($annot)] = $annot;
59
60 1602
            if ($annot instanceof ODM\AbstractDocument) {
61 1602
                if ($documentAnnot !== null) {
62 6
                    throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot);
63
                }
64 1602
                $documentAnnot = $annot;
65
            }
66
67
            // non-document class annotations
68 1602
            if ($annot instanceof ODM\AbstractIndex) {
69 8
                $this->addIndex($class, $annot);
70
            }
71 1602
            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 1602
            } elseif ($annot instanceof ODM\InheritanceType) {
79 978
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
80 1602
            } elseif ($annot instanceof ODM\DiscriminatorField) {
81 204
                $class->setDiscriminatorField($annot->value);
82 1602
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
83
                /** @var array $value */
84 196
                $value = $annot->value;
85 196
                $class->setDiscriminatorMap($value);
86 1602
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
87 1
                $class->setDiscriminatorValue($annot->value);
88 1602
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
89 127
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
90 1602
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
91 129
                $class->setDefaultDiscriminatorValue($annot->value);
92 1602
            } elseif ($annot instanceof ODM\ReadPreference) {
93 6
                $class->setReadPreference($annot->value, $annot->tags ?? []);
94
            }
95
        }
96
97 1598
        if ($documentAnnot === null) {
98 4
            throw MappingException::classIsNotAValidDocument($className);
99
        }
100
101 1596
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
102 969
            $class->isMappedSuperclass = true;
103 1593
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
104 351
            $class->isEmbeddedDocument = true;
105 1585
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
106 128
            $class->isQueryResultDocument = true;
107 1585
        } 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 1580
        } 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 1594
        if (isset($documentAnnot->db)) {
126 1
            $class->setDatabase($documentAnnot->db);
127
        }
128 1594
        if (isset($documentAnnot->collection)) {
129 1020
            $class->setCollection($documentAnnot->collection);
130
        }
131 1594
        if (isset($documentAnnot->view)) {
132 128
            $class->setCollection($documentAnnot->view);
133
        }
134
        // Store bucketName as collection name for GridFS files
135 1594
        if (isset($documentAnnot->bucketName)) {
136 1
            $class->setBucketName($documentAnnot->bucketName);
137
        }
138 1594
        if (isset($documentAnnot->repositoryClass)) {
139 144
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
140
        }
141 1594
        if (isset($documentAnnot->writeConcern)) {
142 10
            $class->setWriteConcern($documentAnnot->writeConcern);
143
        }
144 1594
        if (isset($documentAnnot->indexes)) {
145 1588
            foreach ($documentAnnot->indexes as $index) {
146
                $this->addIndex($class, $index);
147
            }
148
        }
149 1594
        if (! empty($documentAnnot->readOnly)) {
150 5
            $class->markReadOnly();
151
        }
152
153 1594
        foreach ($reflClass->getProperties() as $property) {
154 1593
            if (($class->isMappedSuperclass && ! $property->isPrivate())
155
                ||
156 1593
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
157 1014
                continue;
158
            }
159
160 1590
            $indexes    = [];
161 1590
            $mapping    = ['fieldName' => $property->getName()];
162 1590
            $fieldAnnot = null;
163
164 1590
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
165 1590
                if ($annot instanceof ODM\AbstractField) {
166 1590
                    $fieldAnnot = $annot;
167 1590
                    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 1590
                if ($annot instanceof ODM\AbstractIndex) {
172 245
                    $indexes[] = $annot;
173
                }
174 1590
                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 1590
                } elseif ($annot instanceof ODM\AlsoLoad) {
182 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
183 1590
                } elseif ($annot instanceof ODM\Version) {
184 175
                    $mapping['version'] = true;
185 1590
                } elseif ($annot instanceof ODM\Lock) {
186 28
                    $mapping['lock'] = true;
187
                }
188
            }
189
190 1590
            if ($fieldAnnot) {
191 1590
                $mapping = array_replace($mapping, (array) $fieldAnnot);
192 1590
                $class->mapField($mapping);
193
            }
194
195 1590
            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 1590
                continue;
197
            }
198
199 245
            foreach ($indexes as $index) {
200 245
                $name = $mapping['name'] ?? $mapping['fieldName'];
201 245
                $keys = [$name => $index->order ?: 'asc'];
202 245
                $this->addIndex($class, $index, $keys);
203
            }
204
        }
205
206
        // Set shard key after all fields to ensure we mapped all its keys
207 1592
        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 1591
        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 1318
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
217 973
                continue;
218
            }
219
220 1318
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
221 958
                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 958
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
226 145
                    continue;
227
                }
228
229 939
                if ($annot instanceof ODM\PrePersist) {
230 920
                    $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 1591
    }
251
252 281
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []) : void
253
    {
254 281
        $keys    = array_merge($keys, $index->keys);
255 281
        $options = [];
256 281
        $allowed = ['name', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
257 281
        foreach ($allowed as $name) {
258 281
            if (! isset($index->$name)) {
259 281
                continue;
260
            }
261
262 281
            $options[$name] = $index->$name;
263
        }
264 281
        if (! empty($index->partialFilterExpression)) {
265 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
266
        }
267 281
        $options = array_merge($options, $index->options);
268 281
        $class->addIndex($keys, $options);
269 281
    }
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 1750
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
295
    {
296 1750
        if ($reader === null) {
297 1750
            $reader = new AnnotationReader();
298
        }
299
300 1750
        return new self($reader, $paths);
301
    }
302
}
303
304
interface_exists(\Doctrine\Persistence\Mapping\ClassMetadata::class);
305