Completed
Pull Request — master (#1668)
by Andreas
04:58
created

AnnotationDriver::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2
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
     * {@inheritdoc}
49
     */
50 922
    public function loadMetadataForClass($className, ClassMetadata $class)
51
    {
52
        /** @var $class ClassMetadataInfo */
53 922
        $reflClass = $class->getReflectionClass();
54
55 922
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
56
57 922
        $documentAnnots = array();
58 922
        foreach ($classAnnotations as $annot) {
59 920
            $classAnnotations[get_class($annot)] = $annot;
60
61 920
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
62 920
                if ($annot instanceof $annotClass) {
63 920
                    $documentAnnots[$i] = $annot;
64 920
                    continue 2;
65
                }
66
            }
67
68
            // non-document class annotations
69 427
            if ($annot instanceof ODM\AbstractIndex) {
70 12
                $this->addIndex($class, $annot);
71
            }
72 427
            if ($annot instanceof ODM\Indexes) {
73 71
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
74 71
                    $this->addIndex($class, $index);
75
                }
76 411
            } elseif ($annot instanceof ODM\InheritanceType) {
77 327
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
78 409
            } elseif ($annot instanceof ODM\DiscriminatorField) {
79 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...
80 408
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
81 121
                $class->setDiscriminatorMap($annot->value);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type 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...
82 346
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
83
                $class->setDiscriminatorValue($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...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...
84 346
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
85 57
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
86 343
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
87 57
                $class->setDefaultDiscriminatorValue($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...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...
88 338
            } elseif ($annot instanceof ODM\ReadPreference) {
89 427
                $class->setReadPreference($annot->value, $annot->tags);
0 ignored issues
show
Bug introduced by
It seems like $annot->value can also be of type array; however, Doctrine\ODM\MongoDB\Map...fo::setReadPreference() 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...
90
            }
91
92
        }
93
94 922
        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...
95 3
            throw MappingException::classIsNotAValidDocument($className);
96
        }
97
98
        // find the winning document annotation
99 920
        ksort($documentAnnots);
100 920
        $documentAnnot = reset($documentAnnots);
101
102 920
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
103 317
            $class->isMappedSuperclass = true;
104 919
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
105 275
            $class->isEmbeddedDocument = true;
106 912
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
107 52
            $class->isQueryResultDocument = true;
108
        }
109 920
        if (isset($documentAnnot->db)) {
110 1
            $class->setDatabase($documentAnnot->db);
111
        }
112 920
        if (isset($documentAnnot->collection)) {
113 375
            $class->setCollection($documentAnnot->collection);
114
        }
115 920
        if (isset($documentAnnot->repositoryClass)) {
116 69
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
117
        }
118 920
        if (isset($documentAnnot->writeConcern)) {
119 11
            $class->setWriteConcern($documentAnnot->writeConcern);
120
        }
121 920
        if (isset($documentAnnot->indexes)) {
122 919
            foreach ($documentAnnot->indexes as $index) {
123
                $this->addIndex($class, $index);
124
            }
125
        }
126 920
        if (isset($documentAnnot->slaveOkay)) {
127 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ODM\MongoDB\Map...ataInfo::setSlaveOkay() has been deprecated with message: in version 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...
128
        }
129 920
        if (! empty($documentAnnot->readOnly)) {
130 6
            $class->markReadOnly();
131
        }
132
133 920
        foreach ($reflClass->getProperties() as $property) {
134 919
            if (($class->isMappedSuperclass && ! $property->isPrivate())
135
                ||
136 919
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
137 361
                continue;
138
            }
139
140 918
            $indexes = array();
141 918
            $mapping = array('fieldName' => $property->getName());
142 918
            $fieldAnnot = null;
143
144 918
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
145 918
                if ($annot instanceof ODM\AbstractField) {
146 918
                    $fieldAnnot = $annot;
147 918
                    if ($annot->isDeprecated()) {
148
                        @trigger_error($annot->getDeprecationMessage(), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
149
                    }
150
                }
151 918
                if ($annot instanceof ODM\AbstractIndex) {
152 181
                    $indexes[] = $annot;
153
                }
154 918
                if ($annot instanceof ODM\Indexes) {
155
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
156
                        $indexes[] = $index;
157
                    }
158 918
                } elseif ($annot instanceof ODM\AlsoLoad) {
159 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
160 918
                } elseif ($annot instanceof ODM\Version) {
161 99
                    $mapping['version'] = true;
162 918
                } elseif ($annot instanceof ODM\Lock) {
163 918
                    $mapping['lock'] = true;
164
                }
165
            }
