Completed
Pull Request — master (#1803)
by Andreas
15:44
created

AnnotationDriver   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 95.3%

Importance

Changes 0
Metric Value
wmc 72
lcom 1
cbo 6
dl 0
loc 258
ccs 142
cts 149
cp 0.953
rs 2.64
c 0
b 0
f 0

4 Methods

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