Completed
Push — master ( f3ed76...7db4b5 )
by Andreas
14s queued 10s
created

AnnotationDriver   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 95.86%

Importance

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

4 Methods

Rating   Name   Duplication   Size   Complexity  
A setShardKey() 0 14 3
A create() 0 7 2
F loadMetadataForClass() 0 185 63
A addIndex() 0 18 4

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 constant;
20
use function get_class;
21
use function is_array;
22
use function ksort;
23
use function reset;
24
use function trigger_error;
25
26
/**
27
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
28
 */
29
class AnnotationDriver extends AbstractAnnotationDriver
30
{
31
    /** @var int[] */
32
    protected $entityAnnotationClasses = [
33
        ODM\Document::class            => 1,
34
        ODM\MappedSuperclass::class    => 2,
35
        ODM\EmbeddedDocument::class    => 3,
36
        ODM\QueryResultDocument::class => 4,
37
        ODM\File::class                => 5,
38
    ];
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 1436
    public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $class) : void
44
    {
45
        /** @var ClassMetadata $class */
46 1436
        $reflClass = $class->getReflectionClass();
47
48 1436
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
49
50 1436
        $documentAnnots = [];
51 1436
        foreach ($classAnnotations as $annot) {
52 1434
            $classAnnotations[get_class($annot)] = $annot;
53
54 1434
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
55 1434
                if ($annot instanceof $annotClass) {
56 1434
                    $documentAnnots[$i] = $annot;
57 1434
                    continue 2;
58
                }
59
            }
60
61
            // non-document class annotations
62 951
            if ($annot instanceof ODM\AbstractIndex) {
63 6
                $this->addIndex($class, $annot);
64
            }
65 951
            if ($annot instanceof ODM\Indexes) {
66 95
                foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
67 95
                    $this->addIndex($class, $index);
68
                }
69 936
            } elseif ($annot instanceof ODM\InheritanceType) {
70 862
                $class->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
71 934
            } elseif ($annot instanceof ODM\DiscriminatorField) {
72 131
                $class->setDiscriminatorField($annot->value);
73 933
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
74 131
                $class->setDiscriminatorMap($annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type string; however, Doctrine\ODM\MongoDB\Map...::setDiscriminatorMap() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
75 872
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
76
                $class->setDiscriminatorValue($annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type array; however, Doctrine\ODM\MongoDB\Map...setDiscriminatorValue() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
77 872
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
78 66
                $class->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
79 870
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
80 68
                $class->setDefaultDiscriminatorValue($annot->value);
81 865
            } elseif ($annot instanceof ODM\ReadPreference) {
82 951
                $class->setReadPreference($annot->value, $annot->tags);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type array; however, Doctrine\ODM\MongoDB\Map...ta::setReadPreference() does only seem to accept string|integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
83
            }
84
        }
85
86 1436
        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...
87 3
            throw MappingException::classIsNotAValidDocument($className);
88
        }
89
90
        // find the winning document annotation
91 1434
        ksort($documentAnnots);
92 1434
        $documentAnnot = reset($documentAnnots);
93
94 1434
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
95 852
            $class->isMappedSuperclass = true;
96 1433
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
97 280
            $class->isEmbeddedDocument = true;
98 1426
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
99 67
            $class->isQueryResultDocument = true;
100 1426
        } elseif ($documentAnnot instanceof ODM\File) {
101 78
            $class->isFile = true;
102
103 78
            if ($documentAnnot->chunkSizeBytes !== null) {
104 77
                $class->setChunkSizeBytes($documentAnnot->chunkSizeBytes);
105
            }
106
        }
107
108 1434
        if (isset($documentAnnot->db)) {
109 1
            $class->setDatabase($documentAnnot->db);
110
        }
111 1434
        if (isset($documentAnnot->collection)) {
112 907
            $class->setCollection($documentAnnot->collection);
113
        }
114
        // Store bucketName as collection name for GridFS files
115 1434
        if (isset($documentAnnot->bucketName)) {
116 78
            $class->setBucketName($documentAnnot->bucketName);
117
        }
118 1434
        if (isset($documentAnnot->repositoryClass)) {
119 77
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
120
        }
121 1434
        if (isset($documentAnnot->writeConcern)) {
122 10
            $class->setWriteConcern($documentAnnot->writeConcern);
123
        }
124 1434
        if (isset($documentAnnot->indexes)) {
125 1433
            foreach ($documentAnnot->indexes as $index) {
126
                $this->addIndex($class, $index);
127
            }
128
        }
129 1434
        if (! empty($documentAnnot->readOnly)) {
130 5
            $class->markReadOnly();
131
        }
