From 94e8a96c606d5b5331c0575a1fd074ac887df4d6 Mon Sep 17 00:00:00 2001
From: Fran Moreno <franmomu@gmail.com>
Date: Fri, 8 Dec 2023 12:02:26 +0100
Subject: [PATCH] AttributeDriver no longer extends AnnotationDriver

---
 UPGRADE-2.7.md                                |   6 +
 .../Mapping/Driver/AttributeDriver.php        | 394 +++++++++++++++++-
 .../Mapping/Driver/AttributeReader.php        |  66 +--
 psalm-baseline.xml                            |   9 +-
 .../AbstractAnnotationDriverTestCase.php      |  47 ++-
 .../Tests/Mapping/XmlMappingDriverTest.php    |   4 +-
 6 files changed, 444 insertions(+), 82 deletions(-)
 create mode 100644 UPGRADE-2.7.md

diff --git a/UPGRADE-2.7.md b/UPGRADE-2.7.md
new file mode 100644
index 0000000000..db75980f7b
--- /dev/null
+++ b/UPGRADE-2.7.md
@@ -0,0 +1,6 @@
+# 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`.
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php
index edf8d5c612..31dc77ba45 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php
@@ -5,16 +5,372 @@
 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\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 parameters 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, \Doctrine\Persistence\Mapping\ClassMetadata $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<object>     $class
+     * @param array<string, int|string> $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<object> $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 +380,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 863c424873..3dcb5fe9ee 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<object> */
+    public function getClassAttributes(ReflectionClass $class): array
     {
         return $this->convertToAttributeInstances($class->getAttributes());
     }
 
-    /**
-     * @param class-string<T> $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<object> */
+    public function getMethodAttributes(ReflectionMethod $method): array
     {
         return $this->convertToAttributeInstances($method->getAttributes());
     }
 
-    /**
-     * @param class-string<T> $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<object> */
+    public function getPropertyAttributes(ReflectionProperty $property): array
     {
         return $this->convertToAttributeInstances($property->getAttributes());
     }
 
-    /**
-     * @param class-string<T> $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<object>[] $attributes
      *
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index c62587de74..c17c9fa701 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<files psalm-version="5.15.0@5c774aca4746caf3d239d9c8cadb9f882ca29352">
+<files psalm-version="5.17.0@c620f6e80d0abfca532b00bda366062aaedf6e5d">
   <file src="lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php">
     <MissingTemplateParam>
       <code>IteratorAggregate</code>
@@ -82,6 +82,13 @@
   <file src="lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php">
     <InvalidArgument>
       <code>$mapping</code>
+      <code>$options</code>
+    </InvalidArgument>
+  </file>
+  <file src="lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php">
+    <InvalidArgument>
+      <code>$mapping</code>
+      <code>$options</code>
     </InvalidArgument>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php">
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php
index 7534703110..aefb7ab169 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 ebafc33040..c114a30226 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('<shard-key unique="true" numInitialChunks="4096"><key name="_id"/></shard-key>');
 
         /** @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);