Completed
Pull Request — master (#1567)
by
unknown
23:12 queued 20:21
created

AnnotationDriver   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 92.7%

Importance

Changes 0
Metric Value
wmc 73
lcom 1
cbo 14
dl 0
loc 252
ccs 165
cts 178
cp 0.927
rs 3.8271
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A registerAnnotationClasses() 0 4 1
F loadMetadataForClass() 0 178 63
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
 * 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
        ODM\QueryResultDocument::class => 4,
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 899
    public function loadMetadataForClass($className, ClassMetadata $class)
61
    {
62
        /** @var $class ClassMetadataInfo */
63 899
        $reflClass = $class->getReflectionClass();
64
65 899
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
66
67 899
        $documentAnnots = array();
68 899
        foreach ($classAnnotations as $annot) {
69 897
            $classAnnotations[get_class($annot)] = $annot;
70
71 897
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
72 897
                if ($annot instanceof $annotClass) {
73 897
                    $documentAnnots[$i] = $annot;
74 897
                    continue 2;
75
                }
76 585
            }
77
78
            // non-document class annotations
79 393
            if ($annot instanceof ODM\AbstractIndex) {
80 13
                $this->addIndex($class, $annot);
81 13
            }
82 393
            if ($annot instanceof ODM\Indexes) {
83 64
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
84 64
                    $this->addIndex($class, $index);
85 64
                }
86 393
            } elseif ($annot instanceof ODM\InheritanceType) {
87 301
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
88 383
            } elseif ($annot instanceof ODM\DiscriminatorField) {
89
                // $fieldName property is deprecated, but fall back for BC
90 121
                if (isset($annot->value)) {
91 121
                    $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 121
                } elseif (isset($annot->name)) {
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...scriminatorField::$name has been deprecated with message: property was deprecated in 1.2 and will be removed in 2.0

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...
93
                    $class->setDiscriminatorField($annot->name);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...scriminatorField::$name has been deprecated with message: property was deprecated in 1.2 and will be removed in 2.0

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
                } elseif (isset($annot->fieldName)) {
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...inatorField::$fieldName has been deprecated with message: property was deprecated in 1.0.0-BETA10 and will be removed in 2.0

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
                    $class->setDiscriminatorField($annot->fieldName);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...inatorField::$fieldName has been deprecated with message: property was deprecated in 1.0.0-BETA10 and will be removed in 2.0

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
                }
97 381
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
98 121
                $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 380
            } 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 318
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
102 57
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
103 318
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
104 57
                $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 57
            }
106
107 899
        }
108
109 899
        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 3
            throw MappingException::classIsNotAValidDocument($className);
111
        }
112
113
        // find the winning document annotation
114 897
        ksort($documentAnnots);
115 897
        $documentAnnot = reset($documentAnnots);
116
117 897
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
118 290
            $class->isMappedSuperclass = true;
119 897
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
120 298
            $class->isEmbeddedDocument = true;
121 896
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
122 52
            $class->isQueryResultDocument = true;
123 52
        }
124 897
        if (isset($documentAnnot->db)) {
125 1
            $class->setDatabase($documentAnnot->db);
126 1
        }
127 897
        if (isset($documentAnnot->collection)) {
128 349
            $class->setCollection($documentAnnot->collection);
129 350
        }
130 897
        if (isset($documentAnnot->repositoryClass)) {
131 65
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
132 65
        }
133 897
        if (isset($documentAnnot->writeConcern)) {
134 11
            $class->setWriteConcern($documentAnnot->writeConcern);
135 12
        }
136 897
        if (isset($documentAnnot->indexes)) {
137 896
            foreach ($documentAnnot->indexes as $index) {
138
                $this->addIndex($class, $index);
139 896
            }
140 896
        }
141 897
        if (isset($documentAnnot->requireIndexes)) {
142 889
            $class->setRequireIndexes($documentAnnot->requireIndexes);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ODM\MongoDB\Map...fo::setRequireIndexes() has been deprecated with message: method was deprecated in 1.2 and will be removed in 2.0

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
143 889
        }
144 897
        if (isset($documentAnnot->slaveOkay)) {
145 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
146 1
        }
