Completed
Push — master ( 5e07b2...86e4a2 )
by Andreas
05:54 queued 05:50
created

AnnotationDriver   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 233
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 96.32%

Importance

Changes 0
Metric Value
wmc 69
lcom 1
cbo 15
dl 0
loc 233
ccs 131
cts 136
cp 0.9632
rs 2.8301
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 7 2
F loadMetadataForClass() 0 172 60
A addIndex() 0 16 4
A setShardKey() 0 12 3

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