Completed
Pull Request — 1.0.x (#1420)
by Andreas
10:22 queued 01:18
created

AnnotationDriver   C

Complexity

Total Complexity 67

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 93.63%

Importance

Changes 0
Metric Value
wmc 67
lcom 1
cbo 13
dl 0
loc 219
ccs 147
cts 157
cp 0.9363
rs 5.2808
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A registerAnnotationClasses() 0 4 1
F loadMetadataForClass() 0 168 61
A addIndex() 0 13 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
 * 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\ClassMetadataInfo;
30
use Doctrine\ODM\MongoDB\Mapping\MappingException;
31
32
/**
33
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
34
 *
35
 * @since       1.0
36
 * @author      Jonathan H. Wage <[email protected]>
37
 * @author      Roman Borschel <[email protected]>
38
 */
39
class AnnotationDriver extends AbstractAnnotationDriver
40
{
41
    protected $entityAnnotationClasses = array(
42
        'Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\Document' => 1,
43
        'Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\MappedSuperclass' => 2,
44
        'Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\EmbeddedDocument' => 3,
45
    );
46
47
    /**
48
     * Registers annotation classes to the common registry.
49
     *
50
     * This method should be called when bootstrapping your application.
51
     */
52
    public static function registerAnnotationClasses()
53
    {
54
        AnnotationRegistry::registerFile(__DIR__ . '/../Annotations/DoctrineAnnotations.php');
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 757
    public function loadMetadataForClass($className, ClassMetadata $class)
61
    {
62
        /** @var $class ClassMetadataInfo */
63 757
        $reflClass = $class->getReflectionClass();
64
65 757
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
66
67 757
        $documentAnnots = array();
68 757
        foreach ($classAnnotations as $annot) {
69 755
            $classAnnotations[get_class($annot)] = $annot;
70
71 755
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
72 755
                if ($annot instanceof $annotClass) {
73 755
                    $documentAnnots[$i] = $annot;
74 755
                    continue 2;
75
                }
76 481
            }
77
78
            // non-document class annotations
79 303
            if ($annot instanceof ODM\AbstractIndex) {
80 13
                $this->addIndex($class, $annot);
81 13
            }
82 303
            if ($annot instanceof ODM\Indexes) {
83 28
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
84 28
                    $this->addIndex($class, $index);
85 28
                }
86 303
            } elseif ($annot instanceof ODM\InheritanceType) {
87 225
                $class->setInheritanceType(constant('Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata::INHERITANCE_TYPE_'.$annot->value));
88 294
            } elseif ($annot instanceof ODM\DiscriminatorField) {
89
                // $fieldName property is deprecated, but fall back for BC
90 75
                if (isset($annot->value)) {
91 19
                    $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...
92 75
                } elseif (isset($annot->name)) {
93
                    $class->setDiscriminatorField($annot->name);
94 72
                } 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...
95 72
                    $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...
96 72
                }
97 292
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
98 75
                $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...
99 291
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
100
                $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...
101 240
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
102 21
                $class->setChangeTrackingPolicy(constant('Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata::CHANGETRACKING_'.$annot->value));
103 240
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
104 22
                $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...
105 22
            }
106
107 757
        }
108
109 757
        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...
110 2
            throw MappingException::classIsNotAValidDocument($className);
111
        }
112
113
        // find the winning document annotation
114 755
        ksort($documentAnnots);
115 755
        $documentAnnot = reset($documentAnnots);
116
117 755
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
118 232
            $class->isMappedSuperclass = true;
119 755
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
120 380
            $class->isEmbeddedDocument = true;
121 380
        }
122 755
        if (isset($documentAnnot->db)) {
123 1
            $class->setDatabase($documentAnnot->db);
124 1
        }
125 755
        if (isset($documentAnnot->collection)) {
126 271
            $class->setCollection($documentAnnot->collection);
127 271
        }
128 755
        if (isset($documentAnnot->repositoryClass) && !$class->isEmbeddedDocument) {
129 30
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
130 30
        }
131 755
        if (isset($documentAnnot->indexes)) {
132 754
            foreach ($documentAnnot->indexes as $index) {
133
                $this->addIndex($class, $index);
134 754
            }
135 754
        }
136 755
        if (isset($documentAnnot->requireIndexes)) {
137 748
            $class->setRequireIndexes($documentAnnot->requireIndexes);
138 748
        }
