Completed
Pull Request — master (#1263)
by Andreas
11:16
created

AnnotationDriver::addIndex()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 16
ccs 12
cts 12
cp 1
rs 9.2
cc 4
eloc 11
nc 6
nop 3
crap 4
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\AggregationResultDocument::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 845
    public function loadMetadataForClass($className, ClassMetadata $class)
61
    {
62
        /** @var $class ClassMetadataInfo */
63 845
        $reflClass = $class->getReflectionClass();
64
65 845
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
66
67 845
        $documentAnnots = array();
68 845
        foreach ($classAnnotations as $annot) {
69 843
            $classAnnotations[get_class($annot)] = $annot;
70
71 843
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
72 843
                if ($annot instanceof $annotClass) {
73 843
                    $documentAnnots[$i] = $annot;
74 843
                    continue 2;
75
                }
76
            }
77
78
            // non-document class annotations
79 383
            if ($annot instanceof ODM\AbstractIndex) {
80 13
                $this->addIndex($class, $annot);
81
            }
82 383
            if ($annot instanceof ODM\Indexes) {
83 72
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
84 72
                    $this->addIndex($class, $index);
85
                }
86
            } elseif ($annot instanceof ODM\InheritanceType) {
87 297
                $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 116
                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 116
                    $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 116
                $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 383
                $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 845
        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 843
        ksort($documentAnnots);
115 843
        $documentAnnot = reset($documentAnnots);
116
117 843
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
118 287
            $class->isMappedSuperclass = true;
119
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
120 451
            $class->isEmbeddedDocument = true;
121
        } elseif ($documentAnnot instanceof ODM\AggregationResultDocument) {
122 52
            $class->isAggregationResultDocument = true;
123
        }
124 843
        if (isset($documentAnnot->db)) {
125 1
            $class->setDatabase($documentAnnot->db);
126
        }
127 843
        if (isset($documentAnnot->collection)) {
128 330
            $class->setCollection($documentAnnot->collection);
129
        }
130 843
        if (isset($documentAnnot->repositoryClass) && !$class->isEmbeddedDocument && !$class->isAggregationResultDocument) {
131 67
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
132
        }
133 843
        if (isset($documentAnnot->indexes)) {
134 842
            foreach ($documentAnnot->indexes as $index) {
135
                $this->addIndex($class, $index);
136
            }
137
        }
138 843
        if (isset($documentAnnot->requireIndexes)) {
139 835
            $class->setRequireIndexes($documentAnnot->requireIndexes);
140
        }
141 843
        if (isset($documentAnnot->slaveOkay)) {
142 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
143
        }
144
145 843
        foreach ($reflClass->getProperties() as $property) {
146 842
            if (($class->isMappedSuperclass && ! $property->isPrivate())
147
                ||
148 842
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
149 324
                continue;
150
            }
151
152 841
            $indexes = array();
153 841
            $mapping = array('fieldName' => $property->getName());
154 841
            $fieldAnnot = null;
155
156 841
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
157 841
                if ($annot instanceof ODM\AbstractField) {
158 841
                    $fieldAnnot = $annot;
159
                }
160 841
                if ($annot instanceof ODM\AbstractIndex) {
161 187
                    $indexes[] = $annot;
162
                }
163 841
                if ($annot instanceof ODM\Indexes) {
164
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
165
                        $indexes[] = $index;
166
                    }
167
                } elseif ($annot instanceof ODM\AlsoLoad) {
168 13
                    $mapping['alsoLoadFields'] = (array) $annot->value;
169
                } elseif ($annot instanceof ODM\Version) {
170 97
                    $mapping['version'] = true;
171
                } elseif ($annot instanceof ODM\Lock) {
172 841
                    $mapping['lock'] = true;
173
                }
174
            }
175
176 841
            if ($fieldAnnot) {
177 841
                $mapping = array_replace($mapping, (array) $fieldAnnot);
178 841
                $class->mapField($mapping);
179
            }
180
181 841
            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...
182 187
                foreach ($indexes as $index) {
183 187
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
184 187
                    $keys = array($name => $index->order ?: 'asc');
185 841
                    $this->addIndex($class, $index, $keys);
186
                }
187
            }
188
        }
189
190
        // Set shard key after all fields to ensure we mapped all its keys
191 841
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
192 61
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
193
        }
194
195
        /** @var $method \ReflectionMethod */
196 840
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
197
            /* Filter for the declaring class only. Callbacks from parent
198
             * classes will already be registered.
199
             */
200 599
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
201 303
                continue;
202
            }
203
204 599
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
205 293
                if ($annot instanceof ODM\AlsoLoad) {
206 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...
207
                }
208
209 293
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
210 72
                    continue;
211
                }
212
213 274
                if ($annot instanceof ODM\PrePersist) {
214 252
                    $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...
215
                } elseif ($annot instanceof ODM\PostPersist) {
216 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...
217
                } elseif ($annot instanceof ODM\PreUpdate) {
218 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...
219
                } elseif ($annot instanceof ODM\PostUpdate) {
220 68
                    $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...
221
                } elseif ($annot instanceof ODM\PreRemove) {
222 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...
223
                } elseif ($annot instanceof ODM\PostRemove) {
224 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...
225
                } elseif ($annot instanceof ODM\PreLoad) {
226 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...
227
                } elseif ($annot instanceof ODM\PostLoad) {
228 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...
229
                } elseif ($annot instanceof ODM\PreFlush) {
230 599
                    $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...
231
                }
232
            }
233
        }
234 840
    }
235
236 218
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
237
    {
238 218
        $keys = array_merge($keys, $index->keys);
239 218
        $options = array();
240 218
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
241 218
        foreach ($allowed as $name) {
242 218
            if (isset($index->$name)) {
243 218
                $options[$name] = $index->$name;
244
            }
245
        }
246 218
        if (! empty($index->partialFilterExpression)) {
247 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
248
        }
249 218
        $options = array_merge($options, $index->options);
250 218
        $class->addIndex($keys, $options);
251 218
    }
252
253
    /**
254
     * @param ClassMetadataInfo $class
255
     * @param ODM\ShardKey      $shardKey
256
     *
257
     * @throws MappingException
258
     */
259 61
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
260
    {
261 61
        $options = array();
262 61
        $allowed = array('unique', 'numInitialChunks');
263 61
        foreach ($allowed as $name) {
264 61
            if (isset($shardKey->$name)) {
265 61
                $options[$name] = $shardKey->$name;
266
            }
267
        }
268
269 61
        $class->setShardKey($shardKey->keys, $options);
270 60
    }
271
272
    /**
273
     * Factory method for the Annotation Driver
274
     *
275
     * @param array|string $paths
276
     * @param Reader $reader
277
     * @return AnnotationDriver
278
     */
279 1049
    public static function create($paths = array(), Reader $reader = null)
280
    {
281 1049
        if ($reader === null) {
282 1049
            $reader = new AnnotationReader();
283
        }
284 1049
        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...
285
    }
286
}
287