Completed
Pull Request — master (#1395)
by Andreas
08:36
created

AnnotationDriver   C

Complexity

Total Complexity 68

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 78.13%

Importance

Changes 4
Bugs 0 Features 1
Metric Value
wmc 68
c 4
b 0
f 1
lcom 1
cbo 13
dl 0
loc 222
ccs 100
cts 128
cp 0.7813
rs 5.1647

4 Methods

Rating   Name   Duplication   Size   Complexity  
A registerAnnotationClasses() 0 4 1
F loadMetadataForClass() 0 168 61
A addIndex() 0 16 4
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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Mapping\Driver;
21
22
use Doctrine\Common\Annotations\AnnotationReader;
23
use Doctrine\Common\Annotations\AnnotationRegistry;
24
use Doctrine\Common\Annotations\Reader;
25
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
26
use Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
27
use Doctrine\ODM\MongoDB\Events;
28
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
29
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MappingClassMetadata;
30
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
31
use Doctrine\ODM\MongoDB\Mapping\MappingException;
32
33
/**
34
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
35
 *
36
 * @since       1.0
37
 */
38
class AnnotationDriver extends AbstractAnnotationDriver
39
{
40
    protected $entityAnnotationClasses = array(
41
        ODM\Document::class         => 1,
42
        ODM\MappedSuperclass::class => 2,
43
        ODM\EmbeddedDocument::class => 3,
44
    );
45
46
    /**
47
     * Registers annotation classes to the common registry.
48
     *
49
     * This method should be called when bootstrapping your application.
50
     */
51
    public static function registerAnnotationClasses()
52
    {
53
        AnnotationRegistry::registerFile(__DIR__ . '/../Annotations/DoctrineAnnotations.php');
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59 818
    public function loadMetadataForClass($className, ClassMetadata $class)
60
    {
61
        /** @var $class ClassMetadataInfo */
62 818
        $reflClass = $class->getReflectionClass();
63
64 818
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
65
66 818
        $documentAnnots = array();
67 818
        foreach ($classAnnotations as $annot) {
68 816
            $classAnnotations[get_class($annot)] = $annot;
69
70 816
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
71 816
                if ($annot instanceof $annotClass) {
72 816
                    $documentAnnots[$i] = $annot;
73 816
                    continue 2;
74
                }
75
            }
76
77
            // non-document class annotations
78 365
            if ($annot instanceof ODM\AbstractIndex) {
79 13
                $this->addIndex($class, $annot);
80
            }
81 365
            if ($annot instanceof ODM\Indexes) {
82 66
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
83 66
                    $this->addIndex($class, $index);
84
                }
85
            } elseif ($annot instanceof ODM\InheritanceType) {
86 284
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
87
            } elseif ($annot instanceof ODM\DiscriminatorField) {
88
                // $fieldName property is deprecated, but fall back for BC
89 110
                if (isset($annot->value)) {
90 48
                    $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...
91
                } elseif (isset($annot->name)) {
92
                    $class->setDiscriminatorField($annot->name);
93
                } elseif (isset($annot->fieldName)) {
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...inatorField::$fieldName has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
94 110
                    $class->setDiscriminatorField($annot->fieldName);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...inatorField::$fieldName has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
95
                }
96
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
97 110
                $class->setDiscriminatorMap($annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type null or 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...
98
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
99
                $class->setDiscriminatorValue($annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type array or null; 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...
100
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
101 51
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
102
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
103 365
                $class->setDefaultDiscriminatorValue($annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type array or null; 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...
104
            }
105
106
        }
107
108 818
        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...
109 3
            throw MappingException::classIsNotAValidDocument($className);
110
        }
111
112
        // find the winning document annotation
113 816
        ksort($documentAnnots);
114 816
        $documentAnnot = reset($documentAnnots);
115
116 816
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
117 277
            $class->isMappedSuperclass = true;
118
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
119 441
            $class->isEmbeddedDocument = true;
120
        }
121 816
        if (isset($documentAnnot->db)) {
122 1
            $class->setDatabase($documentAnnot->db);
123
        }
124 816
        if (isset($documentAnnot->collection)) {
125 315
            $class->setCollection($documentAnnot->collection);
126
        }
127 816
        if (isset($documentAnnot->repositoryClass) && !$class->isEmbeddedDocument) {
128 60
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
129
        }
130 816
        if (isset($documentAnnot->indexes)) {
131 815
            foreach ($documentAnnot->indexes as $index) {
132
                $this->addIndex($class, $index);
133
            }
134
        }
135 816
        if (isset($documentAnnot->requireIndexes)) {
136 809
            $class->setRequireIndexes($documentAnnot->requireIndexes);
137
        }
138 816
        if (isset($documentAnnot->slaveOkay)) {
139 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
140
        }
141
142 816
        foreach ($reflClass->getProperties() as $property) {
143 815
            if (($class->isMappedSuperclass && ! $property->isPrivate())
144
                ||
145 815
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
146 315
                continue;
147
            }
148
149 814
            $indexes = array();
150 814
            $mapping = array('fieldName' => $property->getName());
151 814
            $fieldAnnot = null;
152
153 814
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
154 814
                if ($annot instanceof ODM\AbstractField) {
155 814
                    $fieldAnnot = $annot;
156
                }
157 814
                if ($annot instanceof ODM\AbstractIndex) {
158 179
                    $indexes[] = $annot;
159
                }
160 814
                if ($annot instanceof ODM\Indexes) {
161
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
162
                        $indexes[] = $index;
163
                    }
164
                } elseif ($annot instanceof ODM\AlsoLoad) {
165 13
                    $mapping['alsoLoadFields'] = (array) $annot->value;
166
                } elseif ($annot instanceof ODM\Version) {
167 91
                    $mapping['version'] = true;
168
                } elseif ($annot instanceof ODM\Lock) {
169 814
                    $mapping['lock'] = true;
170
                }
171
            }
172
173 814
            if ($fieldAnnot) {
174 814
                $mapping = array_replace($mapping, (array) $fieldAnnot);
175 814
                $class->mapField($mapping);
176
            }
177
178 814
            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...
179 179
                foreach ($indexes as $index) {
180 179
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
181 179
                    $keys = array($name => $index->order ?: 'asc');
182 814
                    $this->addIndex($class, $index, $keys);
183
                }
184
            }
185
        }
