Completed
Pull Request — master (#1608)
by Maciej
15:44 queued 02:26
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
     * Registers annotation classes to the common registry.
49
     *
50
     * This method should be called when bootstrapping your application.
51
     *
52
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0. Register class loader through AnnotationRegistry::registerLoader instead.
53
     */
54
    public static function registerAnnotationClasses()
55
    {
56
        @trigger_error(
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...
57
            sprintf('%s is deprecated - register class loader through AnnotationRegistry::registerLoader instead.', __METHOD__),
58
            E_USER_DEPRECATED
59
        );
60
        AnnotationRegistry::registerFile(__DIR__ . '/../Annotations/DoctrineAnnotations.php');
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 903
    public function loadMetadataForClass($className, ClassMetadata $class)
67
    {
68
        /** @var $class ClassMetadataInfo */
69 903
        $reflClass = $class->getReflectionClass();
70
71 903
        $classAnnotations = $this->reader->getClassAnnotations($reflClass);
72
73 903
        $documentAnnots = array();
74 903
        foreach ($classAnnotations as $annot) {
75 901
            $classAnnotations[get_class($annot)] = $annot;
76
77 901
            foreach ($this->entityAnnotationClasses as $annotClass => $i) {
78 901
                if ($annot instanceof $annotClass) {
79 901
                    $documentAnnots[$i] = $annot;
80 901
                    continue 2;
81
                }
82
            }
83
84
            // non-document class annotations
85 396
            if ($annot instanceof ODM\AbstractIndex) {
86 13
                $this->addIndex($class, $annot);
87
            }
88 396
            if ($annot instanceof ODM\Indexes) {
89 65
                foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
90 65
                    $this->addIndex($class, $index);
91
                }
92 385
            } elseif ($annot instanceof ODM\InheritanceType) {
93 313
                $class->setInheritanceType(constant(MappingClassMetadata::class . '::INHERITANCE_TYPE_'.$annot->value));
94 383
            } elseif ($annot instanceof ODM\DiscriminatorField) {
95
                // $fieldName property is deprecated, but fall back for BC
96 121
                if (isset($annot->value)) {
97 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...
98
                } elseif (isset($annot->name)) {
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...scriminatorField::$name has been deprecated with message: property was deprecated in 1.2 and will be removed in 2.0

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...
99
                    $class->setDiscriminatorField($annot->name);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...scriminatorField::$name has been deprecated with message: property was deprecated in 1.2 and will be removed in 2.0

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...
100
                } elseif (isset($annot->fieldName)) {
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...inatorField::$fieldName has been deprecated with message: property was deprecated in 1.0.0-BETA10 and will be removed in 2.0

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...
101 121
                    $class->setDiscriminatorField($annot->fieldName);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...inatorField::$fieldName has been deprecated with message: property was deprecated in 1.0.0-BETA10 and will be removed in 2.0

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...
102
                }
103 382
            } elseif ($annot instanceof ODM\DiscriminatorMap) {
104 121
                $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...
105 320
            } elseif ($annot instanceof ODM\DiscriminatorValue) {
106
                $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...
107 320
            } elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
108 57
                $class->setChangeTrackingPolicy(constant(MappingClassMetadata::class . '::CHANGETRACKING_'.$annot->value));
109 317
            } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
110 396
                $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...
111
            }
112
113
        }
114
115 903
        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...
116 3
            throw MappingException::classIsNotAValidDocument($className);
117
        }
118
119
        // find the winning document annotation
120 901
        ksort($documentAnnots);
121 901
        $documentAnnot = reset($documentAnnots);
122
123 901
        if ($documentAnnot instanceof ODM\MappedSuperclass) {
124 302
            $class->isMappedSuperclass = true;
125 900
        } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
126 298
            $class->isEmbeddedDocument = true;
127 893
        } elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
128 52
            $class->isQueryResultDocument = true;
129
        }
130 901
        if (isset($documentAnnot->db)) {
131 1
            $class->setDatabase($documentAnnot->db);
132
        }
133 901
        if (isset($documentAnnot->collection)) {
134 351
            $class->setCollection($documentAnnot->collection);
135
        }
136 901
        if (isset($documentAnnot->repositoryClass)) {
137 69
            $class->setCustomRepositoryClass($documentAnnot->repositoryClass);
138
        }
139 901
        if (isset($documentAnnot->writeConcern)) {
140 11
            $class->setWriteConcern($documentAnnot->writeConcern);
141
        }
142 901
        if (isset($documentAnnot->indexes)) {
143 900
            foreach ($documentAnnot->indexes as $index) {
144
                $this->addIndex($class, $index);
145
            }
146
        }
147 901
        if (isset($documentAnnot->requireIndexes)) {
148 893
            $class->setRequireIndexes($documentAnnot->requireIndexes);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ODM\MongoDB\Map...fo::setRequireIndexes() has been deprecated with message: method was deprecated in 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...
149
        }
150 901
        if (isset($documentAnnot->slaveOkay)) {
151 1
            $class->setSlaveOkay($documentAnnot->slaveOkay);
152
        }
