diff --git a/lib/Doctrine/ORM/Mapping/AttributeOverride.php b/lib/Doctrine/ORM/Mapping/AttributeOverride.php index f86d3a1521d..33db15d3f4c 100644 --- a/lib/Doctrine/ORM/Mapping/AttributeOverride.php +++ b/lib/Doctrine/ORM/Mapping/AttributeOverride.php @@ -19,6 +19,8 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * This annotation is used to override the mapping of a entity property. * @@ -28,6 +30,7 @@ * @Annotation * @Target("ANNOTATION") */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final class AttributeOverride implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/Cache.php b/lib/Doctrine/ORM/Mapping/Cache.php index 3226b603160..b3e992e32f8 100644 --- a/lib/Doctrine/ORM/Mapping/Cache.php +++ b/lib/Doctrine/ORM/Mapping/Cache.php @@ -19,6 +19,8 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * Caching to an entity or a collection. * @@ -28,6 +30,7 @@ * @Annotation * @Target({"CLASS","PROPERTY"}) */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)] final class Cache implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php b/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php index 3657b764f1e..8121c1fc958 100644 --- a/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php +++ b/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class ChangeTrackingPolicy implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/Column.php b/lib/Doctrine/ORM/Mapping/Column.php index 711590be672..0c1a4c1a075 100644 --- a/lib/Doctrine/ORM/Mapping/Column.php +++ b/lib/Doctrine/ORM/Mapping/Column.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class Column implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php b/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php index 41e200e12a7..a48ba1acb1b 100644 --- a/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php +++ b/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class CustomIdGenerator implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php index 97ca7e9b577..332f6d7ad5a 100644 --- a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class DiscriminatorColumn implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php b/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php index 09d619465cd..720b7dbe596 100644 --- a/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class DiscriminatorMap implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php b/lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php new file mode 100644 index 00000000000..a4ad4806980 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php @@ -0,0 +1,87 @@ + */ + private $isRepeatableAttribute = []; + + function getClassAnnotations(\ReflectionClass $class) + { + return $this->convertToAttributeInstances($class->getAttributes()); + } + + function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + return $this->getClassAnnotations($class)[$annotationName] ?? ($this->isRepeatable($annotationName) ? [] : null); + } + + function getMethodAnnotations(\ReflectionMethod $method) + { + return $this->convertToAttributeInstances($method->getAttributes()); + } + + function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + return $this->getMethodAnnotations($method)[$annotationName] ?? ($this->isRepeatable($annotationName) ? [] : null); + } + + function getPropertyAnnotations(\ReflectionProperty $property) + { + return $this->convertToAttributeInstances($property->getAttributes()); + } + + function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + return $this->getPropertyAnnotations($property)[$annotationName] ?? ($this->isRepeatable($annotationName) ? [] : null); + } + + private function convertToAttributeInstances(array $attributes) + { + $instances = []; + + foreach ($attributes as $attribute) { + // Make sure we only get Doctrine Annotations + if (is_subclass_of($attribute->getName(), Annotation::class)) { + $instance = new $attribute->getName(); + $arguments = $attribute->getArguments(); + + // unnamed argument is automatically "value" in Doctrine Annotations + if (count($arguments) >= 1 && isset($arguments[0])) { + $arguments['value'] = $arguments[0]; + unset($arguments[0]); + } + + // This works using the old Annotation, but will probably break Attribute IDE autocomplete support + foreach ($arguments as $name => $value) { + $instance->$name = $value; + } + + if ($this->isRepeatable($attribute->getName())) { + $instances[$attribute->getName()][] = $instance; + } else { + $instances[$attribute->getName()] = $instance; + } + } + } + + return $instances; + } + + private function isRepeatable(string $attributeClassName) : bool + { + if (isset($this->isRepeatableAttribute[$attributeClassName])) { + return $this->isRepeatableAttribute[$attributeClassName]; + } + + $reflectionClass = new \ReflectionClass($attributeClassName); + $attribute = $reflectionClass->getAttributes()[0]; + + return $this->isRepeatableAttribute[$attributeClassName] = $attribute->flags && Attribute::IS_REPEATABLE; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/Driver/AttributesDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AttributesDriver.php new file mode 100644 index 00000000000..aebab108918 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/AttributesDriver.php @@ -0,0 +1,503 @@ + 1, + Mapping\MappedSuperclass::class => 2, + ]; + + public function __construct(array $paths) + { + parent::__construct(new AttributeReader(), $paths); + } + + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ + $reflectionClass = $metadata->getReflectionClass(); + + $classAttributes = $this->reader->getClassAnnotations($reflectionClass); + + // Evaluate Entity annotation + if (isset($classAttributes[Mapping\Entity::class])) { + $entityAttribute = $classAttributes[Mapping\Entity::class]; + if ($entityAttribute->repositoryClass !== null) { + $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass); + } + + if ($entityAttribute->readOnly) { + $metadata->markReadOnly(); + } + } else if (isset($classAttributes[Mapping\MappedSuperclass::class])) { + $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class]; + + $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass); + $metadata->isMappedSuperclass = true; + } else if (isset($classAttributes[Mapping\Embeddable::class])) { + $metadata->isEmbeddedClass = true; + } else { + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); + } + + // Evaluate Table annotation + if (isset($classAttributes[Mapping\Table::class])) { + $tableAnnot = $classAttributes[Mapping\Table::class]; + $primaryTable = [ + 'name' => $tableAnnot->name, + 'schema' => $tableAnnot->schema + ]; + + if (isset($classAttributes[Mapping\Index::class])) { + foreach ($classAttributes[Mapping\Index::class] as $indexAnnot) { + $index = ['columns' => $indexAnnot->columns]; + + if ( ! empty($indexAnnot->flags)) { + $index['flags'] = $indexAnnot->flags; + } + + if ( ! empty($indexAnnot->options)) { + $index['options'] = $indexAnnot->options; + } + + if ( ! empty($indexAnnot->name)) { + $primaryTable['indexes'][$indexAnnot->name] = $index; + } else { + $primaryTable['indexes'][] = $index; + } + } + } + + if (isset($classAttributes[Mapping\UniqueConstraint::class])) { + foreach ($classAttributes[Mapping\UniqueConstraint::class] as $uniqueConstraintAnnot) { + $uniqueConstraint = ['columns' => $uniqueConstraintAnnot->columns]; + + if ( ! empty($uniqueConstraintAnnot->options)) { + $uniqueConstraint['options'] = $uniqueConstraintAnnot->options; + } + + if ( ! empty($uniqueConstraintAnnot->name)) { + $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; + } else { + $primaryTable['uniqueConstraints'][] = $uniqueConstraint; + } + } + } + + if ($tableAnnot->options) { + $primaryTable['options'] = $tableAnnot->options; + } + + $metadata->setPrimaryTable($primaryTable); + } + + // Evaluate @Cache annotation + if (isset($classAttributes[Mapping\Cache::class])) { + $cacheAttribute = $classAttributes[Mapping\Cache::class]; + $cacheMap = [ + 'region' => $cacheAttribute->region, + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), + ]; + + $metadata->enableCache($cacheMap); + } + + // Evaluate InheritanceType annotation + if (isset($classAttributes[Mapping\InheritanceType::class])) { + $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class]; + + $metadata->setInheritanceType( + constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value) + ); + + if ($metadata->inheritanceType != Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { + // Evaluate DiscriminatorColumn annotation + if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) { + $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class]; + + $metadata->setDiscriminatorColumn( + [ + 'name' => $discrColumnAttribute->name, + 'type' => $discrColumnAttribute->type ?: 'string', + 'length' => $discrColumnAttribute->length ?: 255, + 'columnDefinition' => $discrColumnAttribute->columnDefinition, + ] + ); + } else { + $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); + } + + // Evaluate DiscriminatorMap annotation + if (isset($classAttributes[Mapping\DiscriminatorMap::class])) { + $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class]; + $metadata->setDiscriminatorMap($discrMapAttribute->value); + } + } + } + + // Evaluate DoctrineChangeTrackingPolicy annotation + if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) { + $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class]; + $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value)); + } + + // Evaluate annotations on properties/fields + /* @var $property \ReflectionProperty */ + foreach ($reflectionClass->getProperties() as $property) { + if ($metadata->isMappedSuperclass && ! $property->isPrivate() + || + $metadata->isInheritedField($property->name) + || + $metadata->isInheritedAssociation($property->name) + || + $metadata->isInheritedEmbeddedClass($property->name)) { + continue; + } + + $mapping = []; + $mapping['fieldName'] = $property->getName(); + + // Evaluate @Cache annotation + if (($cacheAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class)) !== null) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults( + $mapping['fieldName'], + [ + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), + 'region' => $cacheAttribute->region, + ] + ); + } + // Check for JoinColumn/JoinColumns annotations + $joinColumns = []; + + if ($joinColumnAttribute = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class)) { + $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute); + } + + // Field can only be annotated with one of: + // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany + if ($columnAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Column::class)) { + if ($columnAttribute->type == null) { + throw MappingException::propertyTypeIsRequired($className, $property->getName()); + } + + $mapping = $this->columnToArray($property->getName(), $columnAttribute); + + if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + if ($generatedValueAttribute = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class)) { + $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy)); + } + + if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) { + $metadata->setVersionMapping($mapping); + } + + $metadata->mapField($mapping); + + // Check for SequenceGenerator/TableGenerator definition + if ($seqGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class)) { + $metadata->setSequenceGeneratorDefinition( + [ + 'sequenceName' => $seqGeneratorAttribute->sequenceName, + 'allocationSize' => $seqGeneratorAttribute->allocationSize, + 'initialValue' => $seqGeneratorAttribute->initialValue + ] + ); + } else if ($this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) { + throw MappingException::tableIdGeneratorNotImplemented($className); + } else if ($customGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\CustomIdGenerator::class)) { + $metadata->setCustomGeneratorDefinition( + [ + 'class' => $customGeneratorAttribute->class + ] + ); + } + } else if ($oneToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class)) { + if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + $mapping['targetEntity'] = $oneToOneAttribute->targetEntity; + $mapping['joinColumns'] = $joinColumns; + $mapping['mappedBy'] = $oneToOneAttribute->mappedBy; + $mapping['inversedBy'] = $oneToOneAttribute->inversedBy; + $mapping['cascade'] = $oneToOneAttribute->cascade; + $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch); + $metadata->mapOneToOne($mapping); + } else if ($oneToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class)) { + $mapping['mappedBy'] = $oneToManyAttribute->mappedBy; + $mapping['targetEntity'] = $oneToManyAttribute->targetEntity; + $mapping['cascade'] = $oneToManyAttribute->cascade; + $mapping['indexBy'] = $oneToManyAttribute->indexBy; + $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch); + + if ($orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class)) { + $mapping['orderBy'] = $orderByAttribute->value; + } + + $metadata->mapOneToMany($mapping); + } else if ($manyToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class)) { + if ($idAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + $mapping['joinColumns'] = $joinColumns; + $mapping['cascade'] = $manyToOneAttribute->cascade; + $mapping['inversedBy'] = $manyToOneAttribute->inversedBy; + $mapping['targetEntity'] = $manyToOneAttribute->targetEntity; + $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch); + $metadata->mapManyToOne($mapping); + } else if ($manyToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class)) { + $joinTable = []; + + if ($joinTableAttribute = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class)) { + $joinTable = [ + 'name' => $joinTableAttribute->name, + 'schema' => $joinTableAttribute->schema + ]; + + foreach ($this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class) as $joinColumn) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + foreach ($this->reader->getPropertyAnnotation($property, Mapping\InverseJoinColumn::class) as $joinColumn) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); + } + } + + $mapping['joinTable'] = $joinTable; + $mapping['targetEntity'] = $manyToManyAttribute->targetEntity; + $mapping['mappedBy'] = $manyToManyAttribute->mappedBy; + $mapping['inversedBy'] = $manyToManyAttribute->inversedBy; + $mapping['cascade'] = $manyToManyAttribute->cascade; + $mapping['indexBy'] = $manyToManyAttribute->indexBy; + $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch); + + if ($orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class)) { + $mapping['orderBy'] = $orderByAttribute->value; + } + + $metadata->mapManyToMany($mapping); + } else if ($embeddedAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class)) { + $mapping['class'] = $embeddedAttribute->class; + $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix; + + $metadata->mapEmbedded($mapping); + } + } + + // Evaluate AttributeOverrides annotation + if (isset($classAttributes[Mapping\AttributeOverride::class])) { + + foreach ($classAttributes[Mapping\AttributeOverride::class] as $attributeOverrideAttribute) { + $attributeOverride = $this->columnToArray($attributeOverrideAttribute->name, $attributeOverrideAttribute->column); + + $metadata->setAttributeOverride($attributeOverrideAttribute->name, $attributeOverride); + } + } + + // Evaluate EntityListeners annotation + if (isset($classAttributes[Mapping\EntityListeners::class])) { + $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class]; + + foreach ($entityListenersAttribute->value as $item) { + $listenerClassName = $metadata->fullyQualifiedClassName($item); + + if ( ! class_exists($listenerClassName)) { + throw MappingException::entityListenerClassNotFound($listenerClassName, $className); + } + + $hasMapping = false; + $listenerClass = new \ReflectionClass($listenerClassName); + + /* @var $method \ReflectionMethod */ + foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + // find method callbacks. + $callbacks = $this->getMethodCallbacks($method); + $hasMapping = $hasMapping ?: ( ! empty($callbacks)); + + foreach ($callbacks as $value) { + $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); + } + } + + // Evaluate the listener using naming convention. + if ( ! $hasMapping ) { + EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); + } + } + } + + // Evaluate @HasLifecycleCallbacks annotation + if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) { + /* @var $method \ReflectionMethod */ + foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + foreach ($this->getMethodCallbacks($method) as $value) { + $metadata->addLifecycleCallback($value[0], $value[1]); + } + } + } + } + + /** + * Attempts to resolve the fetch mode. + * + * @param string $className The class name. + * @param string $fetchMode The fetch mode. + * + * @return integer The fetch mode as defined in ClassMetadata. + * + * @throws MappingException If the fetch mode is not valid. + */ + private function getFetchMode($className, $fetchMode) + { + if ( ! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { + throw MappingException::invalidFetchMode($className, $fetchMode); + } + + return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); + } + + /** + * Parses the given method. + * + * @param \ReflectionMethod $method + * + * @return callable[] + */ + private function getMethodCallbacks(\ReflectionMethod $method) + { + $callbacks = []; + $attributes = $this->reader->getMethodAnnotations($method); + + foreach ($attributes as $attribute) { + if ($attribute instanceof Mapping\PrePersist) { + $callbacks[] = [$method->name, Events::prePersist]; + } + + if ($attribute instanceof Mapping\PostPersist) { + $callbacks[] = [$method->name, Events::postPersist]; + } + + if ($attribute instanceof Mapping\PreUpdate) { + $callbacks[] = [$method->name, Events::preUpdate]; + } + + if ($attribute instanceof Mapping\PostUpdate) { + $callbacks[] = [$method->name, Events::postUpdate]; + } + + if ($attribute instanceof Mapping\PreRemove) { + $callbacks[] = [$method->name, Events::preRemove]; + } + + if ($attribute instanceof Mapping\PostRemove) { + $callbacks[] = [$method->name, Events::postRemove]; + } + + if ($attribute instanceof Mapping\PostLoad) { + $callbacks[] = [$method->name, Events::postLoad]; + } + + if ($attribute instanceof Mapping\PreFlush) { + $callbacks[] = [$method->name, Events::preFlush]; + } + } + + return $callbacks; + } + + /** + * Parse the given JoinColumn as array + * + * @param Mapping\JoinColumn $joinColumn + * + * @return mixed[] + * + * @psalm-return array{ + * name: string, + * unique: bool, + * nullable: bool, + * onDelete: mixed, + * columnDefinition: string, + * referencedColumnName: string + * } + */ + private function joinColumnToArray(Mapping\JoinColumn $joinColumn) + { + return [ + 'name' => $joinColumn->name, + 'unique' => $joinColumn->unique, + 'nullable' => $joinColumn->nullable, + 'onDelete' => $joinColumn->onDelete, + 'columnDefinition' => $joinColumn->columnDefinition, + 'referencedColumnName' => $joinColumn->referencedColumnName, + ]; + } + + /** + * Parse the given Column as array + * + * @param string $fieldName + * @param Mapping\Column $column + * + * @return mixed[] + * + * @psalm-return array{ + * fieldName: string, + * type: mixed, + * scale: int, + * length: int, + * unique: bool, + * nullable: bool, + * precision: int, + * options?: mixed[], + * columnName?: string, + * columnDefinition?: string + * } + */ + private function columnToArray($fieldName, Mapping\Column $column) + { + $mapping = [ + 'fieldName' => $fieldName, + 'type' => $column->type, + 'scale' => $column->scale, + 'length' => $column->length, + 'unique' => $column->unique, + 'nullable' => $column->nullable, + 'precision' => $column->precision + ]; + + if ($column->options) { + $mapping['options'] = $column->options; + } + + if (isset($column->name)) { + $mapping['columnName'] = $column->name; + } + + if (isset($column->columnDefinition)) { + $mapping['columnDefinition'] = $column->columnDefinition; + } + + return $mapping; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/Embeddable.php b/lib/Doctrine/ORM/Mapping/Embeddable.php index f14bfac82a6..54a5f474206 100644 --- a/lib/Doctrine/ORM/Mapping/Embeddable.php +++ b/lib/Doctrine/ORM/Mapping/Embeddable.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class Embeddable implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/Embedded.php b/lib/Doctrine/ORM/Mapping/Embedded.php index 37339108bc9..5da283efacc 100644 --- a/lib/Doctrine/ORM/Mapping/Embedded.php +++ b/lib/Doctrine/ORM/Mapping/Embedded.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class Embedded implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/Entity.php b/lib/Doctrine/ORM/Mapping/Entity.php index edf6ad5a4be..248a90be30e 100644 --- a/lib/Doctrine/ORM/Mapping/Entity.php +++ b/lib/Doctrine/ORM/Mapping/Entity.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class Entity implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/GeneratedValue.php b/lib/Doctrine/ORM/Mapping/GeneratedValue.php index 27c03d4bee7..1a2f572eeb8 100644 --- a/lib/Doctrine/ORM/Mapping/GeneratedValue.php +++ b/lib/Doctrine/ORM/Mapping/GeneratedValue.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class GeneratedValue implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php b/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php index 313ece31f8c..9bfbdf1cc59 100644 --- a/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php +++ b/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class HasLifecycleCallbacks implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/Id.php b/lib/Doctrine/ORM/Mapping/Id.php index 6c9bcef0d71..06ff443dd40 100644 --- a/lib/Doctrine/ORM/Mapping/Id.php +++ b/lib/Doctrine/ORM/Mapping/Id.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class Id implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/Index.php b/lib/Doctrine/ORM/Mapping/Index.php index 45953a80478..24eb752a8b7 100644 --- a/lib/Doctrine/ORM/Mapping/Index.php +++ b/lib/Doctrine/ORM/Mapping/Index.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("ANNOTATION") */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final class Index implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/InheritanceType.php b/lib/Doctrine/ORM/Mapping/InheritanceType.php index de803369a30..5f42fb095d2 100644 --- a/lib/Doctrine/ORM/Mapping/InheritanceType.php +++ b/lib/Doctrine/ORM/Mapping/InheritanceType.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class InheritanceType implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/InverseJoinColumn.php b/lib/Doctrine/ORM/Mapping/InverseJoinColumn.php new file mode 100644 index 00000000000..14b5518b9c3 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/InverseJoinColumn.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +use Attribute; + +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] +final class InverseJoinColumn implements Annotation +{ + /** + * @var string + */ + public $name; + + /** + * @var string + */ + public $referencedColumnName = 'id'; + + /** + * @var boolean + */ + public $unique = false; + + /** + * @var boolean + */ + public $nullable = true; + + /** + * @var mixed + */ + public $onDelete; + + /** + * @var string + */ + public $columnDefinition; + + /** + * Field name used in non-object hydration (array/scalar). + * + * @var string + */ + public $fieldName; +} diff --git a/lib/Doctrine/ORM/Mapping/JoinColumn.php b/lib/Doctrine/ORM/Mapping/JoinColumn.php index febce917481..751946861d4 100644 --- a/lib/Doctrine/ORM/Mapping/JoinColumn.php +++ b/lib/Doctrine/ORM/Mapping/JoinColumn.php @@ -23,6 +23,7 @@ * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] final class JoinColumn implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/JoinTable.php b/lib/Doctrine/ORM/Mapping/JoinTable.php index 879316a2874..90664ca888a 100644 --- a/lib/Doctrine/ORM/Mapping/JoinTable.php +++ b/lib/Doctrine/ORM/Mapping/JoinTable.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class JoinTable implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/ManyToMany.php b/lib/Doctrine/ORM/Mapping/ManyToMany.php index ca2f53c9eea..e7bd6ac69fe 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToMany.php +++ b/lib/Doctrine/ORM/Mapping/ManyToMany.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class ManyToMany implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/ManyToOne.php b/lib/Doctrine/ORM/Mapping/ManyToOne.php index d3414e6a956..8147d932de4 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToOne.php +++ b/lib/Doctrine/ORM/Mapping/ManyToOne.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class ManyToOne implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/MappedSuperclass.php b/lib/Doctrine/ORM/Mapping/MappedSuperclass.php index 74588107d89..73b6766f417 100644 --- a/lib/Doctrine/ORM/Mapping/MappedSuperclass.php +++ b/lib/Doctrine/ORM/Mapping/MappedSuperclass.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class MappedSuperclass implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/OneToMany.php b/lib/Doctrine/ORM/Mapping/OneToMany.php index 4b2465718e1..c7519158d8f 100644 --- a/lib/Doctrine/ORM/Mapping/OneToMany.php +++ b/lib/Doctrine/ORM/Mapping/OneToMany.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class OneToMany implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/OneToOne.php b/lib/Doctrine/ORM/Mapping/OneToOne.php index b2ab81f88d9..de6a601547c 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOne.php +++ b/lib/Doctrine/ORM/Mapping/OneToOne.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class OneToOne implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/OrderBy.php b/lib/Doctrine/ORM/Mapping/OrderBy.php index ad1b7a8f714..cf6cdfe223a 100644 --- a/lib/Doctrine/ORM/Mapping/OrderBy.php +++ b/lib/Doctrine/ORM/Mapping/OrderBy.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class OrderBy implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/PostLoad.php b/lib/Doctrine/ORM/Mapping/PostLoad.php index 2f8e9932e35..bb91da54ac3 100644 --- a/lib/Doctrine/ORM/Mapping/PostLoad.php +++ b/lib/Doctrine/ORM/Mapping/PostLoad.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PostLoad implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PostPersist.php b/lib/Doctrine/ORM/Mapping/PostPersist.php index 2aea7194911..1785f1aee1e 100644 --- a/lib/Doctrine/ORM/Mapping/PostPersist.php +++ b/lib/Doctrine/ORM/Mapping/PostPersist.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PostPersist implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PostRemove.php b/lib/Doctrine/ORM/Mapping/PostRemove.php index 321c4bd547b..71c9dac6218 100644 --- a/lib/Doctrine/ORM/Mapping/PostRemove.php +++ b/lib/Doctrine/ORM/Mapping/PostRemove.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PostRemove implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PostUpdate.php b/lib/Doctrine/ORM/Mapping/PostUpdate.php index a55f7072a69..3ee2dcaecd8 100644 --- a/lib/Doctrine/ORM/Mapping/PostUpdate.php +++ b/lib/Doctrine/ORM/Mapping/PostUpdate.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PostUpdate implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PreFlush.php b/lib/Doctrine/ORM/Mapping/PreFlush.php index 6697d372c37..341d2ef88b3 100644 --- a/lib/Doctrine/ORM/Mapping/PreFlush.php +++ b/lib/Doctrine/ORM/Mapping/PreFlush.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PreFlush implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PrePersist.php b/lib/Doctrine/ORM/Mapping/PrePersist.php index fea05be6d3d..a0b2969bf2e 100644 --- a/lib/Doctrine/ORM/Mapping/PrePersist.php +++ b/lib/Doctrine/ORM/Mapping/PrePersist.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PrePersist implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PreRemove.php b/lib/Doctrine/ORM/Mapping/PreRemove.php index 29822edacc9..402153f7bb1 100644 --- a/lib/Doctrine/ORM/Mapping/PreRemove.php +++ b/lib/Doctrine/ORM/Mapping/PreRemove.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PreRemove implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/PreUpdate.php b/lib/Doctrine/ORM/Mapping/PreUpdate.php index 290df72e04a..4b04a65acc1 100644 --- a/lib/Doctrine/ORM/Mapping/PreUpdate.php +++ b/lib/Doctrine/ORM/Mapping/PreUpdate.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("METHOD") */ +#[Attribute(Attribute::TARGET_METHOD)] final class PreUpdate implements Annotation { } diff --git a/lib/Doctrine/ORM/Mapping/SequenceGenerator.php b/lib/Doctrine/ORM/Mapping/SequenceGenerator.php index ba1c45b6425..f53c84bd9ab 100644 --- a/lib/Doctrine/ORM/Mapping/SequenceGenerator.php +++ b/lib/Doctrine/ORM/Mapping/SequenceGenerator.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class SequenceGenerator implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/Table.php b/lib/Doctrine/ORM/Mapping/Table.php index 6ed703750be..cceff201082 100644 --- a/lib/Doctrine/ORM/Mapping/Table.php +++ b/lib/Doctrine/ORM/Mapping/Table.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("CLASS") */ +#[Attribute(Attribute::TARGET_CLASS)] final class Table implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/UniqueConstraint.php b/lib/Doctrine/ORM/Mapping/UniqueConstraint.php index f117d1873e8..ce087a8c266 100644 --- a/lib/Doctrine/ORM/Mapping/UniqueConstraint.php +++ b/lib/Doctrine/ORM/Mapping/UniqueConstraint.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("ANNOTATION") */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final class UniqueConstraint implements Annotation { /** diff --git a/lib/Doctrine/ORM/Mapping/Version.php b/lib/Doctrine/ORM/Mapping/Version.php index a2377027950..ac01fea5384 100644 --- a/lib/Doctrine/ORM/Mapping/Version.php +++ b/lib/Doctrine/ORM/Mapping/Version.php @@ -19,10 +19,13 @@ namespace Doctrine\ORM\Mapping; +use Attribute; + /** * @Annotation * @Target("PROPERTY") */ +#[Attribute(Attribute::TARGET_PROPERTY)] final class Version implements Annotation { } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 3eab2d6ae5c..8fdad5500eb 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -4,6 +4,7 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\Events; +use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\ClassMetadataInfo; @@ -1310,12 +1311,15 @@ public static function loadMetadata(ClassMetadataInfo $metadata) * @DiscriminatorMap({"cat" = "Cat", "dog" = "Dog"}) * @DiscriminatorColumn(name="discr", length=32, type="string") */ +#[ORM\Entity, ORM\InheritanceType("SINGLE_TABLE"), ORM\DiscriminatorColumn(name: "discr", length: 32, type: "string")] +#[ORM\DiscriminatorMap(["cat" => "Cat", "dog" => "Dog"])] abstract class Animal { /** * @Id @Column(type="string") @GeneratedValue(strategy="CUSTOM") * @CustomIdGenerator(class="stdClass") */ + #[ORM\Id, ORM\Column(type: "string"), ORM\GeneratedValue(strategy: "CUSTOM")] public $id; public static function loadMetadata(ClassMetadataInfo $metadata) @@ -1326,6 +1330,7 @@ public static function loadMetadata(ClassMetadataInfo $metadata) } /** @Entity */ +#[ORM\Entity] class Cat extends Animal { public static function loadMetadata(ClassMetadataInfo $metadata) @@ -1335,6 +1340,7 @@ public static function loadMetadata(ClassMetadataInfo $metadata) } /** @Entity */ +#[ORM\Entity] class Dog extends Animal { public static function loadMetadata(ClassMetadataInfo $metadata) @@ -1346,6 +1352,7 @@ public static function loadMetadata(ClassMetadataInfo $metadata) /** * @Entity */ +#[ORM\Entity] class DDC1170Entity { @@ -1362,11 +1369,13 @@ function __construct($value = null) * @GeneratedValue(strategy="NONE") * @Column(type="integer", columnDefinition = "INT unsigned NOT NULL") **/ + #[ORM\Id, ORM\GeneratedValue(strategy: "NONE"), ORM\Column(type: "integer", columnDefinition: "INT UNSIGNED NOT NULL")] private $id; /** * @Column(columnDefinition = "VARCHAR(255) NOT NULL") */ + #[ORM\Column(columnDefinition: "VARCHAR(255) NOT NULL")] private $value; /** @@ -1413,6 +1422,9 @@ public static function loadMetadata(ClassMetadataInfo $metadata) * @DiscriminatorMap({"ONE" = "DDC807SubClasse1", "TWO" = "DDC807SubClasse2"}) * @DiscriminatorColumn(name = "dtype", columnDefinition="ENUM('ONE','TWO')") */ +#[ORM\Entity, ORM\InheritanceType("SINGLE_TABLE")] +#[ORM\DiscriminatorColumn(name: "dtype", columnDefinition: "ENUM('ONE','TWO')")] +#[ORM\DiscriminatorMap(["ONE" => "DDC807SubClasse1", "TWO" => "DDC807SubClasse2"])] class DDC807Entity { /** @@ -1420,6 +1432,7 @@ class DDC807Entity * @Column(type="integer") * @GeneratedValue(strategy="NONE") **/ + #[ORM\Id, ORM\Column(type: "integer"), ORM\GeneratedValue(strategy: "NONE")] public $id; public static function loadMetadata(ClassMetadataInfo $metadata) @@ -1454,11 +1467,14 @@ class Group {} * @Entity * @Table(indexes={@Index(columns={"content"}, flags={"fulltext"}, options={"where": "content IS NOT NULL"})}) */ +#[ORM\Entity, ORM\Table(name: "Comment")] +#[ORM\Index(columns: ["content"], flags: ["fulltext"], options: ["where": "content IS NOT NULL"])] class Comment { /** * @Column(type="text") */ + #[ORM\Column(type: "text")] private $content; public static function loadMetadata(ClassMetadataInfo $metadata) @@ -1495,6 +1511,8 @@ public static function loadMetadata(ClassMetadataInfo $metadata) * "TWO" = "SingleTableEntityNoDiscriminatorColumnMappingSub2" * }) */ +#[ORM\Entity, ORM\InheritanceType("SINGLE_TABLE")] +#[ORM\DiscriminatorMap(["ONE" => "SingleTableEntityNoDiscriminatorColumnMappingSub1", "TWO" => "SingleTableEntityNoDiscriminatorColumnMappingSub2"])] class SingleTableEntityNoDiscriminatorColumnMapping { /** @@ -1502,6 +1520,7 @@ class SingleTableEntityNoDiscriminatorColumnMapping * @Column(type="integer") * @GeneratedValue(strategy="NONE") */ + #[ORM\Id, ORM\Column(type: "integer"), ORM\GeneratedValue(strategy: "NONE")] public $id; public static function loadMetadata(ClassMetadataInfo $metadata) @@ -1529,6 +1548,9 @@ class SingleTableEntityNoDiscriminatorColumnMappingSub2 extends SingleTableEntit * }) * @DiscriminatorColumn(name="dtype") */ +#[ORM\Entity, ORM\InheritanceType("SINGLE_TABLE")] +#[ORM\DiscriminatorMap(["ONE" => "SingleTableEntityNoDiscriminatorColumnMappingSub1", "TWO" => "SingleTableEntityNoDiscriminatorColumnMappingSub2"])] +#[ORM\DiscriminatorColumn(name: "dtype")] class SingleTableEntityIncompleteDiscriminatorColumnMapping { /** @@ -1536,6 +1558,7 @@ class SingleTableEntityIncompleteDiscriminatorColumnMapping * @Column(type="integer") * @GeneratedValue(strategy="NONE") */ + #[ORM\Id, ORM\Column(type: "integer"), ORM\GeneratedValue(strategy: "NONE")] public $id; public static function loadMetadata(ClassMetadataInfo $metadata) diff --git a/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php new file mode 100644 index 00000000000..4fc52ec7770 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/AttributeDriverTest.php @@ -0,0 +1,15 @@ +