186
187
        /** @var $method \ReflectionMethod */
188 814
        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 582
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
193 294
                continue;
194
            }
195
196 582
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
197 284
                if ($annot instanceof ODM\AlsoLoad) {
198 13
                    $class->registerAlsoLoadMethod($method->getName(), $annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type null; however, Doctrine\ODM\MongoDB\Map...egisterAlsoLoadMethod() does only seem to accept array|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...
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
199
                }
200
201 284
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
202 66
                    continue;
203
                }
204
205 265
                if ($annot instanceof ODM\PrePersist) {
206 243
                    $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
                } elseif ($annot instanceof ODM\PostPersist) {
208 11
                    $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
                } 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
                } elseif ($annot instanceof ODM\PostUpdate) {
212 62
                    $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
                } elseif ($annot instanceof ODM\PreRemove) {
214 66
                    $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
                } elseif ($annot instanceof ODM\PostRemove) {
216 64
                    $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
                } elseif ($annot instanceof ODM\PreLoad) {
218 65
                    $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
                } elseif ($annot instanceof ODM\PostLoad) {
220 66
                    $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
                } elseif ($annot instanceof ODM\PreFlush) {
222 582
                    $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 814
    }
227
228 210
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
229
    {
230 210
        $keys = array_merge($keys, $index->keys);
231 210
        $options = array();
232 210
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
233 210
        foreach ($allowed as $name) {
234 210
            if (isset($index->$name)) {
235 210
                $options[$name] = $index->$name;
236
            }
237
        }
238 210
        if (! empty($index->partialFilterExpression)) {
239 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
240
        }
241 210
        $options = array_merge($options, $index->options);
242 210
        $class->addIndex($keys, $options);
243 210
    }
244
245
    /**
246
     * Factory method for the Annotation Driver
247
     *
248
     * @param array|string $paths
249
     * @param Reader $reader
250
     * @return AnnotationDriver
251
     */
252 1004
    public static function create($paths = array(), Reader $reader = null)
253
    {
254 1004
        if ($reader === null) {
255 1004
            $reader = new AnnotationReader();
256
        }
257 1004
        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...
258
    }
259
}
260