147
148 897
        foreach ($reflClass->getProperties() as $property) {
149 896
            if (($class->isMappedSuperclass && ! $property->isPrivate())
150
                ||
151 896
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
152 335
                continue;
153
            }
154
155 895
            $indexes = array();
156 895
            $mapping = array('fieldName' => $property->getName());
157 895
            $fieldAnnot = null;
158
159 895
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
160 895
                if ($annot instanceof ODM\AbstractField) {
161 895
                    $fieldAnnot = $annot;
162 895
                }
163 895
                if ($annot instanceof ODM\AbstractIndex) {
164 188
                    $indexes[] = $annot;
165 188
                }
166 895
                if ($annot instanceof ODM\Indexes) {
167
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
168
                        $indexes[] = $index;
169
                    }
170 895
                } elseif ($annot instanceof ODM\AlsoLoad) {
171 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
172 895
                } elseif ($annot instanceof ODM\Version) {
173 97
                    $mapping['version'] = true;
174 895
                } elseif ($annot instanceof ODM\Lock) {
175 24
                    $mapping['lock'] = true;
176 24
                }
177 895
            }
178
179 895
            if ($fieldAnnot) {
180 895
                $mapping = array_replace($mapping, (array) $fieldAnnot);
181 895
                $class->mapField($mapping);
182 895
            }
183
184 895
            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...
185 188
                foreach ($indexes as $index) {
186 188
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
187 188
                    $keys = array($name => $index->order ?: 'asc');
188 188
                    $this->addIndex($class, $index, $keys);
189 188
                }
190 188
            }
191 897
        }
192
193
        // Set shard key after all fields to ensure we mapped all its keys
194 895
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
195 72
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
196 71
        }
197
198
        /** @var $method \ReflectionMethod */
199 894
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
200
            /* Filter for the declaring class only. Callbacks from parent
201
             * classes will already be registered.
202
             */
203 621
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
204 302
                continue;
205
            }
206
207 621
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
208 295
                if ($annot instanceof ODM\AlsoLoad) {
209 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...
210 13
                }
211
212 295
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
213 72
                    continue;
214
                }
215
216 276
                if ($annot instanceof ODM\PrePersist) {
217 255
                    $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...
218 276
                } elseif ($annot instanceof ODM\PostPersist) {
219 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...
220 86
                } elseif ($annot instanceof ODM\PreUpdate) {
221 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...
222 85
                } elseif ($annot instanceof ODM\PostUpdate) {
223 67
                    $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...
224 80
                } elseif ($annot instanceof ODM\PreRemove) {
225 72
                    $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...
226 74
                } elseif ($annot instanceof ODM\PostRemove) {
227 70
                    $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...
228 74
                } elseif ($annot instanceof ODM\PreLoad) {
229 71
                    $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...
230 74
                } elseif ($annot instanceof ODM\PostLoad) {
231 72
                    $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...
232 73
                } elseif ($annot instanceof ODM\PreFlush) {
233 11
                    $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...
234 11
                }
235 621
            }
236 894
        }
237 894
    }
238
239 211
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
240
    {
241 211
        $keys = array_merge($keys, $index->keys);
242 211
        $options = array();
243 211
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
244 211
        foreach ($allowed as $name) {
245 211
            if (isset($index->$name)) {
246 211
                $options[$name] = $index->$name;
247 211
            }
248 211
        }
249 211
        if (! empty($index->partialFilterExpression)) {
250 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
251 2
        }
252 211
        $options = array_merge($options, $index->options);
253 211
        $class->addIndex($keys, $options);
254 211
    }
255
256
    /**
257
     * @param ClassMetadataInfo $class
258
     * @param ODM\ShardKey      $shardKey
259
     *
260
     * @throws MappingException
261
     */
262 72
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
263
    {
264 72
        $options = array();
265 72
        $allowed = array('unique', 'numInitialChunks');
266 72
        foreach ($allowed as $name) {
267 72
            if (isset($shardKey->$name)) {
268 1
                $options[$name] = $shardKey->$name;
269 1
            }
270 72
        }
271
272 72
        $class->setShardKey($shardKey->keys, $options);
273 71
    }
274
275
    /**
276
     * Factory method for the Annotation Driver
277
     *
278
     * @param array|string $paths
279
     * @param Reader $reader
280
     * @return AnnotationDriver
281
     */
282 1129
    public static function create($paths = array(), Reader $reader = null)
283
    {
284 1129
        if ($reader === null) {
285 1129
            $reader = new AnnotationReader();
286 1129
        }
287 1129
        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...
288
    }
289
}
290