Completed
Pull Request — master (#1263)
by Andreas
14:36
created

AnnotationDriver::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
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\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 830
    public function loadMetadataForClass($className, ClassMetadata $class)
61
    {
62
        /** @var $class ClassMetadataInfo */
63 830
        $reflClass = $class->getReflectionClass();
64
65 830
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
66
67 830
        $documentAnnots = array();
68 830
        foreach ($classAnnotations as $annot) {
69 828
            $classAnnotations[get_class($annot)] = $annot;
70
71 828
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
72 828
                if ($annot instanceof $annotClass) {
73 828
                    $documentAnnots[$i] = $annot;
74 828
                    continue 2;
75
                }
76 510
            }
77
78
            // non-document class annotations
79 358
            if ($annot instanceof ODM\AbstractIndex) {
80 11
                $this->addIndex($class, $annot);
81 11
            }
82 358
            if ($annot instanceof ODM\Indexes) {
83 65
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
84 65
                    $this->addIndex($class, $index);
85 65
                }
86 358
            } elseif ($annot instanceof ODM\InheritanceType) {
87 277
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
88 340
            } elseif ($annot instanceof ODM\DiscriminatorField) {
89
                // $fieldName property is deprecated, but fall back for BC
90 101
                if (isset($annot->value)) {
91 48
                    $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 101
                } elseif (isset($annot->name)) {
93
                    $class->setDiscriminatorField($annot->name);
94 98
                } 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 98
                    $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 98
                }
97 338
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
98 101
                $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 337
            } 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 286
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
102 51
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
103 286
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
104 50
                $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 50
            }
106
107 830
        }
108
109 830
        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 2
            throw MappingException::classIsNotAValidDocument($className);
111
        }
112
113
        // find the winning document annotation
114 828
        ksort($documentAnnots);
115 828
        $documentAnnot = reset($documentAnnots);
116
117 828
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
118 277
            $class->isMappedSuperclass = true;
119 828
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
120 380
            $class->isEmbeddedDocument = true;
121 827
        } elseif ($documentAnnot instanceof ODM\AggregationResultDocument) {
122 45
            $class->isAggregationResultDocument = true;
123 45
        }
124 828
        if (isset($documentAnnot->db)) {
125 1
            $class->setDatabase($documentAnnot->db);
126 1
        }
127 828
        if (isset($documentAnnot->collection)) {
128 320
            $class->setCollection($documentAnnot->collection);
129 320
        }
130 828
        if (isset($documentAnnot->repositoryClass) && !$class->isEmbeddedDocument && !$class->isAggregationResultDocument) {
131 60
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
132 60
        }
133 828
        if (isset($documentAnnot->indexes)) {
134 827
            foreach ($documentAnnot->indexes as $index) {
135
                $this->addIndex($class, $index);
136 827
            }
137 827
        }
138 828
        if (isset($documentAnnot->requireIndexes)) {
139 820
            $class->setRequireIndexes($documentAnnot->requireIndexes);
140 820
        }
141 828
        if (isset($documentAnnot->slaveOkay)) {
142
            $class->setSlaveOkay($documentAnnot->slaveOkay);
143
        }
144
145 828
        foreach ($reflClass->getProperties() as $property) {
146 827
            if (($class->isMappedSuperclass && ! $property->isPrivate())
147
                ||
148 827
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
149 310
                continue;
150
            }
151
152 826
            $indexes = array();
153 826
            $mapping = array('fieldName' => $property->getName());
154 826
            $fieldAnnot = null;
155
156 826
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
157 826
                if ($annot instanceof ODM\AbstractField) {
158 826
                    $fieldAnnot = $annot;
159 826
                }
160 826
                if ($annot instanceof ODM\AbstractIndex) {
161 92
                    $indexes[] = $annot;
162 92
                }
163 826
                if ($annot instanceof ODM\Indexes) {
164
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
165
                        $indexes[] = $index;
166
                    }
167 826
                } elseif ($annot instanceof ODM\AlsoLoad) {
168 13
                    $mapping['alsoLoadFields'] = (array) $annot->value;
169 826
                } elseif ($annot instanceof ODM\Version) {
170 91
                    $mapping['version'] = true;
171 826
                } elseif ($annot instanceof ODM\Lock) {
172 24
                    $mapping['lock'] = true;
173 24
                }
174 826
            }
175
176 826
            if ($fieldAnnot) {
177 826
                $mapping = array_replace($mapping, (array) $fieldAnnot);
178 826
                $class->mapField($mapping);
179 826
            }
180
181 826
            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 92
                foreach ($indexes as $index) {
183 92
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
184 92
                    $keys = array($name => $index->order ?: 'asc');
185 92
                    $this->addIndex($class, $index, $keys);
186 92
                }
187 92
            }
188 828
        }
189
190
        /** @var $method \ReflectionMethod */
191 826
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
192
            /* Filter for the declaring class only. Callbacks from parent
193
             * classes will already be registered.
194
             */
195 589
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
196 296
                continue;
197
            }
198
199 589
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
200 286
                if ($annot instanceof ODM\AlsoLoad) {
201 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...
202 13
                }
203
204 286
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
205 66
                    continue;
206
                }
207
208 267
                if ($annot instanceof ODM\PrePersist) {
209 245
                    $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...
210 267
                } elseif ($annot instanceof ODM\PostPersist) {
211 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...
212 81
                } elseif ($annot instanceof ODM\PreUpdate) {
213 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...
214 80
                } elseif ($annot instanceof ODM\PostUpdate) {
215 62
                    $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...
216 75
                } elseif ($annot instanceof ODM\PreRemove) {
217 66
                    $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...
218 68
                } elseif ($annot instanceof ODM\PostRemove) {
219 64
                    $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...
220 68
                } elseif ($annot instanceof ODM\PreLoad) {
221 65
                    $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...
222 68
                } elseif ($annot instanceof ODM\PostLoad) {
223 66
                    $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...
224 67
                } elseif ($annot instanceof ODM\PreFlush) {
225 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...
226 11
                }
227 589
            }
228 826
        }
229 826
    }
230
231 120
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
232
    {
233 120
        $keys = array_merge($keys, $index->keys);
234 120
        $options = array();
235 120
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
236 120
        foreach ($allowed as $name) {
237 120
            if (isset($index->$name)) {
238 120
                $options[$name] = $index->$name;
239 120
            }
240 120
        }
241 120
        if (! empty($index->partialFilterExpression)) {
242 2
            $options['partialFilterExpression'] = $index->partialFilterExpression;
243 2
        }
244 120
        $options = array_merge($options, $index->options);
245 120
        $class->addIndex($keys, $options);
246 120
    }
247
248
    /**
249
     * Factory method for the Annotation Driver
250
     *
251
     * @param array|string $paths
252
     * @param Reader $reader
253
     * @return AnnotationDriver
254
     */
255 962
    public static function create($paths = array(), Reader $reader = null)
256
    {
257 962
        if ($reader === null) {
258 962
            $reader = new AnnotationReader();
259 962
        }
260 962
        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...
261
    }
262
}
263