Completed
Push — master ( 11f27c...4713a5 )
by Andreas
08:33
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 1383
    public function loadMetadataForClass($className, ClassMetadata $class)
51
    {
52
        /** @var $class ClassMetadataInfo */
53 1383
        $reflClass = $class->getReflectionClass();
54
55 1383
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
56
57 1383
        $documentAnnots = array();
58 1383
        foreach ($classAnnotations as $annot) {
59 1381
            $classAnnotations[get_class($annot)] = $annot;
60
61 1381
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
62 1381
                if ($annot instanceof $annotClass) {
63 1381
                    $documentAnnots[$i] = $annot;
64 1381
                    continue 2;
65
                }
66
            }
67
68
            // non-document class annotations
69 919
            if ($annot instanceof ODM\AbstractIndex) {
70 6
                $this->addIndex($class, $annot);
71
            }
72 919
            if ($annot instanceof ODM\Indexes) {
73 62
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
74 62
                    $this->addIndex($class, $index);
75
                }
76 903
            } elseif ($annot instanceof ODM\InheritanceType) {
77 840
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
78 901
            } elseif ($annot instanceof ODM\DiscriminatorField) {
79 111
                $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 900
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
81 111
                $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 839
            } 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 839
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
85 48
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
86 837
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
87 48
                $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 832
            } elseif ($annot instanceof ODM\ReadPreference) {
89 919
                $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 1383
        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 1381
        ksort($documentAnnots);
100 1381
        $documentAnnot = reset($documentAnnots);
101
102 1381
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
103 829
            $class->isMappedSuperclass = true;
104 1380
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
105 234
            $class->isEmbeddedDocument = true;
106 1373
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
107 47
            $class->isQueryResultDocument = true;
108
        }
109 1381
        if (isset($documentAnnot->db)) {
110 1
            $class->setDatabase($documentAnnot->db);
111
        }
112 1381
        if (isset($documentAnnot->collection)) {
113 876
            $class->setCollection($documentAnnot->collection);
114
        }
115 1381
        if (isset($documentAnnot->repositoryClass)) {
116 59
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
117
        }
118 1381
        if (isset($documentAnnot->writeConcern)) {
119 10
            $class->setWriteConcern($documentAnnot->writeConcern);
120
        }
121 1381
        if (isset($documentAnnot->indexes)) {
122 1380
            foreach ($documentAnnot->indexes as $index) {
123
                $this->addIndex($class, $index);
124
            }
125
        }
126 1381
        if (isset($documentAnnot->slaveOkay)) {
127
            $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 1381
        if (! empty($documentAnnot->readOnly)) {
130 5
            $class->markReadOnly();
131
        }
132
133 1381
        foreach ($reflClass->getProperties() as $property) {
134 1380
            if (($class->isMappedSuperclass && ! $property->isPrivate())
135
                ||
136 1380
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
137 872
                continue;
138
            }
139
140 1379
            $indexes = array();
141 1379
            $mapping = array('fieldName' => $property->getName());
142 1379
            $fieldAnnot = null;
143
144 1379
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
145 1379
                if ($annot instanceof ODM\AbstractField) {
146 1379
                    $fieldAnnot = $annot;
147 1379
                    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 1379
                if ($annot instanceof ODM\AbstractIndex) {
152 159
                    $indexes[] = $annot;
153
                }
154 1379
                if ($annot instanceof ODM\Indexes) {
155
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
156
                        $indexes[] = $index;
157
                    }
158 1379
                } elseif ($annot instanceof ODM\AlsoLoad) {
159 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
160 1379
                } elseif ($annot instanceof ODM\Version) {
161 67
                    $mapping['version'] = true;
162 1379
                } elseif ($annot instanceof ODM\Lock) {
163 1379
                    $mapping['lock'] = true;
164
                }
165
            }
166
167 1379
            if ($fieldAnnot) {
168 1379
                $mapping = array_replace($mapping, (array) $fieldAnnot);
169 1379
                $class->mapField($mapping);
170
            }
171
172 1379
            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 159
                foreach ($indexes as $index) {
174 159
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
175 159
                    $keys = array($name => $index->order ?: 'asc');
176 1379
                    $this->addIndex($class, $index, $keys);
177
                }
178
            }
179
        }
180
181
        // Set shard key after all fields to ensure we mapped all its keys
182 1379
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
183 59
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
184
        }
185
186
        /** @var $method \ReflectionMethod */
187 1378
        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 1144
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
192 839
                continue;
193
            }
194
195 1144
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
196 829
                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 829
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
201 64
                    continue;
202
                }
203
204 810
                if ($annot instanceof ODM\PrePersist) {
205 793
                    $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 73
                } elseif ($annot instanceof ODM\PostPersist) {
207 10
                    $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 73
                } 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 68
                } elseif ($annot instanceof ODM\PostUpdate) {
211 55
                    $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 66
                } elseif ($annot instanceof ODM\PreRemove) {
213 64
                    $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 66
                } elseif ($annot instanceof ODM\PostRemove) {
215 62
                    $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 66
                } elseif ($annot instanceof ODM\PreLoad) {
217 63
                    $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 65
                } elseif ($annot instanceof ODM\PostLoad) {
219 64
                    $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 1144
                    $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 1378
    }
226
227 181
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
228
    {
229 181
        $keys = array_merge($keys, $index->keys);
230 181
        $options = array();
231 181
        $allowed = array('name', 'dropDups', 'background', 'unique', 'sparse', 'expireAfterSeconds');
232 181
        foreach ($allowed as $name) {
233 181
            if (isset($index->$name)) {
234 181
                $options[$name] = $index->$name;
235
            }
236
        }
237 181
        if (! empty($index->partialFilterExpression)) {
238 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
239
        }
240 181
        $options = array_merge($options, $index->options);
241 181
        $class->addIndex($keys, $options);
242 181
    }
243
244
    /**
245
     * @param ClassMetadataInfo $class
246
     * @param ODM\ShardKey      $shardKey
247
     *
248
     * @throws MappingException
249
     */
250 59
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
251
    {
252 59
        $options = array();
253 59
        $allowed = array('unique', 'numInitialChunks');
254 59
        foreach ($allowed as $name) {
255 59
            if (isset($shardKey->$name)) {
256 59
                $options[$name] = $shardKey->$name;
257
            }
258
        }
259
260 59
        $class->setShardKey($shardKey->keys, $options);
261 58
    }
262
263
    /**
264
     * Factory method for the Annotation Driver
265
     *
266
     * @param array|string $paths
267
     * @param Reader $reader
268
     * @return AnnotationDriver
269
     */
270 1676
    public static function create($paths = array(), Reader $reader = null)
271
    {
272 1676
        if ($reader === null) {
273 1676
            $reader = new AnnotationReader();
274
        }
275 1676
        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