153
154 901
        foreach ($reflClass->getProperties() as $property) {
155 900
            if (($class->isMappedSuperclass && ! $property->isPrivate())
156
                ||
157 900
                ($class->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $class->name)) {
158 347
                continue;
159
            }
160
161 899
            $indexes = array();
162 899
            $mapping = array('fieldName' => $property->getName());
163 899
            $fieldAnnot = null;
164
165 899
            foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
166 899
                if ($annot instanceof ODM\AbstractField) {
167 899
                    $fieldAnnot = $annot;
168
                }
169 899
                if ($annot instanceof ODM\AbstractIndex) {
170 198
                    $indexes[] = $annot;
171
                }
172 899
                if ($annot instanceof ODM\Indexes) {
173
                    foreach (is_array($annot->value) ? $annot->value : array($annot->value) as $index) {
174
                        $indexes[] = $index;
175
                    }
176 899
                } elseif ($annot instanceof ODM\AlsoLoad) {
177 15
                    $mapping['alsoLoadFields'] = (array) $annot->value;
178 899
                } elseif ($annot instanceof ODM\Version) {
179 97
                    $mapping['version'] = true;
180 899
                } elseif ($annot instanceof ODM\Lock) {
181 899
                    $mapping['lock'] = true;
182
                }
183
            }
184
185 899
            if ($fieldAnnot) {
186 899
                $mapping = array_replace($mapping, (array) $fieldAnnot);
187 899
                $class->mapField($mapping);
188
            }
189
190 899
            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...
191 198
                foreach ($indexes as $index) {
192 198
                    $name = isset($mapping['name']) ? $mapping['name'] : $mapping['fieldName'];
193 198
                    $keys = array($name => $index->order ?: 'asc');
194 899
                    $this->addIndex($class, $index, $keys);
195
                }
196
            }
197
        }
198
199
        // Set shard key after all fields to ensure we mapped all its keys
200 899
        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
201 62
            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
202
        }
203
204
        /** @var $method \ReflectionMethod */
205 898
        foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
206
            /* Filter for the declaring class only. Callbacks from parent
207
             * classes will already be registered.
208
             */
209 634
            if ($method->getDeclaringClass()->name !== $reflClass->name) {
210 314
                continue;
211
            }
212
213 634
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
214 307
                if ($annot instanceof ODM\AlsoLoad) {
215 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...
216
                }
217
218 307
                if ( ! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
219 72
                    continue;
220
                }
221
222 288
                if ($annot instanceof ODM\PrePersist) {
223 267
                    $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...
224 86
                } elseif ($annot instanceof ODM\PostPersist) {
225 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...
226 85
                } elseif ($annot instanceof ODM\PreUpdate) {
227 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...
228 80
                } elseif ($annot instanceof ODM\PostUpdate) {
229 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...
230 74
                } elseif ($annot instanceof ODM\PreRemove) {
231 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...
232 74
                } elseif ($annot instanceof ODM\PostRemove) {
233 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...
234 74
                } elseif ($annot instanceof ODM\PreLoad) {
235 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...
236 73
                } elseif ($annot instanceof ODM\PostLoad) {
237 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...
238 11
                } elseif ($annot instanceof ODM\PreFlush) {
239 634
                    $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...
240
                }
241
            }
242
        }
243 898
    }
244
245 222
    private function addIndex(ClassMetadataInfo $class, $index, array $keys = array())
246
    {
247 222
        $keys = array_merge($keys, $index->keys);
248 222
        $options = array();
249 222
        $allowed = array('name', 'dropDups', 'background', 'safe', 'unique', 'sparse', 'expireAfterSeconds');
250 222
        foreach ($allowed as $name) {
251 222
            if (isset($index->$name)) {
252 222
                $options[$name] = $index->$name;
253
            }
254
        }
255 222
        if (! empty($index->partialFilterExpression)) {
256 3
            $options['partialFilterExpression'] = $index->partialFilterExpression;
257
        }
258 222
        $options = array_merge($options, $index->options);
259 222
        $class->addIndex($keys, $options);
260 222
    }
261
262
    /**
263
     * @param ClassMetadataInfo $class
264
     * @param ODM\ShardKey      $shardKey
265
     *
266
     * @throws MappingException
267
     */
268 62
    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
269
    {
270 62
        $options = array();
271 62
        $allowed = array('unique', 'numInitialChunks');
272 62
        foreach ($allowed as $name) {
273 62
            if (isset($shardKey->$name)) {
274 62
                $options[$name] = $shardKey->$name;
275
            }
276
        }
277
278 62
        $class->setShardKey($shardKey->keys, $options);
279 61
    }
280
281
    /**
282
     * Factory method for the Annotation Driver
283
     *
284
     * @param array|string $paths
285
     * @param Reader $reader
286
     * @return AnnotationDriver
287
     */
288 1138
    public static function create($paths = array(), Reader $reader = null)
289
    {
290 1138
        if ($reader === null) {
291 1138
            $reader = new AnnotationReader();
292
        }
293 1138
        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...
294
    }
295
}
296