Completed
Push — master ( f0fe3a...13ea99 )
by Andreas
12s
created

AnnotationDriver   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 95.86%

Importance

Changes 0
Metric Value
wmc 72
lcom 1
cbo 6
dl 0
loc 252
ccs 139
cts 145
cp 0.9586
rs 2.64
c 0
b 0
f 0

4 Methods

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