Completed
Pull Request — master (#1263)
by Andreas
10:33
created

AnnotationDriver::addIndex()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

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