Completed
Pull Request — master (#1716)
by Maciej
10:47
created

AnnotationDriver   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 237
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 237
c 0
b 0
f 0
ccs 131
cts 136
cp 0.9632
rs 2.8301

4 Methods

Rating   Name   Duplication   Size   Complexity  
F loadMetadataForClass() 0 173 60
A addIndex() 0 16 4
A setShardKey() 0 12 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
namespace Doctrine\ODM\MongoDB\Mapping\Driver;
4
5
use Doctrine\Common\Annotations\AnnotationReader;
6
use Doctrine\Common\Annotations\AnnotationRegistry;
7
use Doctrine\Common\Annotations\Reader;
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
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 as MappingClassMetadata;
13
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
14
use Doctrine\ODM\MongoDB\Mapping\MappingException;
15
16
/**
17
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
18
 *
19
 * @since       1.0
20
 */
21
class AnnotationDriver extends AbstractAnnotationDriver
22
{
23
    protected $entityAnnotationClasses = array(
24
        ODM\Document::class            => 1,
25
        ODM\MappedSuperclass::class    => 2,
26
        ODM\EmbeddedDocument::class    => 3,
27
        ODM\QueryResultDocument::class => 4,
28
    );
29
30
    /**
31
     * {@inheritdoc}
32
     */
33 1378
    public function loadMetadataForClass($className, ClassMetadata $class)
34
    {
35
        /** @var $class ClassMetadataInfo */
36 1378
        $reflClass = $class->getReflectionClass();
37
38 1378
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
39
40 1378
        $documentAnnots = array();
41 1378
        foreach ($classAnnotations as $annot) {
42 1376
            $classAnnotations[get_class($annot)] = $annot;
43
44 1376
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
45 1376
                if ($annot instanceof $annotClass) {
46 1376
                    $documentAnnots[$i] = $annot;
47 1376
                    continue 2;
48
                }
49
            }
50
51
            // non-document class annotations
52 917
            if ($annot instanceof ODM\AbstractIndex) {
53 6
                $this->addIndex($class, $annot);
54
            }
55 917
            if ($annot instanceof ODM\Indexes) {
56 62
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
57 62
                    $this->addIndex($class, $index);
58
                }
59 901
            } elseif ($annot instanceof ODM\InheritanceType) {
60 840
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
61 899
            } elseif ($annot instanceof ODM\DiscriminatorField) {
62 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...
63 898
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
64 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...
65 837
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
66
                $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...
67 837
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
68 46
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
69 835
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
70 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...
71 830
            } elseif ($annot instanceof ODM\ReadPreference) {
72 917
                $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...fo::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...
73
            }
74
75
        }
76
77 1378
        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...
78 3
            throw MappingException::classIsNotAValidDocument($className);
79
        }
80
81
        // find the winning document annotation
82 1376
        ksort($documentAnnots);
83 1376
        $documentAnnot = reset($documentAnnots);
84
85 1376
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
86 829
            $class->isMappedSuperclass = true;
87 1375
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
88 234
            $class->isEmbeddedDocument = true;
89 1368
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
90 47
            $class->isQueryResultDocument = true;
91
        }
92 1376
        if (isset($documentAnnot->db)) {
93 1
            $class->setDatabase($documentAnnot->db);
94
        }
95 1376
        if (isset($documentAnnot->collection)) {
96 874
            $class->setCollection($documentAnnot->collection);
97
        }
98 1376
        if (isset($documentAnnot->repositoryClass)) {
99 57
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
100
        }
101 1376
        if (isset($documentAnnot->writeConcern)) {
102 10
            $class->setWriteConcern($documentAnnot->writeConcern);
103
        }
104 1376
        if (isset($documentAnnot->indexes)) {
105 1375
            foreach ($documentAnnot->indexes as $index) {
106
                $this->addIndex($class, $index);
107
            }
108
        }
109 1376
        if (! empty($documentAnnot->readOnly)) {
110 5
            $class->markReadOnly();
111
        }