166
167 918
            if ($fieldAnnot) {
168 918
                $mapping = array_replace($mapping, (array) $fieldAnnot);
169 918
                $class->mapField($mapping);
170
            }
171
172 918
            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...
173 181
                foreach ($indexes as $index) {
174 181
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
175 181
                    $keys = array($name => $index->order ?: 'asc');
176 918
                    $this->addIndex($class, $index, $keys);
177
                }
178
            }
179
        }
180
181
        // Set shard key after all fields to ensure we mapped all its keys
182 918
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
183 74
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
184
        }
185
186
        /** @var $method \ReflectionMethod */
187 917
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
188
            /* Filter for the declaring class only. Callbacks from parent
189
             * classes will already be registered.
190
             */
191 660
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
192 328
                continue;
193
            }
194
195 660
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
196 321
                if ($annot instanceof ODM\AlsoLoad) {
197 13
                    $class->registerAlsoLoadMethod($method->getName(), $annot->value);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
198
                }
199
200 321
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
201 72
                    continue;
202
                }
203
204 302
                if ($annot instanceof ODM\PrePersist) {
205 281
                    $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...
206 86
                } elseif ($annot instanceof ODM\PostPersist) {
207 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...
208 85
                } elseif ($annot instanceof ODM\PreUpdate) {
209 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...
210 80
                } elseif ($annot instanceof ODM\PostUpdate) {
211 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...
212 74
                } elseif ($annot instanceof ODM\PreRemove) {
213 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...
214 74
                } elseif ($annot instanceof ODM\PostRemove) {
215 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...
216 74
                } elseif ($annot instanceof ODM\PreLoad) {
217 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...
218 73
                } elseif ($annot instanceof ODM\PostLoad) {
219 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...
220 11
                } elseif ($annot instanceof ODM\PreFlush) {
221 660
                    $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...
222
                }
223
            }
224
        }
225 917
    }
226
227 209
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
228
    {
229 209
        $keys = array_merge($keys, $index->keys);
230 209
        $options = array();
231 209
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
232 209
        foreach ($allowed as $name) {
233 209
            if (isset($index->$name)) {
234 209
                $options[$name] = $index->$name;
235
            }
236
        }
237 209
        if (! empty($index->partialFilterExpression)) {
238 3
            $options['partialFilterExpression'] = $index->partialFilterExpression;
239
        }
240 209
        $options = array_merge($options, $index->options);
241 209
        $class->addIndex($keys, $options);
242 209
    }
243
244
    /**
245
     * @param ClassMetadataInfo $class
246
     * @param ODM\ShardKey      $shardKey
247
     *
248
     * @throws MappingException
249
     */
250 74
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
251
    {
252 74
        $options = array();
253 74
        $allowed = array('unique', 'numInitialChunks');
254 74
        foreach ($allowed as $name) {
255 74
            if (isset($shardKey->$name)) {
256 74
                $options[$name] = $shardKey->$name;
257
            }
258
        }
259
260 74
        $class->setShardKey($shardKey->keys, $options);
261 73
    }
262
263
    /**
264
     * Factory method for the Annotation Driver
265
     *
266
     * @param array|string $paths
267
     * @param Reader $reader
268
     * @return AnnotationDriver
269
     */
270 1154
    public static function create($paths = array(), Reader $reader = null)
271
    {
272 1154
        if ($reader === null) {
273 1154
            $reader = new AnnotationReader();
274
        }
275 1154
        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...
276
    }
277
}
278