Completed
Pull Request — master (#1448)
by Andreas
10:28
created

AnnotationDriver::loadMetadataForClass()   F

Complexity

Conditions 65
Paths > 20000

Size

Total Lines 178
Code Lines 115

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 89
CRAP Score 113.8346

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 178
ccs 89
cts 115
cp 0.7739
rs 2
cc 65
eloc 115
nc 46387250
nop 2
crap 113.8346

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 872
    public function loadMetadataForClass($className, ClassMetadata $class)
61
    {
62
        /** @var $class ClassMetadataInfo */
63 872
        $reflClass = $class->getReflectionClass();
64
65 872
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
66
67 872
        $documentAnnots = array();
68 872
        foreach ($classAnnotations as $annot) {
69 870
            $classAnnotations[get_class($annot)] = $annot;
70
71 870
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
72 870
                if ($annot instanceof $annotClass) {
73 870
                    $documentAnnots[$i] = $annot;
74 870
                    continue 2;
75
                }
76
            }
77
78
            // non-document class annotations
79 373
            if ($annot instanceof ODM\AbstractIndex) {
80 13
                $this->addIndex($class, $annot);
81
            }
82 373
            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
                }
86
            } elseif ($annot instanceof ODM\InheritanceType) {
87 292
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
88
            } elseif ($annot instanceof ODM\DiscriminatorField) {
89
                // $fieldName property is deprecated, but fall back for BC
90 114
                if (isset($annot->value)) {
91 54
                    $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
                } elseif (isset($annot->name)) {
93
                    $class->setDiscriminatorField($annot->name);
94
                } 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 114
                    $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
                }
97
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
98 114
                $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
            } 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
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
102 57
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
103
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
104 373
                $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
            }
106
107
        }
108
109 872
        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 870
        ksort($documentAnnots);
115 870
        $documentAnnot = reset($documentAnnots);
116
117 870
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
118 288
            $class->isMappedSuperclass = true;
119
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
120 295
            $class->isEmbeddedDocument = true;
121
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
122 52
            $class->isQueryResultDocument = true;
123
        }
124 870
        if (isset($documentAnnot->db)) {
125 1
            $class->setDatabase($documentAnnot->db);
126
        }
127 870
        if (isset($documentAnnot->collection)) {
128 333
            $class->setCollection($documentAnnot->collection);
129
        }
130 870
        if (isset($documentAnnot->repositoryClass) && !$class->isEmbeddedDocument && !$class->isQueryResultDocument) {
131 65
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
132
        }
133 870
        if (isset($documentAnnot->writeConcern)) {
134 11
            $class->setWriteConcern($documentAnnot->writeConcern);
135
        }
136 870
        if (isset($documentAnnot->indexes)) {
137 869
            foreach ($documentAnnot->indexes as $index) {
138
                $this->addIndex($class, $index);
139
            }
140
        }
141 870
        if (isset($documentAnnot->requireIndexes)) {
142 862
            $class->setRequireIndexes($documentAnnot->requireIndexes);
143
        }
144 870
        if (isset($documentAnnot->slaveOkay)) {
145 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
146
        }
147
148 870
        foreach ($reflClass->getProperties() as $property) {
149 869
            if (($class->isMappedSuperclass && ! $property->isPrivate())
150
                ||
151 869
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
152 327
                continue;
153
            }
154
155 868
            $indexes = array();
156 868
            $mapping = array('fieldName' => $property->getName());
157 868
            $fieldAnnot = null;
158
159 868
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
160 868
                if ($annot instanceof ODM\AbstractField) {
161 868
                    $fieldAnnot = $annot;
162
                }
163 868
                if ($annot instanceof ODM\AbstractIndex) {
164 185
                    $indexes[] = $annot;
165
                }
166 868
                if ($annot instanceof ODM\Indexes) {
167
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
168
                        $indexes[] = $index;
169
                    }
170
                } elseif ($annot instanceof ODM\AlsoLoad) {
171 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
172
                } elseif ($annot instanceof ODM\Version) {
173 97
                    $mapping['version'] = true;
174
                } elseif ($annot instanceof ODM\Lock) {
175 868
                    $mapping['lock'] = true;
176
                }
177
            }
178
179 868
            if ($fieldAnnot) {
180 868
                $mapping = array_replace($mapping, (array) $fieldAnnot);
181 868
                $class->mapField($mapping);
182
            }
183
184 868
            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 185
                foreach ($indexes as $index) {
186 185
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
187 185
                    $keys = array($name => $index->order ?: 'asc');
188 868
                    $this->addIndex($class, $index, $keys);
189
                }
190
            }
191
        }
192
193
        // Set shard key after all fields to ensure we mapped all its keys
194 868
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
195 61
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
196
        }
197
198
        /** @var $method \ReflectionMethod */
199 867
        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 605
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
204 299
                continue;
205
            }
206
207 605
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
208 293
                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
                }
211
212 293
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
213 72
                    continue;
214
                }
215
216 274
                if ($annot instanceof ODM\PrePersist) {
217 253
                    $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
                } 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
                } 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
                } 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
                } 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
                } 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
                } 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
                } 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
                } elseif ($annot instanceof ODM\PreFlush) {
233 605
                    $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
                }
235
            }
236
        }
237 867
    }
238
239 208
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
240
    {
241 208
        $keys = array_merge($keys, $index->keys);
242 208
        $options = array();
243 208
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
244 208
        foreach ($allowed as $name) {
245 208
            if (isset($index->$name)) {
246 208
                $options[$name] = $index->$name;
247
            }
248
        }
249 208
        if (! empty($index->partialFilterExpression)) {
250 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
251
        }
252 208
        $options = array_merge($options, $index->options);
253 208
        $class->addIndex($keys, $options);
254 208
    }
255
256
    /**
257
     * @param ClassMetadataInfo $class
258
     * @param ODM\ShardKey      $shardKey
259
     *
260
     * @throws MappingException
261
     */
262 61
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
263
    {
264 61
        $options = array();
265 61
        $allowed = array('unique', 'numInitialChunks');
266 61
        foreach ($allowed as $name) {
267 61
            if (isset($shardKey->$name)) {
268 61
                $options[$name] = $shardKey->$name;
269
            }
270
        }
271
272 61
        $class->setShardKey($shardKey->keys, $options);
273 60
    }
274
275
    /**
276
     * Factory method for the Annotation Driver
277
     *
278
     * @param array|string $paths
279
     * @param Reader $reader
280
     * @return AnnotationDriver
281
     */
282 1092
    public static function create($paths = array(), Reader $reader = null)
283
    {
284 1092
        if ($reader === null) {
285 1092
            $reader = new AnnotationReader();
286
        }
287 1092
        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