Completed
Pull Request — master (#1385)
by Andreas
18:26
created

AnnotationDriver::setShardKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 12
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 7
nc 3
nop 2
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
 */
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
    );
45
46
    /**
47
     * Registers annotation classes to the common registry.
48
     *
49
     * This method should be called when bootstrapping your application.
50
     */
51
    public static function registerAnnotationClasses()
52
    {
53
        AnnotationRegistry::registerFile(__DIR__ . '/../Annotations/DoctrineAnnotations.php');
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59 842
    public function loadMetadataForClass($className, ClassMetadata $class)
60
    {
61
        /** @var $class ClassMetadataInfo */
62 842
        $reflClass = $class->getReflectionClass();
63
64 842
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
65
66 842
        $documentAnnots = array();
67 842
        foreach ($classAnnotations as $annot) {
68 840
            $classAnnotations[get_class($annot)] = $annot;
69
70 840
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
71 840
                if ($annot instanceof $annotClass) {
72 840
                    $documentAnnots[$i] = $annot;
73 840
                    continue 2;
74
                }
75 575
            }
76
77
            // non-document class annotations
78 389
            if ($annot instanceof ODM\AbstractIndex) {
79 13
                $this->addIndex($class, $annot);
80 13
            }
81 389
            if ($annot instanceof ODM\Indexes) {
82 72
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
83 72
                    $this->addIndex($class, $index);
84 72
                }
85 389
            } elseif ($annot instanceof ODM\InheritanceType) {
86 294
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
87 371
            } elseif ($annot instanceof ODM\DiscriminatorField) {
88
                // $fieldName property is deprecated, but fall back for BC
89 116
                if (isset($annot->value)) {
90 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...
91 116
                } elseif (isset($annot->name)) {
92
                    $class->setDiscriminatorField($annot->name);
93 113
                } 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...
94 113
                    $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...
95 113
                }
96 369
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
97 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...
98 368
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
99
                $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...
100 311
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
101 57
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
102 311
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
103 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...
104 57
            }
105
106 842
        }
107
108 842
        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...
109 3
            throw MappingException::classIsNotAValidDocument($className);
110
        }
111
112
        // find the winning document annotation
113 840
        ksort($documentAnnots);
114 840
        $documentAnnot = reset($documentAnnots);
115
116 840
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
117 284
            $class->isMappedSuperclass = true;
118 840
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
119 448
            $class->isEmbeddedDocument = true;
120 448
        }
121 840
        if (isset($documentAnnot->db)) {
122 1
            $class->setDatabase($documentAnnot->db);
123 1
        }
124 840
        if (isset($documentAnnot->collection)) {
125 330
            $class->setCollection($documentAnnot->collection);
126 330
        }
127 840
        if (isset($documentAnnot->repositoryClass) && !$class->isEmbeddedDocument) {
128 66
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
129 66
        }
130 840
        if (isset($documentAnnot->indexes)) {
131 839
            foreach ($documentAnnot->indexes as $index) {
132
                $this->addIndex($class, $index);
133 839
            }
134 839
        }
135 840
        if (isset($documentAnnot->requireIndexes)) {
136 832
            $class->setRequireIndexes($documentAnnot->requireIndexes);
137 832
        }
138 840
        if (isset($documentAnnot->slaveOkay)) {
139 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
140 1
        }
141
142 840
        foreach ($reflClass->getProperties() as $property) {
143 839
            if (($class->isMappedSuperclass && ! $property->isPrivate())
144
                ||
145 839
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
146 321
                continue;
147
            }
148
149 838
            $indexes = array();
150 838
            $mapping = array('fieldName' => $property->getName());
151 838
            $fieldAnnot = null;
152
153 838
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
154 838
                if ($annot instanceof ODM\AbstractField) {
155 838
                    $fieldAnnot = $annot;
156 838
                }
157 838
                if ($annot instanceof ODM\AbstractIndex) {
158 185
                    $indexes[] = $annot;
159 185
                }
160 838
                if ($annot instanceof ODM\Indexes) {
161
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
162
                        $indexes[] = $index;
163
                    }
164 838
                } elseif ($annot instanceof ODM\AlsoLoad) {
165 13
                    $mapping['alsoLoadFields'] = (array) $annot->value;
166 838
                } elseif ($annot instanceof ODM\Version) {
167 97
                    $mapping['version'] = true;
168 838
                } elseif ($annot instanceof ODM\Lock) {
169 24
                    $mapping['lock'] = true;
170 24
                }
171 838
            }
172
173 838
            if ($fieldAnnot) {
174 838
                $mapping = array_replace($mapping, (array) $fieldAnnot);
175 838
                $class->mapField($mapping);
176 838
            }
177
178 838
            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...
179 185
                foreach ($indexes as $index) {
180 185
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
181 185
                    $keys = array($name => $index->order ?: 'asc');
182 185
                    $this->addIndex($class, $index, $keys);
183 186
                }
184 185
            }
185 840
        }
186
187
        // Set shard key after all fields to ensure we mapped all its keys
188 838
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
189 70
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
190 69
        }
191
192
        /** @var $method \ReflectionMethod */
193 837
        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 588
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
198 300
                continue;
199
            }
200
201 588
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
202 290
                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 290
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
207 72
                    continue;
208
                }
209
210 271
                if ($annot instanceof ODM\PrePersist) {
211 249
                    $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 271
                } 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 87
                } 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 86
                } elseif ($annot instanceof ODM\PostUpdate) {
217 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...
218 81
                } elseif ($annot instanceof ODM\PreRemove) {
219 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...
220 74
                } elseif ($annot instanceof ODM\PostRemove) {
221 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...
222 74
                } elseif ($annot instanceof ODM\PreLoad) {
223 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...
224 74
                } elseif ($annot instanceof ODM\PostLoad) {
225 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...
226 73
                } 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 588
            }
230 837
        }
231 837
    }
232
233 216
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
234
    {
235 216
        $keys = array_merge($keys, $index->keys);
236 216
        $options = array();
237 216
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
238 216
        foreach ($allowed as $name) {
239 216
            if (isset($index->$name)) {
240 216
                $options[$name] = $index->$name;
241 216
            }
242 216
        }
243 216
        if (! empty($index->partialFilterExpression)) {
244 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
245 2
        }
246 216
        $options = array_merge($options, $index->options);
247 216
        $class->addIndex($keys, $options);
248 216
    }
249
250
    /**
251
     * @param ClassMetadataInfo $class
252
     * @param ODM\ShardKey      $shardKey
253
     *
254
     * @throws MappingException
255
     */
256 70
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
257
    {
258 70
        $options = array();
259 70
        $allowed = array('unique', 'numInitialChunks');
260 70
        foreach ($allowed as $name) {
261 70
            if (isset($shardKey->$name)) {
262 1
                $options[$name] = $shardKey->$name;
263 1
            }
264 70
        }
265
266 70
        $class->setShardKey($shardKey->keys, $options);
267 69
    }
268
269
    /**
270
     * Factory method for the Annotation Driver
271
     *
272
     * @param array|string $paths
273
     * @param Reader $reader
274
     * @return AnnotationDriver
275
     */
276 1043
    public static function create($paths = array(), Reader $reader = null)
277
    {
278 1043
        if ($reader === null) {
279 1043
            $reader = new AnnotationReader();
280 1043
        }
281 1043
        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...
282
    }
283
}
284