139 755
        if (isset($documentAnnot->slaveOkay)) {
140 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
141 1
        }
142
143 755
        foreach ($reflClass->getProperties() as $property) {
144 754
            if (($class->isMappedSuperclass && ! $property->isPrivate())
145
                ||
146 754
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
147 264
                continue;
148
            }
149
150 753
            $indexes = array();
151 753
            $mapping = array('fieldName' => $property->getName());
152 753
            $fieldAnnot = null;
153
154 753
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
155 753
                if ($annot instanceof ODM\AbstractField) {
156 753
                    $fieldAnnot = $annot;
157 753
                }
158 753
                if ($annot instanceof ODM\AbstractIndex) {
159 138
                    $indexes[] = $annot;
160 138
                }
161 753
                if ($annot instanceof ODM\Indexes) {
162
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
163
                        $indexes[] = $index;
164
                    }
165 753
                } elseif ($annot instanceof ODM\AlsoLoad) {
166 13
                    $mapping['alsoLoadFields'] = (array) $annot->value;
167 753
                } elseif ($annot instanceof ODM\Version) {
168 61
                    $mapping['version'] = true;
169 753
                } elseif ($annot instanceof ODM\Lock) {
170 24
                    $mapping['lock'] = true;
171 24
                }
172 753
            }
173
174 753
            if ($fieldAnnot) {
175 753
                $mapping = array_replace($mapping, (array) $fieldAnnot);
176 753
                $class->mapField($mapping);
177 753
            }
178
179 753
            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...
180 138
                foreach ($indexes as $index) {
181 138
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
182 138
                    $keys = array($name => $index->order ?: 'asc');
183 139
                    $this->addIndex($class, $index, $keys);
184 138
                }
185 138
            }
186 755
        }
187
188
        /** @var $method \ReflectionMethod */
189 753
        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 518
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
194 243
                continue;
195
            }
196
197 518
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
198 238
                if ($annot instanceof ODM\AlsoLoad) {
199 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...
200 13
                }
201
202 238
                if ( ! isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\HasLifecycleCallbacks'])) {
203 37
                    continue;
204
                }
205
206 219
                if ($annot instanceof ODM\PrePersist) {
207 198
                    $class->addLifecycleCallback($method->getName(), Events::prePersist);
208 219
                } elseif ($annot instanceof ODM\PostPersist) {
209 11
                    $class->addLifecycleCallback($method->getName(), Events::postPersist);
210 51
                } elseif ($annot instanceof ODM\PreUpdate) {
211 15
                    $class->addLifecycleCallback($method->getName(), Events::preUpdate);
212 50
                } elseif ($annot instanceof ODM\PostUpdate) {
213 32
                    $class->addLifecycleCallback($method->getName(), Events::postUpdate);
214 45
                } elseif ($annot instanceof ODM\PreRemove) {
215 37
                    $class->addLifecycleCallback($method->getName(), Events::preRemove);
216 39
                } elseif ($annot instanceof ODM\PostRemove) {
217 35
                    $class->addLifecycleCallback($method->getName(), Events::postRemove);
218 39
                } elseif ($annot instanceof ODM\PreLoad) {
219 36
                    $class->addLifecycleCallback($method->getName(), Events::preLoad);
220 39
                } elseif ($annot instanceof ODM\PostLoad) {
221 37
                    $class->addLifecycleCallback($method->getName(), Events::postLoad);
222 38
                } elseif ($annot instanceof ODM\PreFlush) {
223 11
                    $class->addLifecycleCallback($method->getName(), Events::preFlush);
224 11
                }
225 518
            }
226 753
        }
227 753
    }
228
229 160
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
230
    {
231 160
        $keys = array_merge($keys, $index->keys);
232 160
        $options = array();
233 160
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
234 160
        foreach ($allowed as $name) {
235 160
            if (isset($index->$name)) {
236 160
                $options[$name] = $index->$name;
237 160
            }
238 160
        }
239 160
        $options = array_merge($options, $index->options);
240 160
        $class->addIndex($keys, $options);
241 160
    }
242
243
    /**
244
     * Factory method for the Annotation Driver
245
     *
246
     * @param array|string $paths
247
     * @param Reader $reader
248
     * @return AnnotationDriver
249
     */
250 945
    public static function create($paths = array(), Reader $reader = null)
251
    {
252 945
        if ($reader === null) {
253 945
            $reader = new AnnotationReader();
254 945
        }
255 945
        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