112
113 1376
        foreach ($reflClass->getProperties() as $property) {
114 1375
            if (($class->isMappedSuperclass && ! $property->isPrivate())
115
                ||
116 1375
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
117 871
                continue;
118
            }
119
120 1374
            $indexes = array();
121 1374
            $mapping = array('fieldName' => $property->getName());
122 1374
            $fieldAnnot = null;
123
124 1374
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
125 1374
                if ($annot instanceof ODM\AbstractField) {
126 1374
                    $fieldAnnot = $annot;
127 1374
                    if ($annot->isDeprecated()) {
128
                        @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...
129
                    }
130
                }
131 1374
                if ($annot instanceof ODM\AbstractIndex) {
132 159
                    $indexes[] = $annot;
133
                }
134 1374
                if ($annot instanceof ODM\Indexes) {
135
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
136
                        $indexes[] = $index;
137
                    }
138 1374
                } elseif ($annot instanceof ODM\AlsoLoad) {
139 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
140 1374
                } elseif ($annot instanceof ODM\Version) {
141 67
                    $mapping['version'] = true;
142 1374
                } elseif ($annot instanceof ODM\Lock) {
143 1374
                    $mapping['lock'] = true;
144
                }
145
            }
146
147 1374
            if ($fieldAnnot) {
148 1374
                $mapping = array_replace($mapping, (array) $fieldAnnot);
149 1374
                $class->mapField($mapping);
150
            }
151
152 1374
            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...
153 159
                foreach ($indexes as $index) {
154 159
                    $name = $mapping['name'] ?? $mapping['fieldName'];
155 159
                    $keys = array($name => $index->order ?: 'asc');
156 1374
                    $this->addIndex($class, $index, $keys);
157
                }
158
            }
159
        }
160
161
        // Set shard key after all fields to ensure we mapped all its keys
162 1374
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
163 59
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
164
        }
165
166
        /** @var $method \ReflectionMethod */
167 1373
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
168
            /* Filter for the declaring class only. Callbacks from parent
169
             * classes will already be registered.
170
             */
171 1139
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
172 837
                continue;
173
            }
174
175 1139
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
176 827
                if ($annot instanceof ODM\AlsoLoad) {
177 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...
178
                }
179
180 827
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
181 64
                    continue;
182
                }
183
184 808
                if ($annot instanceof ODM\PrePersist) {
185 793
                    $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...
186 71
                } elseif ($annot instanceof ODM\PostPersist) {
187 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...
188 71
                } elseif ($annot instanceof ODM\PreUpdate) {
189 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...
190 66
                } elseif ($annot instanceof ODM\PostUpdate) {
191 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...
192 64
                } elseif ($annot instanceof ODM\PreRemove) {
193 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...
194 64
                } elseif ($annot instanceof ODM\PostRemove) {
195 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...
196 64
                } elseif ($annot instanceof ODM\PreLoad) {
197 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...
198 63
                } elseif ($annot instanceof ODM\PostLoad) {
199 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...
200 11
                } elseif ($annot instanceof ODM\PreFlush) {
201 1139
                    $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...
202
                }
203
            }
204
        }
205 1373
    }
206
207 181
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
208
    {
209 181
        $keys = array_merge($keys, $index->keys);
210 181
        $options = array();
211 181
        $allowed = array('name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds');
212 181
        foreach ($allowed as $name) {
213 181
            if (isset($index->$name)) {
214 181
                $options[$name] = $index->$name;
215
            }
216
        }
217 181
        if (! empty($index->partialFilterExpression)) {
218 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
219
        }
220 181
        $options = array_merge($options, $index->options);
221 181
        $class->addIndex($keys, $options);
222 181
    }
223
224
    /**
225
     * @param ClassMetadataInfo $class
226
     * @param ODM\ShardKey      $shardKey
227
     *
228
     * @throws MappingException
229
     */
230 59
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
231
    {
232 59
        $options = array();
233 59
        $allowed = array('unique', 'numInitialChunks');
234 59
        foreach ($allowed as $name) {
235 59
            if (isset($shardKey->$name)) {
236 59
                $options[$name] = $shardKey->$name;
237
            }
238
        }
239
240 59
        $class->setShardKey($shardKey->keys, $options);
241 58
    }
242
243
    /**
244
     * Factory method for the Annotation Driver
245
     *
246
     * @param array|string $paths
247
     * @param Reader $reader
248
     * @return AnnotationDriver
249
     */
250 1664
    public static function create($paths = array(), Reader $reader = null)
251
    {
252 1664
        if ($reader === null) {
253 1664
            $reader = new AnnotationReader();
254
        }
255 1664
        return new self($reader, $paths);
0 ignored issues
show
Compatibility introduced by
$reader of type object<Doctrine\Common\Annotations\Reader> is not a sub-type of object<Doctrine\Common\A...tions\AnnotationReader>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Annotations\Reader to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
256
    }
257
}
258