diff --git a/UPGRADE-2.7.md b/UPGRADE-2.7.md index 910c2dea4..32cc61a7e 100644 --- a/UPGRADE-2.7.md +++ b/UPGRADE-2.7.md @@ -1,5 +1,10 @@ # UPGRADE FROM 2.6 to 2.7 +## Backward compatibility breaks + +* `Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver` no longer extends + `Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver`. + ## doctrine/persistence * MongoDB ODM 2.7 requires `doctrine/persistence` 3.2 or newer. diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php index 9feef86bb..d23e601d9 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php @@ -6,37 +6,12 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\Reader; -use Doctrine\ODM\MongoDB\Events; -use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; -use Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractIndex; -use Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Mapping\MappingException; -use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; -use Doctrine\Persistence\Mapping\Driver\MappingDriver; -use MongoDB\Driver\Exception\UnexpectedValueException; -use ReflectionClass; -use ReflectionMethod; - -use function array_merge; -use function array_replace; -use function assert; -use function class_exists; -use function constant; -use function count; -use function interface_exists; -use function is_array; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; -use function trigger_deprecation; /** * The AnnotationDriver reads the mapping metadata from docblock annotations. */ -class AnnotationDriver implements MappingDriver +class AnnotationDriver extends AttributeDriver { - use ColocatedMappingDriver; - /** * The annotation reader. * @@ -60,323 +35,6 @@ public function __construct($reader, $paths = null) $this->addPaths((array) $paths); } - public function isTransient($className) - { - $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className)); - - foreach ($classAnnotations as $annot) { - if ($annot instanceof ODM\AbstractDocument) { - return false; - } - } - - return true; - } - - public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $metadata): void - { - assert($metadata instanceof ClassMetadata); - $reflClass = $metadata->getReflectionClass(); - - $classAnnotations = $this->reader->getClassAnnotations($reflClass); - - $documentAnnot = null; - foreach ($classAnnotations as $annot) { - $classAnnotations[$annot::class] = $annot; - - if ($annot instanceof ODM\AbstractDocument) { - if ($documentAnnot !== null) { - throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot); - } - - $documentAnnot = $annot; - } - - // non-document class annotations - if ($annot instanceof ODM\AbstractIndex) { - $this->addIndex($metadata, $annot); - } - - if ($annot instanceof ODM\Indexes) { - trigger_deprecation( - 'doctrine/mongodb-odm', - '2.2', - 'The "@Indexes" annotation used in class "%s" is deprecated. Specify all "@Index" and "@UniqueIndex" annotations on the class.', - $className, - ); - $value = $annot->value; - foreach (is_array($value) ? $value : [$value] as $index) { - $this->addIndex($metadata, $index); - } - } elseif ($annot instanceof ODM\InheritanceType) { - $metadata->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value)); - } elseif ($annot instanceof ODM\DiscriminatorField) { - $metadata->setDiscriminatorField($annot->value); - } elseif ($annot instanceof ODM\DiscriminatorMap) { - $value = $annot->value; - assert(is_array($value)); - $metadata->setDiscriminatorMap($value); - } elseif ($annot instanceof ODM\DiscriminatorValue) { - $metadata->setDiscriminatorValue($annot->value); - } elseif ($annot instanceof ODM\ChangeTrackingPolicy) { - $metadata->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value)); - } elseif ($annot instanceof ODM\DefaultDiscriminatorValue) { - $metadata->setDefaultDiscriminatorValue($annot->value); - } elseif ($annot instanceof ODM\ReadPreference) { - $metadata->setReadPreference($annot->value, $annot->tags ?? []); - } elseif ($annot instanceof ODM\Validation) { - if (isset($annot->validator)) { - try { - $validatorBson = fromJSON($annot->validator); - } catch (UnexpectedValueException $e) { - throw MappingException::schemaValidationError($e->getCode(), $e->getMessage(), $className, 'validator'); - } - - $validator = toPHP($validatorBson, []); - $metadata->setValidator($validator); - } - - if (isset($annot->action)) { - $metadata->setValidationAction($annot->action); - } - - if (isset($annot->level)) { - $metadata->setValidationLevel($annot->level); - } - } - } - - if ($documentAnnot === null) { - throw MappingException::classIsNotAValidDocument($className); - } - - if ($documentAnnot instanceof ODM\MappedSuperclass) { - $metadata->isMappedSuperclass = true; - } elseif ($documentAnnot instanceof ODM\EmbeddedDocument) { - $metadata->isEmbeddedDocument = true; - } elseif ($documentAnnot instanceof ODM\QueryResultDocument) { - $metadata->isQueryResultDocument = true; - } elseif ($documentAnnot instanceof ODM\View) { - if (! $documentAnnot->rootClass) { - throw MappingException::viewWithoutRootClass($className); - } - - if (! class_exists($documentAnnot->rootClass)) { - throw MappingException::viewRootClassNotFound($className, $documentAnnot->rootClass); - } - - $metadata->markViewOf($documentAnnot->rootClass); - } elseif ($documentAnnot instanceof ODM\File) { - $metadata->isFile = true; - - if ($documentAnnot->chunkSizeBytes !== null) { - $metadata->setChunkSizeBytes($documentAnnot->chunkSizeBytes); - } - } - - if (isset($documentAnnot->db)) { - $metadata->setDatabase($documentAnnot->db); - } - - if (isset($documentAnnot->collection)) { - $metadata->setCollection($documentAnnot->collection); - } - - if (isset($documentAnnot->view)) { - $metadata->setCollection($documentAnnot->view); - } - - // Store bucketName as collection name for GridFS files - if (isset($documentAnnot->bucketName)) { - $metadata->setBucketName($documentAnnot->bucketName); - } - - if (isset($documentAnnot->repositoryClass)) { - $metadata->setCustomRepositoryClass($documentAnnot->repositoryClass); - } - - if (isset($documentAnnot->writeConcern)) { - $metadata->setWriteConcern($documentAnnot->writeConcern); - } - - if (isset($documentAnnot->indexes) && count($documentAnnot->indexes)) { - trigger_deprecation( - 'doctrine/mongodb-odm', - '2.2', - 'The "indexes" parameter in the "%s" annotation for class "%s" is deprecated. Specify all "@Index" and "@UniqueIndex" annotations on the class.', - $className, - $documentAnnot::class, - ); - - foreach ($documentAnnot->indexes as $index) { - $this->addIndex($metadata, $index); - } - } - - if (! empty($documentAnnot->readOnly)) { - $metadata->markReadOnly(); - } - - foreach ($reflClass->getProperties() as $property) { - if ( - ($metadata->isMappedSuperclass && ! $property->isPrivate()) - || - ($metadata->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $metadata->name) - ) { - continue; - } - - $indexes = []; - $mapping = ['fieldName' => $property->getName()]; - $fieldAnnot = null; - - foreach ($this->reader->getPropertyAnnotations($property) as $annot) { - if ($annot instanceof ODM\AbstractField) { - $fieldAnnot = $annot; - } - - if ($annot instanceof ODM\AbstractIndex) { - $indexes[] = $annot; - } - - if ($annot instanceof ODM\Indexes) { - $value = $annot->value; - foreach (is_array($value) ? $value : [$value] as $index) { - $indexes[] = $index; - } - } elseif ($annot instanceof ODM\AlsoLoad) { - $mapping['alsoLoadFields'] = (array) $annot->value; - } elseif ($annot instanceof ODM\Version) { - $mapping['version'] = true; - } elseif ($annot instanceof ODM\Lock) { - $mapping['lock'] = true; - } - } - - if ($fieldAnnot) { - $mapping = array_replace($mapping, (array) $fieldAnnot); - $metadata->mapField($mapping); - } - - if (! $indexes) { - continue; - } - - foreach ($indexes as $index) { - $name = $mapping['name'] ?? $mapping['fieldName']; - $keys = [$name => $index->order ?: 'asc']; - $this->addIndex($metadata, $index, $keys); - } - } - - // Set shard key after all fields to ensure we mapped all its keys - if (isset($classAnnotations[ShardKey::class])) { - assert($classAnnotations[ShardKey::class] instanceof ShardKey); - $this->setShardKey($metadata, $classAnnotations[ShardKey::class]); - } - - foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - /* Filter for the declaring class only. Callbacks from parent - * classes will already be registered. - */ - if ($method->getDeclaringClass()->name !== $reflClass->name) { - continue; - } - - foreach ($this->reader->getMethodAnnotations($method) as $annot) { - if ($annot instanceof ODM\AlsoLoad) { - $metadata->registerAlsoLoadMethod($method->getName(), $annot->value); - } - - if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) { - continue; - } - - if ($annot instanceof ODM\PrePersist) { - $metadata->addLifecycleCallback($method->getName(), Events::prePersist); - } elseif ($annot instanceof ODM\PostPersist) { - $metadata->addLifecycleCallback($method->getName(), Events::postPersist); - } elseif ($annot instanceof ODM\PreUpdate) { - $metadata->addLifecycleCallback($method->getName(), Events::preUpdate); - } elseif ($annot instanceof ODM\PostUpdate) { - $metadata->addLifecycleCallback($method->getName(), Events::postUpdate); - } elseif ($annot instanceof ODM\PreRemove) { - $metadata->addLifecycleCallback($method->getName(), Events::preRemove); - } elseif ($annot instanceof ODM\PostRemove) { - $metadata->addLifecycleCallback($method->getName(), Events::postRemove); - } elseif ($annot instanceof ODM\PreLoad) { - $metadata->addLifecycleCallback($method->getName(), Events::preLoad); - } elseif ($annot instanceof ODM\PostLoad) { - $metadata->addLifecycleCallback($method->getName(), Events::postLoad); - } elseif ($annot instanceof ODM\PreFlush) { - $metadata->addLifecycleCallback($method->getName(), Events::preFlush); - } - } - } - } - - /** - * @param ClassMetadata $class - * @param array $keys - */ - private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []): void - { - $keys = array_merge($keys, $index->keys); - $options = []; - $allowed = ['name', 'background', 'unique', 'sparse', 'expireAfterSeconds']; - foreach ($allowed as $name) { - if (! isset($index->$name)) { - continue; - } - - $options[$name] = $index->$name; - } - - if (! empty($index->partialFilterExpression)) { - $options['partialFilterExpression'] = $index->partialFilterExpression; - } - - $options = array_merge($options, $index->options); - $class->addIndex($keys, $options); - } - - /** - * @param ClassMetadata $class - * - * @throws MappingException - */ - private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey): void - { - $options = []; - $allowed = ['unique', 'numInitialChunks']; - foreach ($allowed as $name) { - if (! isset($shardKey->$name)) { - continue; - } - - $options[$name] = $shardKey->$name; - } - - $class->setShardKey($shardKey->keys, $options); - } - - /** - * Retrieve the current annotation reader - * - * @return Reader - */ - public function getReader() - { - trigger_deprecation( - 'doctrine/mongodb-odm', - '2.4', - '%s is deprecated with no replacement', - __METHOD__, - ); - - return $this->reader; - } - /** * Factory method for the Annotation Driver * @@ -387,5 +45,3 @@ public static function create($paths = [], ?Reader $reader = null): AnnotationDr return new self($reader ?? new AnnotationReader(), $paths); } } - -interface_exists(\Doctrine\Persistence\Mapping\ClassMetadata::class); diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php index edf8d5c61..bf080f7cb 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php @@ -5,16 +5,373 @@ namespace Doctrine\ODM\MongoDB\Mapping\Driver; use Doctrine\Common\Annotations\Reader; +use Doctrine\ODM\MongoDB\Events; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractIndex; +use Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey; +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; +use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use MongoDB\Driver\Exception\UnexpectedValueException; +use ReflectionClass; +use ReflectionMethod; +use ReflectionProperty; + +use function array_merge; +use function array_replace; +use function assert; +use function class_exists; +use function constant; +use function count; +use function is_array; +use function MongoDB\BSON\fromJSON; +use function MongoDB\BSON\toPHP; +use function trigger_deprecation; /** - * The AnnotationDriver reads the mapping metadata from docblock annotations. + * The AtttributeDriver reads the mapping metadata from attributes. */ -class AttributeDriver extends AnnotationDriver +class AttributeDriver implements MappingDriver { + use ColocatedMappingDriver; + + /** + * @internal this property will be private in 3.0 + * + * @var Reader|AttributeReader + */ + protected $reader; + /** @param string|string[]|null $paths */ public function __construct($paths = null, ?Reader $reader = null) { - parent::__construct($reader ?? new AttributeReader(), $paths); + if ($reader !== null) { + trigger_deprecation( + 'doctrine/mongodb-odm', + '2.7', + 'Passing a $reader parameter to %s is deprecated', + __METHOD__, + ); + } + + $this->reader = $reader ?? new AttributeReader(); + + $this->addPaths((array) $paths); + } + + public function isTransient($className) + { + $classAttributes = $this->getClassAttributes(new ReflectionClass($className)); + + foreach ($classAttributes as $attribute) { + if ($attribute instanceof ODM\AbstractDocument) { + return false; + } + } + + return true; + } + + public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void + { + assert($metadata instanceof ClassMetadata); + $reflClass = $metadata->getReflectionClass(); + + $classAttributes = $this->getClassAttributes($reflClass); + + $documentAttribute = null; + foreach ($classAttributes as $attribute) { + $classAttributes[$attribute::class] = $attribute; + + if ($attribute instanceof ODM\AbstractDocument) { + if ($documentAttribute !== null) { + throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAttribute, $attribute); + } + + $documentAttribute = $attribute; + } + + // non-document class attributes + if ($attribute instanceof ODM\AbstractIndex) { + $this->addIndex($metadata, $attribute); + } + + if ($attribute instanceof ODM\Indexes) { + trigger_deprecation( + 'doctrine/mongodb-odm', + '2.2', + 'The "@Indexes" attribute used in class "%s" is deprecated. Specify all "@Index" and "@UniqueIndex" attributes on the class.', + $className, + ); + $value = $attribute->value; + foreach (is_array($value) ? $value : [$value] as $index) { + $this->addIndex($metadata, $index); + } + } elseif ($attribute instanceof ODM\InheritanceType) { + $metadata->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $attribute->value)); + } elseif ($attribute instanceof ODM\DiscriminatorField) { + $metadata->setDiscriminatorField($attribute->value); + } elseif ($attribute instanceof ODM\DiscriminatorMap) { + $value = $attribute->value; + assert(is_array($value)); + $metadata->setDiscriminatorMap($value); + } elseif ($attribute instanceof ODM\DiscriminatorValue) { + $metadata->setDiscriminatorValue($attribute->value); + } elseif ($attribute instanceof ODM\ChangeTrackingPolicy) { + $metadata->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $attribute->value)); + } elseif ($attribute instanceof ODM\DefaultDiscriminatorValue) { + $metadata->setDefaultDiscriminatorValue($attribute->value); + } elseif ($attribute instanceof ODM\ReadPreference) { + $metadata->setReadPreference($attribute->value, $attribute->tags ?? []); + } elseif ($attribute instanceof ODM\Validation) { + if (isset($attribute->validator)) { + try { + $validatorBson = fromJSON($attribute->validator); + } catch (UnexpectedValueException $e) { + throw MappingException::schemaValidationError($e->getCode(), $e->getMessage(), $className, 'validator'); + } + + $validator = toPHP($validatorBson, []); + $metadata->setValidator($validator); + } + + if (isset($attribute->action)) { + $metadata->setValidationAction($attribute->action); + } + + if (isset($attribute->level)) { + $metadata->setValidationLevel($attribute->level); + } + } + } + + if ($documentAttribute === null) { + throw MappingException::classIsNotAValidDocument($className); + } + + if ($documentAttribute instanceof ODM\MappedSuperclass) { + $metadata->isMappedSuperclass = true; + } elseif ($documentAttribute instanceof ODM\EmbeddedDocument) { + $metadata->isEmbeddedDocument = true; + } elseif ($documentAttribute instanceof ODM\QueryResultDocument) { + $metadata->isQueryResultDocument = true; + } elseif ($documentAttribute instanceof ODM\View) { + if (! $documentAttribute->rootClass) { + throw MappingException::viewWithoutRootClass($className); + } + + if (! class_exists($documentAttribute->rootClass)) { + throw MappingException::viewRootClassNotFound($className, $documentAttribute->rootClass); + } + + $metadata->markViewOf($documentAttribute->rootClass); + } elseif ($documentAttribute instanceof ODM\File) { + $metadata->isFile = true; + + if ($documentAttribute->chunkSizeBytes !== null) { + $metadata->setChunkSizeBytes($documentAttribute->chunkSizeBytes); + } + } + + if (isset($documentAttribute->db)) { + $metadata->setDatabase($documentAttribute->db); + } + + if (isset($documentAttribute->collection)) { + $metadata->setCollection($documentAttribute->collection); + } + + if (isset($documentAttribute->view)) { + $metadata->setCollection($documentAttribute->view); + } + + // Store bucketName as collection name for GridFS files + if (isset($documentAttribute->bucketName)) { + $metadata->setBucketName($documentAttribute->bucketName); + } + + if (isset($documentAttribute->repositoryClass)) { + $metadata->setCustomRepositoryClass($documentAttribute->repositoryClass); + } + + if (isset($documentAttribute->writeConcern)) { + $metadata->setWriteConcern($documentAttribute->writeConcern); + } + + if (isset($documentAttribute->indexes) && count($documentAttribute->indexes)) { + trigger_deprecation( + 'doctrine/mongodb-odm', + '2.2', + 'The "indexes" parameter in the "%s" attribute for class "%s" is deprecated. Specify all "@Index" and "@UniqueIndex" attributes on the class.', + $className, + $documentAttribute::class, + ); + + foreach ($documentAttribute->indexes as $index) { + $this->addIndex($metadata, $index); + } + } + + if (! empty($documentAttribute->readOnly)) { + $metadata->markReadOnly(); + } + + foreach ($reflClass->getProperties() as $property) { + if ( + ($metadata->isMappedSuperclass && ! $property->isPrivate()) + || + ($metadata->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $metadata->name) + ) { + continue; + } + + $indexes = []; + $mapping = ['fieldName' => $property->getName()]; + $fieldAttribute = null; + + foreach ($this->getPropertyAttributes($property) as $propertyAttribute) { + if ($propertyAttribute instanceof ODM\AbstractField) { + $fieldAttribute = $propertyAttribute; + } + + if ($propertyAttribute instanceof ODM\AbstractIndex) { + $indexes[] = $propertyAttribute; + } + + if ($propertyAttribute instanceof ODM\Indexes) { + $value = $propertyAttribute->value; + foreach (is_array($value) ? $value : [$value] as $index) { + $indexes[] = $index; + } + } elseif ($propertyAttribute instanceof ODM\AlsoLoad) { + $mapping['alsoLoadFields'] = (array) $propertyAttribute->value; + } elseif ($propertyAttribute instanceof ODM\Version) { + $mapping['version'] = true; + } elseif ($propertyAttribute instanceof ODM\Lock) { + $mapping['lock'] = true; + } + } + + if ($fieldAttribute) { + $mapping = array_replace($mapping, (array) $fieldAttribute); + $metadata->mapField($mapping); + } + + if (! $indexes) { + continue; + } + + foreach ($indexes as $index) { + $name = $mapping['name'] ?? $mapping['fieldName']; + $keys = [$name => $index->order ?: 'asc']; + $this->addIndex($metadata, $index, $keys); + } + } + + // Set shard key after all fields to ensure we mapped all its keys + if (isset($classAttributes[ShardKey::class])) { + assert($classAttributes[ShardKey::class] instanceof ShardKey); + $this->setShardKey($metadata, $classAttributes[ShardKey::class]); + } + + foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + /* Filter for the declaring class only. Callbacks from parent + * classes will already be registered. + */ + if ($method->getDeclaringClass()->name !== $reflClass->name) { + continue; + } + + foreach ($this->getMethodAttributes($method) as $methodAttribute) { + if ($methodAttribute instanceof ODM\AlsoLoad) { + $metadata->registerAlsoLoadMethod($method->getName(), $methodAttribute->value); + } + + if (! isset($classAttributes[ODM\HasLifecycleCallbacks::class])) { + continue; + } + + if ($methodAttribute instanceof ODM\PrePersist) { + $metadata->addLifecycleCallback($method->getName(), Events::prePersist); + } elseif ($methodAttribute instanceof ODM\PostPersist) { + $metadata->addLifecycleCallback($method->getName(), Events::postPersist); + } elseif ($methodAttribute instanceof ODM\PreUpdate) { + $metadata->addLifecycleCallback($method->getName(), Events::preUpdate); + } elseif ($methodAttribute instanceof ODM\PostUpdate) { + $metadata->addLifecycleCallback($method->getName(), Events::postUpdate); + } elseif ($methodAttribute instanceof ODM\PreRemove) { + $metadata->addLifecycleCallback($method->getName(), Events::preRemove); + } elseif ($methodAttribute instanceof ODM\PostRemove) { + $metadata->addLifecycleCallback($method->getName(), Events::postRemove); + } elseif ($methodAttribute instanceof ODM\PreLoad) { + $metadata->addLifecycleCallback($method->getName(), Events::preLoad); + } elseif ($methodAttribute instanceof ODM\PostLoad) { + $metadata->addLifecycleCallback($method->getName(), Events::postLoad); + } elseif ($methodAttribute instanceof ODM\PreFlush) { + $metadata->addLifecycleCallback($method->getName(), Events::preFlush); + } + } + } + } + + /** + * @param ClassMetadata $class + * @param array $keys + */ + private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []): void + { + $keys = array_merge($keys, $index->keys); + $options = []; + $allowed = ['name', 'background', 'unique', 'sparse', 'expireAfterSeconds']; + foreach ($allowed as $name) { + if (! isset($index->$name)) { + continue; + } + + $options[$name] = $index->$name; + } + + if (! empty($index->partialFilterExpression)) { + $options['partialFilterExpression'] = $index->partialFilterExpression; + } + + $options = array_merge($options, $index->options); + $class->addIndex($keys, $options); + } + + /** + * @param ClassMetadata $class + * + * @throws MappingException + */ + private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey): void + { + $options = []; + $allowed = ['unique', 'numInitialChunks']; + foreach ($allowed as $name) { + if (! isset($shardKey->$name)) { + continue; + } + + $options[$name] = $shardKey->$name; + } + + $class->setShardKey($shardKey->keys, $options); + } + + /** @return Reader|AttributeReader */ + public function getReader() + { + trigger_deprecation( + 'doctrine/mongodb-odm', + '2.4', + '%s is deprecated with no replacement', + __METHOD__, + ); + + return $this->reader; } /** @@ -24,8 +381,38 @@ public function __construct($paths = null, ?Reader $reader = null) * * @return AttributeDriver */ - public static function create($paths = [], ?Reader $reader = null): AnnotationDriver + public static function create($paths = [], ?Reader $reader = null) { return new self($paths, $reader); } + + /** @return object[] */ + private function getClassAttributes(ReflectionClass $class): array + { + if ($this->reader instanceof AttributeReader) { + return $this->reader->getClassAttributes($class); + } + + return $this->reader->getClassAnnotations($class); + } + + /** @return object[] */ + private function getMethodAttributes(ReflectionMethod $method): array + { + if ($this->reader instanceof AttributeReader) { + return $this->reader->getMethodAttributes($method); + } + + return $this->reader->getMethodAnnotations($method); + } + + /** @return object[] */ + private function getPropertyAttributes(ReflectionProperty $property): array + { + if ($this->reader instanceof AttributeReader) { + return $this->reader->getPropertyAttributes($property); + } + + return $this->reader->getPropertyAnnotations($property); + } } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeReader.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeReader.php index 863c42487..3dcb5fe9e 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeReader.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeReader.php @@ -4,7 +4,6 @@ namespace Doctrine\ODM\MongoDB\Mapping\Driver; -use Doctrine\Common\Annotations\Reader; use Doctrine\ODM\MongoDB\Mapping\Annotations\Annotation; use ReflectionAttribute; use ReflectionClass; @@ -15,77 +14,26 @@ use function is_subclass_of; /** @internal */ -final class AttributeReader implements Reader +final class AttributeReader { - public function getClassAnnotations(ReflectionClass $class): array + /** @return array */ + public function getClassAttributes(ReflectionClass $class): array { return $this->convertToAttributeInstances($class->getAttributes()); } - /** - * @param class-string $annotationName - * - * @return T|null - * - * @template T - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - foreach ($this->getClassAnnotations($class) as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - public function getMethodAnnotations(ReflectionMethod $method): array + /** @return array */ + public function getMethodAttributes(ReflectionMethod $method): array { return $this->convertToAttributeInstances($method->getAttributes()); } - /** - * @param class-string $annotationName - * - * @return T|null - * - * @template T - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - foreach ($this->getMethodAnnotations($method) as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - public function getPropertyAnnotations(ReflectionProperty $property): array + /** @return array */ + public function getPropertyAttributes(ReflectionProperty $property): array { return $this->convertToAttributeInstances($property->getAttributes()); } - /** - * @param class-string $annotationName - * - * @return T|null - * - * @template T - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - foreach ($this->getPropertyAnnotations($property) as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - /** * @param ReflectionAttribute[] $attributes * diff --git a/psalm-baseline.xml b/psalm-baseline.xml index a1a8c0535..b62935081 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + IteratorAggregate @@ -79,7 +79,7 @@ $mapping - + $mapping $options diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php index 753470311..e19079cf3 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php @@ -8,12 +8,16 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; +use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Documents\CmsUser; use Generator; use PHPUnit\Framework\Attributes\DataProvider; use stdClass; +use function assert; + abstract class AbstractAnnotationDriverTestCase extends AbstractMappingDriverTestCase { public function testFieldInheritance(): void @@ -159,11 +163,10 @@ public function testClassCanBeMappedByOneAbstractDocument(object $wrong, string $this->expectException(MappingException::class); $this->expectExceptionMessageMatches($messageRegExp); - $cm = new ClassMetadata($wrong::class); - $reader = new AnnotationReader(); - $annotationDriver = new AnnotationDriver($reader); + $cm = new ClassMetadata($wrong::class); + $driver = static::loadDriver(); - $annotationDriver->loadMetadataForClass($wrong::class, $cm); + $driver->loadMetadataForClass($wrong::class, $cm); } public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generator @@ -173,7 +176,9 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato * @ODM\Document() * @ODM\EmbeddedDocument */ - new class () { + new #[ODM\Document] + #[ODM\EmbeddedDocument] + class () { }, '/as EmbeddedDocument because it was already mapped as Document\.$/', ]; @@ -183,7 +188,9 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato * @ODM\Document() * @ODM\File */ - new class () { + new #[ODM\Document] + #[ODM\File] + class () { }, '/as File because it was already mapped as Document\.$/', ]; @@ -193,7 +200,9 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato * @ODM\Document() * @ODM\QueryResultDocument */ - new class () { + new #[ODM\Document] + #[ODM\QueryResultDocument] + class () { }, '/as QueryResultDocument because it was already mapped as Document\.$/', ]; @@ -203,7 +212,9 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato * @ODM\Document() * @ODM\View */ - new class () { + new #[ODM\Document] + #[ODM\View] + class () { }, '/as View because it was already mapped as Document\.$/', ]; @@ -213,7 +224,9 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato * @ODM\Document() * @ODM\MappedSuperclass */ - new class () { + new #[ODM\Document] + #[ODM\MappedSuperclass] + class () { }, '/as MappedSuperclass because it was already mapped as Document\.$/', ]; @@ -223,7 +236,9 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato * @ODM\MappedSuperclass() * @ODM\Document */ - new class () { + new #[ODM\MappedSuperclass] + #[ODM\Document] + class () { }, '/as Document because it was already mapped as MappedSuperclass\.$/', ]; @@ -231,17 +246,17 @@ public static function provideClassCanBeMappedByOneAbstractDocument(): ?Generato public function testWrongValueForValidationValidatorShouldThrowException(): void { - $annotationDriver = $this->loadDriver(); - $classMetadata = new ClassMetadata(WrongValueForValidationValidator::class); + $driver = static::loadDriver(); + $classMetadata = new ClassMetadata(WrongValueForValidationValidator::class); $this->expectException(MappingException::class); $this->expectExceptionMessage('The following schema validation error occurred while parsing the "validator" property of the "Doctrine\ODM\MongoDB\Tests\Mapping\WrongValueForValidationValidator" class: "Got parse error at "w", position 0: "SPECIAL_EXPECTED"" (code 0).'); - $annotationDriver->loadMetadataForClass($classMetadata->name, $classMetadata); + $driver->loadMetadataForClass($classMetadata->name, $classMetadata); } - protected function loadDriverForCMSDocuments(): AnnotationDriver + protected function loadDriverForCMSDocuments(): MappingDriver { - $annotationDriver = $this->loadDriver(); - self::assertInstanceOf(AnnotationDriver::class, $annotationDriver); + $annotationDriver = static::loadDriver(); + assert($annotationDriver instanceof AnnotationDriver || $annotationDriver instanceof AttributeDriver); $annotationDriver->addPaths([__DIR__ . '/../../../../../Documents']); return $annotationDriver; diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php index ebafc3304..c114a3022 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php @@ -24,7 +24,7 @@ protected static function loadDriver(): MappingDriver public function testSetShardKeyOptionsByAttributes(): void { $class = new ClassMetadata(stdClass::class); - $driver = $this->loadDriver(); + $driver = static::loadDriver(); $element = new SimpleXMLElement(''); /** @uses XmlDriver::setShardKey */ @@ -41,7 +41,7 @@ public function testSetShardKeyOptionsByAttributes(): void public function testInvalidMappingFileTriggersException(): void { $className = InvalidMappingDocument::class; - $mappingDriver = $this->loadDriver(); + $mappingDriver = static::loadDriver(); $class = new ClassMetadata($className);