132
133 1434
        foreach ($reflClass->getProperties() as $property) {
134 1433
            if (($class->isMappedSuperclass && ! $property->isPrivate())
135
                ||
136 1433
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
137 894
                continue;
138
            }
139
140 1432
            $indexes    = [];
141 1432
            $mapping    = ['fieldName' => $property->getName()];
142 1432
            $fieldAnnot = null;
143
144 1432
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
145 1432
                if ($annot instanceof ODM\AbstractField) {
146 1432
                    $fieldAnnot = $annot;
147 1432
                    if ($annot->isDeprecated()) {
148
                        @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...
149
                    }
150
                }
151 1432
                if ($annot instanceof ODM\AbstractIndex) {
152 182
                    $indexes[] = $annot;
153
                }
154 1432
                if ($annot instanceof ODM\Indexes) {
155
                    foreach (is_array($annot->value) ? $annot->value : [$annot->value] as $index) {
156
                        $indexes[] = $index;
157
                    }
158 1432
                } elseif ($annot instanceof ODM\AlsoLoad) {
159 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
160 1432
                } elseif ($annot instanceof ODM\Version) {
161 106
                    $mapping['version'] = true;
162 1432
                } elseif ($annot instanceof ODM\Lock) {
163 1432
                    $mapping['lock'] = true;
164
                }
165
            }
166
167 1432
            if ($fieldAnnot) {
168 1432
                $mapping = array_replace($mapping, (array) $fieldAnnot);
169 1432
                $class->mapField($mapping);
170
            }
171
172 1432
            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...
173 1432
                continue;
174
            }
175
176 182
            foreach ($indexes as $index) {
177 182
                $name = $mapping['name'] ?? $mapping['fieldName'];
178 182
                $keys = [$name => $index->order ?: 'asc'];
179 182
                $this->addIndex($class, $index, $keys);
180
            }
181
        }
182
183
        // Set shard key after all fields to ensure we mapped all its keys
184 1432
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
185 88
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
186
        }
187
188
        /** @var ReflectionMethod $method */
189 1431
        foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
190
            /* Filter for the declaring class only. Callbacks from parent
191
             * classes will already be registered.
192
             */
193 1182
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
194 859
                continue;
195
            }
196
197 1182
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
198 853
                if ($annot instanceof ODM\AlsoLoad) {
199 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...
200
                }
201
202 853
                if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
203 84
                    continue;
204
                }
205
206 834
                if ($annot instanceof ODM\PrePersist) {
207 815
                    $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...
208 95
                } elseif ($annot instanceof ODM\PostPersist) {
209 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...
210 95
                } elseif ($annot instanceof ODM\PreUpdate) {
211 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...
212 90
                } elseif ($annot instanceof ODM\PostUpdate) {
213 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...
214 84
                } elseif ($annot instanceof ODM\PreRemove) {
215 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...
216 84
                } elseif ($annot instanceof ODM\PostRemove) {
217 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...
218 84
                } elseif ($annot instanceof ODM\PreLoad) {
219 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...
220 83
                } elseif ($annot instanceof ODM\PostLoad) {
221 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...
222 11
                } elseif ($annot instanceof ODM\PreFlush) {
223 1182
                    $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...
224
                }
225
            }
226
        }
227 1431
    }
228
229 215
    private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []) : void
230
    {
231 215
        $keys    = array_merge($keys, $index->keys);
232 215
        $options = [];
233 215
        $allowed = ['name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
234 215
        foreach ($allowed as $name) {
235 215
            if (! isset($index->$name)) {
236 215
                continue;
237
            }
238
239 215
            $options[$name] = $index->$name;
240
        }
241 215
        if (! empty($index->partialFilterExpression)) {
242 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
243
        }
244 215
        $options = array_merge($options, $index->options);
245 215
        $class->addIndex($keys, $options);
246 215
    }
247
248
    /**
249
     * @throws MappingException
250
     */
251 88
    private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey) : void
252
    {
253 88
        $options = [];
254 88
        $allowed = ['unique', 'numInitialChunks'];
255 88
        foreach ($allowed as $name) {
256 88
            if (! isset($shardKey->$name)) {
257 88
                continue;
258
            }
259
260
            $options[$name] = $shardKey->$name;
261
        }
262
263 88
        $class->setShardKey($shardKey->keys, $options);
264 87
    }
265
266
    /**
267
     * Factory method for the Annotation Driver
268
     *
269
     * @param array|string $paths
270
     */
271 1695
    public static function create($paths = [], ?Reader $reader = null) : AnnotationDriver
272
    {
273 1695
        if ($reader === null) {
274 1695
            $reader = new AnnotationReader();
275
        }
276 1695
        return new self($reader, $paths);
0 ignored issues
show
Bug introduced by
It seems like $paths defined by parameter $paths on line 271 can also be of type array; however, Doctrine\Common\Persiste...onDriver::__construct() does only seem to accept string|array<integer,string>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
277
    }
278
}
279