welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
@@ -0,0 +1,898 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\Reader;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use UnexpectedValueException;
use function assert;
use function class_exists;
use function constant;
use function count;
use function defined;
use function get_class;
use function is_array;
use function is_numeric;
/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
*
* @deprecated This class will be removed in 3.0 without replacement.
*/
class AnnotationDriver extends CompatibilityAnnotationDriver
{
use ColocatedMappingDriver;
use ReflectionBasedDriver;
/**
* The annotation reader.
*
* @internal this property will be private in 3.0
*
* @var Reader
*/
protected $reader;
/**
* @var int[]
* @psalm-var array<class-string, int>
*/
protected $entityAnnotationClasses = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
/**
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
* docblock annotations.
*
* @param Reader $reader The AnnotationReader to use
* @param string|string[]|null $paths One or multiple paths where mapping classes can be found.
*/
public function __construct($reader, $paths = null, bool $reportFieldsWhereDeclared = false)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/10098',
'The annotation mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to the attribute or XML driver.'
);
$this->reader = $reader;
$this->addPaths((array) $paths);
if (! $reportFieldsWhereDeclared) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10455',
'In ORM 3.0, the AttributeDriver will report fields for the classes where they are declared. This may uncover invalid mapping configurations. To opt into the new mode also with the AnnotationDriver today, set the "reportFieldsWhereDeclared" constructor parameter to true.',
self::class
);
}
$this->reportFieldsWhereDeclared = $reportFieldsWhereDeclared;
}
/**
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
{
$class = $metadata->getReflectionClass()
// this happens when running annotation driver in combination with
// static reflection services. This is not the nicest fix
?? new ReflectionClass($metadata->name);
$classAnnotations = $this->reader->getClassAnnotations($class);
foreach ($classAnnotations as $key => $annot) {
if (! is_numeric($key)) {
continue;
}
$classAnnotations[get_class($annot)] = $annot;
}
// Evaluate Entity annotation
if (isset($classAnnotations[Mapping\Entity::class])) {
$entityAnnot = $classAnnotations[Mapping\Entity::class];
assert($entityAnnot instanceof Mapping\Entity);
if ($entityAnnot->repositoryClass !== null) {
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
}
if ($entityAnnot->readOnly) {
$metadata->markReadOnly();
}
} elseif (isset($classAnnotations[Mapping\MappedSuperclass::class])) {
$mappedSuperclassAnnot = $classAnnotations[Mapping\MappedSuperclass::class];
assert($mappedSuperclassAnnot instanceof Mapping\MappedSuperclass);
$metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
$metadata->isMappedSuperclass = true;
} elseif (isset($classAnnotations[Mapping\Embeddable::class])) {
$metadata->isEmbeddedClass = true;
} else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
}
// Evaluate Table annotation
if (isset($classAnnotations[Mapping\Table::class])) {
$tableAnnot = $classAnnotations[Mapping\Table::class];
assert($tableAnnot instanceof Mapping\Table);
$primaryTable = [
'name' => $tableAnnot->name,
'schema' => $tableAnnot->schema,
];
foreach ($tableAnnot->indexes ?? [] as $indexAnnot) {
$index = [];
if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}
if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexAnnot->name ?? count($primaryTable['indexes']))
);
}
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;
}
}
foreach ($tableAnnot->uniqueConstraints ?? [] as $uniqueConstraintAnnot) {
$uniqueConstraint = [];
if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}
if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}
if (
isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
|| (
! isset($uniqueConstraint['columns'])
&& ! isset($uniqueConstraint['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints']))
);
}
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($classAnnotations[Mapping\Cache::class])) {
$cacheAnnot = $classAnnotations[Mapping\Cache::class];
$cacheMap = [
'region' => $cacheAnnot->region,
'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
];
$metadata->enableCache($cacheMap);
}
// Evaluate NamedNativeQueries annotation
if (isset($classAnnotations[Mapping\NamedNativeQueries::class])) {
$namedNativeQueriesAnnot = $classAnnotations[Mapping\NamedNativeQueries::class];
foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
$metadata->addNamedNativeQuery(
[
'name' => $namedNativeQuery->name,
'query' => $namedNativeQuery->query,
'resultClass' => $namedNativeQuery->resultClass,
'resultSetMapping' => $namedNativeQuery->resultSetMapping,
]
);
}
}
// Evaluate SqlResultSetMappings annotation
if (isset($classAnnotations[Mapping\SqlResultSetMappings::class])) {
$sqlResultSetMappingsAnnot = $classAnnotations[Mapping\SqlResultSetMappings::class];
foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
$entities = [];
$columns = [];
foreach ($resultSetMapping->entities as $entityResultAnnot) {
$entityResult = [
'fields' => [],
'entityClass' => $entityResultAnnot->entityClass,
'discriminatorColumn' => $entityResultAnnot->discriminatorColumn,
];
foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
$entityResult['fields'][] = [
'name' => $fieldResultAnnot->name,
'column' => $fieldResultAnnot->column,
];
}
$entities[] = $entityResult;
}
foreach ($resultSetMapping->columns as $columnResultAnnot) {
$columns[] = [
'name' => $columnResultAnnot->name,
];
}
$metadata->addSqlResultSetMapping(
[
'name' => $resultSetMapping->name,
'entities' => $entities,
'columns' => $columns,
]
);
}
}
// Evaluate NamedQueries annotation
if (isset($classAnnotations[Mapping\NamedQueries::class])) {
$namedQueriesAnnot = $classAnnotations[Mapping\NamedQueries::class];
if (! is_array($namedQueriesAnnot->value)) {
throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
}
foreach ($namedQueriesAnnot->value as $namedQuery) {
if (! ($namedQuery instanceof Mapping\NamedQuery)) {
throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
}
$metadata->addNamedQuery(
[
'name' => $namedQuery->name,
'query' => $namedQuery->query,
]
);
}
}
// Evaluate InheritanceType annotation
if (isset($classAnnotations[Mapping\InheritanceType::class])) {
$inheritanceTypeAnnot = $classAnnotations[Mapping\InheritanceType::class];
assert($inheritanceTypeAnnot instanceof Mapping\InheritanceType);
$metadata->setInheritanceType(
constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)
);
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) {
$discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class];
assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn);
$columnDef = [
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type ?: 'string',
'length' => $discrColumnAnnot->length ?? 255,
'columnDefinition' => $discrColumnAnnot->columnDefinition,
'enumType' => $discrColumnAnnot->enumType,
];
if ($discrColumnAnnot->options) {
$columnDef['options'] = $discrColumnAnnot->options;
}
$metadata->setDiscriminatorColumn($columnDef);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate DiscriminatorMap annotation
if (isset($classAnnotations[Mapping\DiscriminatorMap::class])) {
$discrMapAnnot = $classAnnotations[Mapping\DiscriminatorMap::class];
assert($discrMapAnnot instanceof Mapping\DiscriminatorMap);
$metadata->setDiscriminatorMap($discrMapAnnot->value);
}
}
}
// Evaluate DoctrineChangeTrackingPolicy annotation
if (isset($classAnnotations[Mapping\ChangeTrackingPolicy::class])) {
$changeTrackingAnnot = $classAnnotations[Mapping\ChangeTrackingPolicy::class];
assert($changeTrackingAnnot instanceof Mapping\ChangeTrackingPolicy);
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value));
}
// Evaluate annotations on properties/fields
foreach ($class->getProperties() as $property) {
if ($this->isRepeatedPropertyDeclaration($property, $metadata)) {
continue;
}
$mapping = [];
$mapping['fieldName'] = $property->name;
// Evaluate @Cache annotation
$cacheAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class);
if ($cacheAnnot !== null) {
$mapping['cache'] = $metadata->getAssociationCacheDefaults(
$mapping['fieldName'],
[
'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
'region' => $cacheAnnot->region,
]
);
}
// Check for JoinColumn/JoinColumns annotations
$joinColumns = [];
$joinColumnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class);
if ($joinColumnAnnot) {
$joinColumns[] = $this->joinColumnToArray($joinColumnAnnot);
} else {
$joinColumnsAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumns::class);
if ($joinColumnsAnnot) {
foreach ($joinColumnsAnnot->value as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
}
}
// Field can only be annotated with one of:
// @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
$columnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Column::class);
if ($columnAnnot) {
$mapping = $this->columnToArray($property->name, $columnAnnot);
$idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
if ($idAnnot) {
$mapping['id'] = true;
}
$generatedValueAnnot = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class);
if ($generatedValueAnnot) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy));
}
if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
// Check for SequenceGenerator/TableGenerator definition
$seqGeneratorAnnot = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class);
if ($seqGeneratorAnnot) {
$metadata->setSequenceGeneratorDefinition(
[
'sequenceName' => $seqGeneratorAnnot->sequenceName,
'allocationSize' => $seqGeneratorAnnot->allocationSize,
'initialValue' => $seqGeneratorAnnot->initialValue,
]
);
} else {
$customGeneratorAnnot = $this->reader->getPropertyAnnotation($property, Mapping\CustomIdGenerator::class);
if ($customGeneratorAnnot) {
$metadata->setCustomGeneratorDefinition(
[
'class' => $customGeneratorAnnot->class,
]
);
}
}
} else {
$this->loadRelationShipMapping(
$property,
$mapping,
$metadata,
$joinColumns,
$className
);
}
}
// Evaluate AssociationOverrides annotation
if (isset($classAnnotations[Mapping\AssociationOverrides::class])) {
$associationOverridesAnnot = $classAnnotations[Mapping\AssociationOverrides::class];
assert($associationOverridesAnnot instanceof Mapping\AssociationOverrides);
foreach ($associationOverridesAnnot->overrides as $associationOverride) {
$override = [];
$fieldName = $associationOverride->name;
// Check for JoinColumn/JoinColumns annotations
if ($associationOverride->joinColumns) {
$joinColumns = [];
foreach ($associationOverride->joinColumns as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
$override['joinColumns'] = $joinColumns;
}
// Check for JoinTable annotations
if ($associationOverride->joinTable) {
$joinTableAnnot = $associationOverride->joinTable;
$joinTable = [
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema,
];
foreach ($joinTableAnnot->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
$override['joinTable'] = $joinTable;
}
// Check for inversedBy
if ($associationOverride->inversedBy) {
$override['inversedBy'] = $associationOverride->inversedBy;
}
// Check for `fetch`
if ($associationOverride->fetch) {
$override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
// Evaluate AttributeOverrides annotation
if (isset($classAnnotations[Mapping\AttributeOverrides::class])) {
$attributeOverridesAnnot = $classAnnotations[Mapping\AttributeOverrides::class];
assert($attributeOverridesAnnot instanceof Mapping\AttributeOverrides);
foreach ($attributeOverridesAnnot->overrides as $attributeOverrideAnnot) {
$attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column);
$metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride);
}
}
// Evaluate EntityListeners annotation
if (isset($classAnnotations[Mapping\EntityListeners::class])) {
$entityListenersAnnot = $classAnnotations[Mapping\EntityListeners::class];
assert($entityListenersAnnot instanceof Mapping\EntityListeners);
foreach ($entityListenersAnnot->value as $item) {
$listenerClassName = $metadata->fullyQualifiedClassName($item);
if (! class_exists($listenerClassName)) {
throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
}
$hasMapping = false;
$listenerClass = new ReflectionClass($listenerClassName);
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($classAnnotations[Mapping\HasLifecycleCallbacks::class])) {
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($this->getMethodCallbacks($method) as $value) {
$metadata->addLifecycleCallback($value[0], $value[1]);
}
}
}
}
/**
* @param mixed[] $joinColumns
* @param class-string $className
* @param array<string, mixed> $mapping
*/
private function loadRelationShipMapping(
ReflectionProperty $property,
array &$mapping,
PersistenceClassMetadata $metadata,
array $joinColumns,
string $className
): void {
$oneToOneAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class);
if ($oneToOneAnnot) {
$idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
if ($idAnnot) {
$mapping['id'] = true;
}
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity;
$mapping['joinColumns'] = $joinColumns;
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
$mapping['inversedBy'] = $oneToOneAnnot->inversedBy;
$mapping['cascade'] = $oneToOneAnnot->cascade;
$mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch);
$metadata->mapOneToOne($mapping);
return;
}
$oneToManyAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class);
if ($oneToManyAnnot) {
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
$mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
$mapping['cascade'] = $oneToManyAnnot->cascade;
$mapping['indexBy'] = $oneToManyAnnot->indexBy;
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch);
$orderByAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
if ($orderByAnnot) {
$mapping['orderBy'] = $orderByAnnot->value;
}
$metadata->mapOneToMany($mapping);
}
$manyToOneAnnot = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class);
if ($manyToOneAnnot) {
$idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
if ($idAnnot) {
$mapping['id'] = true;
}
$mapping['joinColumns'] = $joinColumns;
$mapping['cascade'] = $manyToOneAnnot->cascade;
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
$mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
$mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch);
$metadata->mapManyToOne($mapping);
}
$manyToManyAnnot = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class);
if ($manyToManyAnnot) {
$joinTable = [];
$joinTableAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class);
if ($joinTableAnnot) {
$joinTable = [
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema,
];
if ($joinTableAnnot->options) {
$joinTable['options'] = $joinTableAnnot->options;
}
foreach ($joinTableAnnot->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
}
$mapping['joinTable'] = $joinTable;
$mapping['targetEntity'] = $manyToManyAnnot->targetEntity;
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade;
$mapping['indexBy'] = $manyToManyAnnot->indexBy;
$mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch);
$orderByAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
if ($orderByAnnot) {
$mapping['orderBy'] = $orderByAnnot->value;
}
$metadata->mapManyToMany($mapping);
}
$embeddedAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class);
if ($embeddedAnnot) {
$mapping['class'] = $embeddedAnnot->class;
$mapping['columnPrefix'] = $embeddedAnnot->columnPrefix;
$metadata->mapEmbedded($mapping);
}
}
/**
* Attempts to resolve the fetch mode.
*
* @param class-string $className
*
* @psalm-return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getFetchMode(string $className, string $fetchMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
throw MappingException::invalidFetchMode($className, $fetchMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/**
* Attempts to resolve the generated mode.
*
* @psalm-return ClassMetadata::GENERATED_*
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getGeneratedMode(string $generatedMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
throw MappingException::invalidGeneratedMode($generatedMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
}
/**
* Parses the given method.
*
* @return list<array{string, string}>
* @psalm-return list<array{string, (Events::*)}>
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
$callbacks = [];
$annotations = $this->reader->getMethodAnnotations($method);
foreach ($annotations as $annot) {
if ($annot instanceof Mapping\PrePersist) {
$callbacks[] = [$method->name, Events::prePersist];
}
if ($annot instanceof Mapping\PostPersist) {
$callbacks[] = [$method->name, Events::postPersist];
}
if ($annot instanceof Mapping\PreUpdate) {
$callbacks[] = [$method->name, Events::preUpdate];
}
if ($annot instanceof Mapping\PostUpdate) {
$callbacks[] = [$method->name, Events::postUpdate];
}
if ($annot instanceof Mapping\PreRemove) {
$callbacks[] = [$method->name, Events::preRemove];
}
if ($annot instanceof Mapping\PostRemove) {
$callbacks[] = [$method->name, Events::postRemove];
}
if ($annot instanceof Mapping\PostLoad) {
$callbacks[] = [$method->name, Events::postLoad];
}
if ($annot instanceof Mapping\PreFlush) {
$callbacks[] = [$method->name, Events::preFlush];
}
}
return $callbacks;
}
/**
* Parse the given JoinColumn as array
*
* @return mixed[]
* @psalm-return array{
* name: string|null,
* unique: bool,
* nullable: bool,
* onDelete: mixed,
* columnDefinition: string|null,
* referencedColumnName: string,
* options?: array<string, mixed>
* }
*/
private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array
{
$mapping = [
'name' => $joinColumn->name,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
'referencedColumnName' => $joinColumn->referencedColumnName,
];
if ($joinColumn->options) {
$mapping['options'] = $joinColumn->options;
}
return $mapping;
}
/**
* Parse the given Column as array
*
* @return mixed[]
* @psalm-return array{
* fieldName: string,
* type: mixed,
* scale: int,
* length: int,
* unique: bool,
* nullable: bool,
* precision: int,
* notInsertable?: bool,
* notUpdateble?: bool,
* generated?: ClassMetadata::GENERATED_*,
* enumType?: class-string,
* options?: mixed[],
* columnName?: string,
* columnDefinition?: string
* }
*/
private function columnToArray(string $fieldName, Mapping\Column $column): array
{
$mapping = [
'fieldName' => $fieldName,
'type' => $column->type,
'scale' => $column->scale,
'length' => $column->length,
'unique' => $column->unique,
'nullable' => $column->nullable,
'precision' => $column->precision,
];
if (! $column->insertable) {
$mapping['notInsertable'] = true;
}
if (! $column->updatable) {
$mapping['notUpdatable'] = true;
}
if ($column->generated) {
$mapping['generated'] = $this->getGeneratedMode($column->generated);
}
if ($column->options) {
$mapping['options'] = $column->options;
}
if (isset($column->name)) {
$mapping['columnName'] = $column->name;
}
if (isset($column->columnDefinition)) {
$mapping['columnDefinition'] = $column->columnDefinition;
}
if ($column->enumType !== null) {
$mapping['enumType'] = $column->enumType;
}
return $mapping;
}
/**
* Retrieve the current annotation reader
*
* @return Reader
*/
public function getReader()
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9587',
'%s is deprecated with no replacement',
__METHOD__
);
return $this->reader;
}
/**
* {@inheritDoc}
*/
public function isTransient($className)
{
$classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
foreach ($classAnnotations as $annot) {
if (isset($this->entityAnnotationClasses[get_class($annot)])) {
return false;
}
}
return true;
}
/**
* Factory method for the Annotation Driver.
*
* @param mixed[]|string $paths
*
* @return AnnotationDriver
*/
public static function create($paths = [], ?AnnotationReader $reader = null)
{
if ($reader === null) {
$reader = new AnnotationReader();
}
return new self($reader, $paths);
}
}
@@ -0,0 +1,773 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingAttribute;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use LogicException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function assert;
use function class_exists;
use function constant;
use function defined;
use function get_class;
use const PHP_VERSION_ID;
class AttributeDriver extends CompatibilityAnnotationDriver
{
use ColocatedMappingDriver;
use ReflectionBasedDriver;
private const ENTITY_ATTRIBUTE_CLASSES = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
/**
* @deprecated override isTransient() instead of overriding this property
*
* @var array<class-string<MappingAttribute>, int>
*/
protected $entityAnnotationClasses = self::ENTITY_ATTRIBUTE_CLASSES;
/**
* The attribute reader.
*
* @internal this property will be private in 3.0
*
* @var AttributeReader
*/
protected $reader;
/** @param array<string> $paths */
public function __construct(array $paths, bool $reportFieldsWhereDeclared = false)
{
if (PHP_VERSION_ID < 80000) {
throw new LogicException(
'The attribute metadata driver cannot be enabled on PHP 7. Please upgrade to PHP 8 or choose a different'
. ' metadata driver.'
);
}
$this->reader = new AttributeReader();
$this->addPaths($paths);
if ($this->entityAnnotationClasses !== self::ENTITY_ATTRIBUTE_CLASSES) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10204',
'Changing the value of %s::$entityAnnotationClasses is deprecated and will have no effect in Doctrine ORM 3.0.',
self::class
);
}
if (! $reportFieldsWhereDeclared) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10455',
'In ORM 3.0, the AttributeDriver will report fields for the classes where they are declared. This may uncover invalid mapping configurations. To opt into the new mode today, set the "reportFieldsWhereDeclared" constructor parameter to true.',
self::class
);
}
$this->reportFieldsWhereDeclared = $reportFieldsWhereDeclared;
}
/**
* Retrieve the current annotation reader
*
* @deprecated no replacement planned.
*
* @return AttributeReader
*/
public function getReader()
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9587',
'%s is deprecated with no replacement',
__METHOD__
);
return $this->reader;
}
/**
* {@inheritDoc}
*/
public function isTransient($className)
{
$classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
foreach ($classAttributes as $a) {
$attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
if (isset($this->entityAnnotationClasses[get_class($attr)])) {
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
{
$reflectionClass = $metadata->getReflectionClass()
// this happens when running attribute driver in combination with
// static reflection services. This is not the nicest fix
?? new ReflectionClass($metadata->name);
$classAttributes = $this->reader->getClassAttributes($reflectionClass);
// Evaluate Entity attribute
if (isset($classAttributes[Mapping\Entity::class])) {
$entityAttribute = $classAttributes[Mapping\Entity::class];
if ($entityAttribute->repositoryClass !== null) {
$metadata->setCustomRepositoryClass($entityAttribute->repositoryClass);
}
if ($entityAttribute->readOnly) {
$metadata->markReadOnly();
}
} elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) {
$mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class];
$metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass);
$metadata->isMappedSuperclass = true;
} elseif (isset($classAttributes[Mapping\Embeddable::class])) {
$metadata->isEmbeddedClass = true;
} else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
}
$primaryTable = [];
if (isset($classAttributes[Mapping\Table::class])) {
$tableAnnot = $classAttributes[Mapping\Table::class];
$primaryTable['name'] = $tableAnnot->name;
$primaryTable['schema'] = $tableAnnot->schema;
if ($tableAnnot->options) {
$primaryTable['options'] = $tableAnnot->options;
}
}
if (isset($classAttributes[Mapping\Index::class])) {
foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) {
$index = [];
if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}
if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexAnnot->name ?? $idx)
);
}
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 $idx => $uniqueConstraintAnnot) {
$uniqueConstraint = [];
if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}
if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}
if (
isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
|| (
! isset($uniqueConstraint['columns'])
&& ! isset($uniqueConstraint['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueConstraintAnnot->name ?? $idx)
);
}
if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
}
if (! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
} else {
$primaryTable['uniqueConstraints'][] = $uniqueConstraint;
}
}
}
$metadata->setPrimaryTable($primaryTable);
// Evaluate #[Cache] attribute
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 attribute
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 !== ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn attribute
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
$columnDef = [
'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null,
'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null,
];
if ($discrColumnAttribute->options) {
$columnDef['options'] = (array) $discrColumnAttribute->options;
}
$metadata->setDiscriminatorColumn($columnDef);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate DiscriminatorMap attribute
if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
$discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
$metadata->setDiscriminatorMap($discrMapAttribute->value);
}
}
}
// Evaluate DoctrineChangeTrackingPolicy attribute
if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
$changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class];
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value));
}
foreach ($reflectionClass->getProperties() as $property) {
assert($property instanceof ReflectionProperty);
if ($this->isRepeatedPropertyDeclaration($property, $metadata)) {
continue;
}
$mapping = [];
$mapping['fieldName'] = $property->name;
// Evaluate #[Cache] attribute
$cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class);
if ($cacheAttribute !== null) {
assert($cacheAttribute instanceof Mapping\Cache);
$mapping['cache'] = $metadata->getAssociationCacheDefaults(
$mapping['fieldName'],
[
'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
'region' => $cacheAttribute->region,
]
);
}
// Check for JoinColumn/JoinColumns attributes
$joinColumns = [];
$joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class);
foreach ($joinColumnAttributes as $joinColumnAttribute) {
$joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
}
// Field can only be attributed with one of:
// Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded
$columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class);
$oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class);
$oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class);
$manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class);
$manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class);
$embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class);
if ($columnAttribute !== null) {
$mapping = $this->columnToArray($property->name, $columnAttribute);
if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
$mapping['id'] = true;
}
$generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class);
if ($generatedValueAttribute !== null) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy));
}
if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
// Check for SequenceGenerator/TableGenerator definition
$seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class);
$customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class);
if ($seqGeneratorAttribute !== null) {
$metadata->setSequenceGeneratorDefinition(
[
'sequenceName' => $seqGeneratorAttribute->sequenceName,
'allocationSize' => $seqGeneratorAttribute->allocationSize,
'initialValue' => $seqGeneratorAttribute->initialValue,
]
);
} elseif ($customGeneratorAttribute !== null) {
$metadata->setCustomGeneratorDefinition(
[
'class' => $customGeneratorAttribute->class,
]
);
}
} elseif ($oneToOneAttribute !== null) {
if ($this->reader->getPropertyAttribute($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);
} elseif ($oneToManyAttribute !== null) {
$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);
$orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
}
$metadata->mapOneToMany($mapping);
} elseif ($manyToOneAttribute !== null) {
$idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);
if ($idAttribute !== null) {
$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);
} elseif ($manyToManyAttribute !== null) {
$joinTable = [];
$joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class);
if ($joinTableAttribute !== null) {
$joinTable = [
'name' => $joinTableAttribute->name,
'schema' => $joinTableAttribute->schema,
];
if ($joinTableAttribute->options) {
$joinTable['options'] = $joinTableAttribute->options;
}
foreach ($joinTableAttribute->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
}
foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($this->reader->getPropertyAttributeCollection($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);
$orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
}
$metadata->mapManyToMany($mapping);
} elseif ($embeddedAttribute !== null) {
$mapping['class'] = $embeddedAttribute->class;
$mapping['columnPrefix'] = $embeddedAttribute->columnPrefix;
$metadata->mapEmbedded($mapping);
}
}
// Evaluate AssociationOverrides attribute
if (isset($classAttributes[Mapping\AssociationOverrides::class])) {
$associationOverride = $classAttributes[Mapping\AssociationOverrides::class];
foreach ($associationOverride->overrides as $associationOverride) {
$override = [];
$fieldName = $associationOverride->name;
// Check for JoinColumn/JoinColumns attributes
if ($associationOverride->joinColumns) {
$joinColumns = [];
foreach ($associationOverride->joinColumns as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
$override['joinColumns'] = $joinColumns;
}
if ($associationOverride->inverseJoinColumns) {
$joinColumns = [];
foreach ($associationOverride->inverseJoinColumns as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
$override['inverseJoinColumns'] = $joinColumns;
}
// Check for JoinTable attributes
if ($associationOverride->joinTable) {
$joinTableAnnot = $associationOverride->joinTable;
$joinTable = [
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema,
'joinColumns' => $override['joinColumns'] ?? [],
'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [],
];
unset($override['joinColumns'], $override['inverseJoinColumns']);
$override['joinTable'] = $joinTable;
}
// Check for inversedBy
if ($associationOverride->inversedBy) {
$override['inversedBy'] = $associationOverride->inversedBy;
}
// Check for `fetch`
if ($associationOverride->fetch) {
$override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
// Evaluate AttributeOverrides attribute
if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
$attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class];
foreach ($attributeOverridesAnnot->overrides as $attributeOverride) {
$mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column);
$metadata->setAttributeOverride($attributeOverride->name, $mapping);
}
}
// Evaluate EntityListeners attribute
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);
foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
assert($method instanceof ReflectionMethod);
// 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] attribute
if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
assert($method instanceof ReflectionMethod);
foreach ($this->getMethodCallbacks($method) as $value) {
$metadata->addLifecycleCallback($value[0], $value[1]);
}
}
}
}
/**
* Attempts to resolve the fetch mode.
*
* @param class-string $className The class name.
* @param string $fetchMode The fetch mode.
*
* @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getFetchMode(string $className, string $fetchMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
throw MappingException::invalidFetchMode($className, $fetchMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/**
* Attempts to resolve the generated mode.
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getGeneratedMode(string $generatedMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
throw MappingException::invalidGeneratedMode($generatedMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
}
/**
* Parses the given method.
*
* @return list<array{string, string}>
* @psalm-return list<array{string, (Events::*)}>
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
$callbacks = [];
$attributes = $this->reader->getMethodAttributes($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|Mapping\InverseJoinColumn $joinColumn
*
* @return mixed[]
* @psalm-return array{
* name: string|null,
* unique: bool,
* nullable: bool,
* onDelete: mixed,
* columnDefinition: string|null,
* referencedColumnName: string,
* options?: array<string, mixed>
* }
*/
private function joinColumnToArray($joinColumn): array
{
$mapping = [
'name' => $joinColumn->name,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
'referencedColumnName' => $joinColumn->referencedColumnName,
];
if ($joinColumn->options) {
$mapping['options'] = $joinColumn->options;
}
return $mapping;
}
/**
* Parse the given Column as array
*
* @return mixed[]
* @psalm-return array{
* fieldName: string,
* type: mixed,
* scale: int,
* length: int,
* unique: bool,
* nullable: bool,
* precision: int,
* enumType?: class-string,
* options?: mixed[],
* columnName?: string,
* columnDefinition?: string
* }
*/
private function columnToArray(string $fieldName, Mapping\Column $column): array
{
$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;
}
if ($column->updatable === false) {
$mapping['notUpdatable'] = true;
}
if ($column->insertable === false) {
$mapping['notInsertable'] = true;
}
if ($column->generated !== null) {
$mapping['generated'] = $this->getGeneratedMode($column->generated);
}
if ($column->enumType) {
$mapping['enumType'] = $column->enumType;
}
return $mapping;
}
}
@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Attribute;
use Doctrine\ORM\Mapping\Annotation;
use LogicException;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function assert;
use function is_string;
use function is_subclass_of;
use function sprintf;
/** @internal */
final class AttributeReader
{
/** @var array<class-string<Annotation>,bool> */
private array $isRepeatableAttribute = [];
/**
* @psalm-return class-string-map<T, T|RepeatableAttributeCollection<T>>
*
* @template T of Annotation
*/
public function getClassAttributes(ReflectionClass $class): array
{
return $this->convertToAttributeInstances($class->getAttributes());
}
/**
* @return class-string-map<T, T|RepeatableAttributeCollection<T>>
*
* @template T of Annotation
*/
public function getMethodAttributes(ReflectionMethod $method): array
{
return $this->convertToAttributeInstances($method->getAttributes());
}
/**
* @return class-string-map<T, T|RepeatableAttributeCollection<T>>
*
* @template T of Annotation
*/
public function getPropertyAttributes(ReflectionProperty $property): array
{
return $this->convertToAttributeInstances($property->getAttributes());
}
/**
* @param class-string<T> $attributeName The name of the annotation.
*
* @return T|null
*
* @template T of Annotation
*/
public function getPropertyAttribute(ReflectionProperty $property, $attributeName)
{
if ($this->isRepeatable($attributeName)) {
throw new LogicException(sprintf(
'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.',
$attributeName
));
}
return $this->getPropertyAttributes($property)[$attributeName] ?? null;
}
/**
* @param class-string<T> $attributeName The name of the annotation.
*
* @return RepeatableAttributeCollection<T>
*
* @template T of Annotation
*/
public function getPropertyAttributeCollection(
ReflectionProperty $property,
string $attributeName
): RepeatableAttributeCollection {
if (! $this->isRepeatable($attributeName)) {
throw new LogicException(sprintf(
'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.',
$attributeName
));
}
return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection();
}
/**
* @param array<ReflectionAttribute> $attributes
*
* @return class-string-map<T, T|RepeatableAttributeCollection<T>>
*
* @template T of Annotation
*/
private function convertToAttributeInstances(array $attributes): array
{
$instances = [];
foreach ($attributes as $attribute) {
$attributeName = $attribute->getName();
assert(is_string($attributeName));
// Make sure we only get Doctrine Attributes
if (! is_subclass_of($attributeName, Annotation::class)) {
continue;
}
$instance = $attribute->newInstance();
assert($instance instanceof Annotation);
if ($this->isRepeatable($attributeName)) {
if (! isset($instances[$attributeName])) {
$instances[$attributeName] = new RepeatableAttributeCollection();
}
$collection = $instances[$attributeName];
assert($collection instanceof RepeatableAttributeCollection);
$collection[] = $instance;
} else {
$instances[$attributeName] = $instance;
}
}
return $instances;
}
/** @param class-string<Annotation> $attributeClassName */
private function isRepeatable(string $attributeClassName): bool
{
if (isset($this->isRepeatableAttribute[$attributeClassName])) {
return $this->isRepeatableAttribute[$attributeClassName];
}
$reflectionClass = new ReflectionClass($attributeClassName);
$attribute = $reflectionClass->getAttributes()[0]->newInstance();
return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0;
}
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as PersistenceAnnotationDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use function class_exists;
if (! class_exists(PersistenceAnnotationDriver::class)) {
/** @internal This class will be removed in ORM 3.0. */
abstract class CompatibilityAnnotationDriver implements MappingDriver
{
}
} else {
/** @internal This class will be removed in ORM 3.0. */
abstract class CompatibilityAnnotationDriver extends PersistenceAnnotationDriver
{
}
}
@@ -0,0 +1,570 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use InvalidArgumentException;
use function array_diff;
use function array_keys;
use function array_merge;
use function assert;
use function count;
use function current;
use function get_class;
use function in_array;
use function preg_replace;
use function sort;
use function strtolower;
/**
* The DatabaseDriver reverse engineers the mapping metadata from a database.
*
* @link www.doctrine-project.org
*/
class DatabaseDriver implements MappingDriver
{
/**
* Replacement for {@see Types::ARRAY}.
*
* To be removed as soon as support for DBAL 3 is dropped.
*/
private const ARRAY = 'array';
/**
* Replacement for {@see Types::OBJECT}.
*
* To be removed as soon as support for DBAL 3 is dropped.
*/
private const OBJECT = 'object';
/**
* Replacement for {@see Types::JSON_ARRAY}.
*
* To be removed as soon as support for DBAL 2 is dropped.
*/
private const JSON_ARRAY = 'json_array';
/** @var AbstractSchemaManager */
private $sm;
/** @var array<string,Table>|null */
private $tables = null;
/** @var array<class-string, string> */
private $classToTableNames = [];
/** @psalm-var array<string, Table> */
private $manyToManyTables = [];
/** @var mixed[] */
private $classNamesForTables = [];
/** @var mixed[] */
private $fieldNamesForColumns = [];
/**
* The namespace for the generated entities.
*
* @var string|null
*/
private $namespace;
/** @var Inflector */
private $inflector;
public function __construct(AbstractSchemaManager $schemaManager)
{
$this->sm = $schemaManager;
$this->inflector = InflectorFactory::create()->build();
}
/**
* Set the namespace for the generated entities.
*
* @param string $namespace
*
* @return void
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
}
/**
* {@inheritDoc}
*/
public function isTransient($className)
{
return true;
}
/**
* {@inheritDoc}
*/
public function getAllClassNames()
{
$this->reverseEngineerMappingFromDatabase();
return array_keys($this->classToTableNames);
}
/**
* Sets class name for a table.
*
* @param string $tableName
* @param string $className
*
* @return void
*/
public function setClassNameForTable($tableName, $className)
{
$this->classNamesForTables[$tableName] = $className;
}
/**
* Sets field name for a column on a specific table.
*
* @param string $tableName
* @param string $columnName
* @param string $fieldName
*
* @return void
*/
public function setFieldNameForColumn($tableName, $columnName, $fieldName)
{
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
}
/**
* Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager.
*
* @param Table[] $entityTables
* @param Table[] $manyToManyTables
* @psalm-param list<Table> $entityTables
* @psalm-param list<Table> $manyToManyTables
*
* @return void
*/
public function setTables($entityTables, $manyToManyTables)
{
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($entityTables as $table) {
$className = $this->getClassNameForTable($table->getName());
$this->classToTableNames[$className] = $table->getName();
$this->tables[$table->getName()] = $table;
}
foreach ($manyToManyTables as $table) {
$this->manyToManyTables[$table->getName()] = $table;
}
}
public function setInflector(Inflector $inflector): void
{
$this->inflector = $inflector;
}
/**
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
{
if (! $metadata instanceof ClassMetadata) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/249',
'Passing an instance of %s to %s is deprecated, please pass a %s instance instead.',
get_class($metadata),
__METHOD__,
ClassMetadata::class
);
}
$this->reverseEngineerMappingFromDatabase();
if (! isset($this->classToTableNames[$className])) {
throw new InvalidArgumentException('Unknown class ' . $className);
}
$tableName = $this->classToTableNames[$className];
$metadata->name = $className;
$metadata->table['name'] = $tableName;
$this->buildIndexes($metadata);
$this->buildFieldMappings($metadata);
$this->buildToOneAssociationMappings($metadata);
foreach ($this->manyToManyTables as $manyTable) {
foreach ($manyTable->getForeignKeys() as $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) {
continue;
}
$myFk = $foreignKey;
$otherFk = null;
foreach ($manyTable->getForeignKeys() as $foreignKey) {
if ($foreignKey !== $myFk) {
$otherFk = $foreignKey;
break;
}
}
if (! $otherFk) {
// the definition of this many to many table does not contain
// enough foreign key information to continue reverse engineering.
continue;
}
$localColumn = current($myFk->getLocalColumns());
$associationMapping = [];
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
if (current($manyTable->getColumns())->getName() === $localColumn) {
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true);
$associationMapping['joinTable'] = [
'name' => strtolower($manyTable->getName()),
'joinColumns' => [],
'inverseJoinColumns' => [],
];
$fkCols = $myFk->getForeignColumns();
$cols = $myFk->getLocalColumns();
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['joinColumns'][] = [
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
];
}
$fkCols = $otherFk->getForeignColumns();
$cols = $otherFk->getLocalColumns();
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['inverseJoinColumns'][] = [
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
];
}
} else {
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true);
}
$metadata->mapManyToMany($associationMapping);
break;
}
}
}
/** @throws MappingException */
private function reverseEngineerMappingFromDatabase(): void
{
if ($this->tables !== null) {
return;
}
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($this->sm->listTables() as $table) {
$tableName = $table->getName();
$foreignKeys = $table->getForeignKeys();
$allForeignKeyColumns = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
$primaryKey = $table->getPrimaryKey();
if ($primaryKey === null) {
throw new MappingException(
'Table ' . $tableName . ' has no primary key. Doctrine does not ' .
"support reverse engineering from tables that don't have a primary key."
);
}
$pkColumns = $primaryKey->getColumns();
sort($pkColumns);
sort($allForeignKeyColumns);
if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) {
$this->manyToManyTables[$tableName] = $table;
} else {
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = $this->getClassNameForTable($tableName);
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
}
}
/**
* Build indexes from a class metadata.
*/
private function buildIndexes(ClassMetadataInfo $metadata): void
{
$tableName = $metadata->table['name'];
$indexes = $this->tables[$tableName]->getIndexes();
foreach ($indexes as $index) {
if ($index->isPrimary()) {
continue;
}
$indexName = $index->getName();
$indexColumns = $index->getColumns();
$constraintType = $index->isUnique()
? 'uniqueConstraints'
: 'indexes';
$metadata->table[$constraintType][$indexName]['columns'] = $indexColumns;
}
}
/**
* Build field mapping from class metadata.
*/
private function buildFieldMappings(ClassMetadataInfo $metadata): void
{
$tableName = $metadata->table['name'];
$columns = $this->tables[$tableName]->getColumns();
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
$allForeignKeys = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns());
}
$ids = [];
$fieldMappings = [];
foreach ($columns as $column) {
if (in_array($column->getName(), $allForeignKeys, true)) {
continue;
}
$fieldMapping = $this->buildFieldMapping($tableName, $column);
if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) {
$fieldMapping['id'] = true;
$ids[] = $fieldMapping;
}
$fieldMappings[] = $fieldMapping;
}
// We need to check for the columns here, because we might have associations as id as well.
if ($ids && count($primaryKeys) === 1) {
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
}
foreach ($fieldMappings as $fieldMapping) {
$metadata->mapField($fieldMapping);
}
}
/**
* Build field mapping from a schema column definition
*
* @return mixed[]
* @psalm-return array{
* fieldName: string,
* columnName: string,
* type: string,
* nullable: bool,
* options?: array{
* unsigned?: bool,
* fixed?: bool,
* comment?: string,
* default?: string
* },
* precision?: int,
* scale?: int,
* length?: int|null
* }
*/
private function buildFieldMapping(string $tableName, Column $column): array
{
$fieldMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false),
'columnName' => $column->getName(),
'type' => Type::getTypeRegistry()->lookupName($column->getType()),
'nullable' => ! $column->getNotnull(),
];
// Type specific elements
switch ($fieldMapping['type']) {
case self::ARRAY:
case Types::BLOB:
case Types::GUID:
case self::JSON_ARRAY:
case self::OBJECT:
case Types::SIMPLE_ARRAY:
case Types::STRING:
case Types::TEXT:
$fieldMapping['length'] = $column->getLength();
$fieldMapping['options']['fixed'] = $column->getFixed();
break;
case Types::DECIMAL:
case Types::FLOAT:
$fieldMapping['precision'] = $column->getPrecision();
$fieldMapping['scale'] = $column->getScale();
break;
case Types::INTEGER:
case Types::BIGINT:
case Types::SMALLINT:
$fieldMapping['options']['unsigned'] = $column->getUnsigned();
break;
}
// Comment
$comment = $column->getComment();
if ($comment !== null) {
$fieldMapping['options']['comment'] = $comment;
}
// Default
$default = $column->getDefault();
if ($default !== null) {
$fieldMapping['options']['default'] = $default;
}
return $fieldMapping;
}
/**
* Build to one (one to one, many to one) association mapping from class metadata.
*
* @return void
*/
private function buildToOneAssociationMappings(ClassMetadataInfo $metadata)
{
assert($this->tables !== null);
$tableName = $metadata->table['name'];
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
foreach ($foreignKeys as $foreignKey) {
$foreignTableName = $foreignKey->getForeignTableName();
$fkColumns = $foreignKey->getLocalColumns();
$fkForeignColumns = $foreignKey->getForeignColumns();
$localColumn = current($fkColumns);
$associationMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true),
'targetEntity' => $this->getClassNameForTable($foreignTableName),
];
if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
$associationMapping['fieldName'] .= '2'; // "foo" => "foo2"
}
if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) {
$associationMapping['id'] = true;
}
for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) {
$associationMapping['joinColumns'][] = [
'name' => $fkColumns[$i],
'referencedColumnName' => $fkForeignColumns[$i],
];
}
// Here we need to check if $fkColumns are the same as $primaryKeys
if (! array_diff($fkColumns, $primaryKeys)) {
$metadata->mapOneToOne($associationMapping);
} else {
$metadata->mapManyToOne($associationMapping);
}
}
}
/**
* Retrieve schema table definition primary keys.
*
* @return string[]
*/
private function getTablePrimaryKeys(Table $table): array
{
try {
return $table->getPrimaryKey()->getColumns();
} catch (SchemaException $e) {
// Do nothing
}
return [];
}
/**
* Returns the mapped class name for a table if it exists. Otherwise return "classified" version.
*
* @psalm-return class-string
*/
private function getClassNameForTable(string $tableName): string
{
if (isset($this->classNamesForTables[$tableName])) {
return $this->namespace . $this->classNamesForTables[$tableName];
}
return $this->namespace . $this->inflector->classify(strtolower($tableName));
}
/**
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
*
* @param bool $fk Whether the column is a foreignkey or not.
*/
private function getFieldNameForColumn(
string $tableName,
string $columnName,
bool $fk = false
): string {
if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) {
return $this->fieldNamesForColumns[$tableName][$columnName];
}
$columnName = strtolower($columnName);
// Replace _id if it is a foreignkey column
if ($fk) {
$columnName = preg_replace('/_id$/', '', $columnName);
}
return $this->inflector->camelize($columnName);
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
/**
* {@inheritDoc}
*
* @deprecated this driver will be removed. Use Doctrine\Persistence\Mapping\Driver\MappingDriverChain instead
*/
class DriverChain extends MappingDriverChain
{
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Persistence\Mapping\Driver\FileLocator;
use Doctrine\Persistence\Mapping\Driver\PHPDriver as CommonPHPDriver;
/**
* {@inheritDoc}
*
* @deprecated this driver will be removed, use StaticPHPDriver or other mapping drivers instead.
*/
class PHPDriver extends CommonPHPDriver
{
/** @param string|string[]|FileLocator $locator */
public function __construct($locator)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/9277',
'PHPDriver is deprecated, use StaticPHPDriver or other mapping drivers instead.'
);
parent::__construct($locator);
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadata;
use ReflectionProperty;
/** @internal */
trait ReflectionBasedDriver
{
/** @var bool */
private $reportFieldsWhereDeclared = false;
/**
* Helps to deal with the case that reflection may report properties inherited from parent classes.
* When we know about the fields already (inheritance has been anticipated in ClassMetadataFactory),
* the driver must skip them.
*
* The declaring classes may mismatch when there are private properties: The same property name may be
* reported multiple times, but since it is private, it is in fact multiple (different) properties in
* different classes. In that case, report the property as an individual field. (ClassMetadataFactory will
* probably fail in that case, though.)
*/
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
{
if (! $this->reportFieldsWhereDeclared) {
return $metadata->isMappedSuperclass && ! $property->isPrivate()
|| $metadata->isInheritedField($property->name)
|| $metadata->isInheritedAssociation($property->name)
|| $metadata->isInheritedEmbeddedClass($property->name);
}
$declaringClass = $property->class;
if (
isset($metadata->fieldMappings[$property->name]['declared'])
&& $metadata->fieldMappings[$property->name]['declared'] === $declaringClass
) {
return true;
}
if (
isset($metadata->associationMappings[$property->name]['declared'])
&& $metadata->associationMappings[$property->name]['declared'] === $declaringClass
) {
return true;
}
return isset($metadata->embeddedClasses[$property->name]['declared'])
&& $metadata->embeddedClasses[$property->name]['declared'] === $declaringClass;
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use ArrayObject;
use Doctrine\ORM\Mapping\Annotation;
/**
* @template-extends ArrayObject<int, T>
* @template T of Annotation
*/
final class RepeatableAttributeCollection extends ArrayObject
{
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
/**
* XmlDriver that additionally looks for mapping information in a global file.
*/
class SimplifiedXmlDriver extends XmlDriver
{
public const DEFAULT_FILE_EXTENSION = '.orm.xml';
/**
* {@inheritDoc}
*/
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension, $isXsdValidationEnabled);
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
/**
* YamlDriver that additionally looks for mapping information in a global file.
*
* @deprecated This class is being removed from the ORM and won't have any replacement
*/
class SimplifiedYamlDriver extends YamlDriver
{
public const DEFAULT_FILE_EXTENSION = '.orm.yml';
/**
* {@inheritDoc}
*/
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension);
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver as CommonStaticPHPDriver;
/**
* {@inheritDoc}
*
* @deprecated this driver will be removed. Use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver instead
*/
class StaticPHPDriver extends CommonStaticPHPDriver
{
}
File diff suppressed because it is too large Load Diff
+937
View File
@@ -0,0 +1,937 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\FileDriver;
use InvalidArgumentException;
use LogicException;
use Symfony\Component\Yaml\Yaml;
use function array_map;
use function class_exists;
use function constant;
use function defined;
use function explode;
use function file_get_contents;
use function is_array;
use function is_string;
use function sprintf;
use function strlen;
use function strtoupper;
use function substr;
/**
* The YamlDriver reads the mapping metadata from yaml schema files.
*
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
*/
class YamlDriver extends FileDriver
{
public const DEFAULT_FILE_EXTENSION = '.dcm.yml';
/**
* {@inheritDoc}
*/
public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8465',
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to attribute or XML driver.'
);
if (! class_exists(Yaml::class)) {
throw new LogicException(
'The YAML metadata driver cannot be enabled because the "symfony/yaml" library'
. ' is not installed. Please run "composer require symfony/yaml" or choose a different'
. ' metadata driver.'
);
}
parent::__construct($locator, $fileExtension);
}
/**
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
{
$element = $this->getElement($className);
if ($element['type'] === 'entity') {
if (isset($element['repositoryClass'])) {
$metadata->setCustomRepositoryClass($element['repositoryClass']);
}
if (isset($element['readOnly']) && $element['readOnly'] === true) {
$metadata->markReadOnly();
}
} elseif ($element['type'] === 'mappedSuperclass') {
$metadata->setCustomRepositoryClass(
$element['repositoryClass'] ?? null
);
$metadata->isMappedSuperclass = true;
} elseif ($element['type'] === 'embeddable') {
$metadata->isEmbeddedClass = true;
} else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
}
// Evaluate root level properties
$primaryTable = [];
if (isset($element['table'])) {
$primaryTable['name'] = $element['table'];
}
if (isset($element['schema'])) {
$primaryTable['schema'] = $element['schema'];
}
// Evaluate second level cache
if (isset($element['cache'])) {
$metadata->enableCache($this->cacheToArray($element['cache']));
}
$metadata->setPrimaryTable($primaryTable);
// Evaluate named queries
if (isset($element['namedQueries'])) {
foreach ($element['namedQueries'] as $name => $queryMapping) {
if (is_string($queryMapping)) {
$queryMapping = ['query' => $queryMapping];
}
if (! isset($queryMapping['name'])) {
$queryMapping['name'] = $name;
}
$metadata->addNamedQuery($queryMapping);
}
}
// Evaluate named native queries
if (isset($element['namedNativeQueries'])) {
foreach ($element['namedNativeQueries'] as $name => $mappingElement) {
if (! isset($mappingElement['name'])) {
$mappingElement['name'] = $name;
}
$metadata->addNamedNativeQuery(
[
'name' => $mappingElement['name'],
'query' => $mappingElement['query'] ?? null,
'resultClass' => $mappingElement['resultClass'] ?? null,
'resultSetMapping' => $mappingElement['resultSetMapping'] ?? null,
]
);
}
}
// Evaluate sql result set mappings
if (isset($element['sqlResultSetMappings'])) {
foreach ($element['sqlResultSetMappings'] as $name => $resultSetMapping) {
if (! isset($resultSetMapping['name'])) {
$resultSetMapping['name'] = $name;
}
$entities = [];
$columns = [];
if (isset($resultSetMapping['entityResult'])) {
foreach ($resultSetMapping['entityResult'] as $entityResultElement) {
$entityResult = [
'fields' => [],
'entityClass' => $entityResultElement['entityClass'] ?? null,
'discriminatorColumn' => $entityResultElement['discriminatorColumn'] ?? null,
];
if (isset($entityResultElement['fieldResult'])) {
foreach ($entityResultElement['fieldResult'] as $fieldResultElement) {
$entityResult['fields'][] = [
'name' => $fieldResultElement['name'] ?? null,
'column' => $fieldResultElement['column'] ?? null,
];
}
}
$entities[] = $entityResult;
}
}
if (isset($resultSetMapping['columnResult'])) {
foreach ($resultSetMapping['columnResult'] as $columnResultAnnot) {
$columns[] = [
'name' => $columnResultAnnot['name'] ?? null,
];
}
}
$metadata->addSqlResultSetMapping(
[
'name' => $resultSetMapping['name'],
'entities' => $entities,
'columns' => $columns,
]
);
}
}
if (isset($element['inheritanceType'])) {
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType'])));
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate discriminatorColumn
if (isset($element['discriminatorColumn'])) {
$discrColumn = $element['discriminatorColumn'];
$metadata->setDiscriminatorColumn(
[
'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null,
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string) $discrColumn['columnDefinition'] : null,
'enumType' => isset($discrColumn['enumType']) ? (string) $discrColumn['enumType'] : null,
]
);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate discriminatorMap
if (isset($element['discriminatorMap'])) {
$metadata->setDiscriminatorMap($element['discriminatorMap']);
}
}
}
// Evaluate changeTrackingPolicy
if (isset($element['changeTrackingPolicy'])) {
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
. strtoupper($element['changeTrackingPolicy'])));
}
// Evaluate indexes
if (isset($element['indexes'])) {
foreach ($element['indexes'] as $name => $indexYml) {
if (! isset($indexYml['name'])) {
$indexYml['name'] = $name;
}
$index = [];
if (isset($indexYml['columns'])) {
if (is_string($indexYml['columns'])) {
$index['columns'] = array_map('trim', explode(',', $indexYml['columns']));
} else {
$index['columns'] = $indexYml['columns'];
}
}
if (isset($indexYml['fields'])) {
if (is_string($indexYml['fields'])) {
$index['fields'] = array_map('trim', explode(',', $indexYml['fields']));
} else {
$index['fields'] = $indexYml['fields'];
}
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
$indexYml['name']
);
}
if (isset($indexYml['flags'])) {
if (is_string($indexYml['flags'])) {
$index['flags'] = array_map('trim', explode(',', $indexYml['flags']));
} else {
$index['flags'] = $indexYml['flags'];
}
}
if (isset($indexYml['options'])) {
$index['options'] = $indexYml['options'];
}
$metadata->table['indexes'][$indexYml['name']] = $index;
}
}
// Evaluate uniqueConstraints
if (isset($element['uniqueConstraints'])) {
foreach ($element['uniqueConstraints'] as $name => $uniqueYml) {
if (! isset($uniqueYml['name'])) {
$uniqueYml['name'] = $name;
}
$unique = [];
if (isset($uniqueYml['columns'])) {
if (is_string($uniqueYml['columns'])) {
$unique['columns'] = array_map('trim', explode(',', $uniqueYml['columns']));
} else {
$unique['columns'] = $uniqueYml['columns'];
}
}
if (isset($uniqueYml['fields'])) {
if (is_string($uniqueYml['fields'])) {
$unique['fields'] = array_map('trim', explode(',', $uniqueYml['fields']));
} else {
$unique['fields'] = $uniqueYml['fields'];
}
}
if (
isset($unique['columns'], $unique['fields'])
|| (
! isset($unique['columns'])
&& ! isset($unique['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
$uniqueYml['name']
);
}
if (isset($uniqueYml['options'])) {
$unique['options'] = $uniqueYml['options'];
}
$metadata->table['uniqueConstraints'][$uniqueYml['name']] = $unique;
}
}
if (isset($element['options'])) {
$metadata->table['options'] = $element['options'];
}
$associationIds = [];
if (isset($element['id'])) {
// Evaluate identifier settings
foreach ($element['id'] as $name => $idElement) {
if (isset($idElement['associationKey']) && $idElement['associationKey'] === true) {
$associationIds[$name] = true;
continue;
}
$mapping = [
'id' => true,
'fieldName' => $name,
];
if (isset($idElement['type'])) {
$mapping['type'] = $idElement['type'];
}
if (isset($idElement['column'])) {
$mapping['columnName'] = $idElement['column'];
}
if (isset($idElement['length'])) {
$mapping['length'] = $idElement['length'];
}
if (isset($idElement['columnDefinition'])) {
$mapping['columnDefinition'] = $idElement['columnDefinition'];
}
if (isset($idElement['options'])) {
$mapping['options'] = $idElement['options'];
}
$metadata->mapField($mapping);
if (isset($idElement['generator'])) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
. strtoupper($idElement['generator']['strategy'])));
}
// Check for SequenceGenerator definition
if (isset($idElement['sequenceGenerator'])) {
$metadata->setSequenceGeneratorDefinition($idElement['sequenceGenerator']);
} elseif (isset($idElement['customIdGenerator'])) {
$customGenerator = $idElement['customIdGenerator'];
$metadata->setCustomGeneratorDefinition(
[
'class' => (string) $customGenerator['class'],
]
);
}
}
}
// Evaluate fields
if (isset($element['fields'])) {
foreach ($element['fields'] as $name => $fieldMapping) {
$mapping = $this->columnToArray($name, $fieldMapping);
if (isset($fieldMapping['id'])) {
$mapping['id'] = true;
if (isset($fieldMapping['generator']['strategy'])) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
. strtoupper($fieldMapping['generator']['strategy'])));
}
}
if (isset($mapping['version'])) {
$metadata->setVersionMapping($mapping);
unset($mapping['version']);
}
$metadata->mapField($mapping);
}
}
if (isset($element['embedded'])) {
foreach ($element['embedded'] as $name => $embeddedMapping) {
$mapping = [
'fieldName' => $name,
'class' => $embeddedMapping['class'] ?? null,
'columnPrefix' => $embeddedMapping['columnPrefix'] ?? null,
];
$metadata->mapEmbedded($mapping);
}
}
// Evaluate oneToOne relationships
if (isset($element['oneToOne'])) {
foreach ($element['oneToOne'] as $name => $oneToOneElement) {
$mapping = [
'fieldName' => $name,
'targetEntity' => $oneToOneElement['targetEntity'] ?? null,
];
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
if (isset($oneToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']);
}
if (isset($oneToOneElement['mappedBy'])) {
$mapping['mappedBy'] = $oneToOneElement['mappedBy'];
} else {
if (isset($oneToOneElement['inversedBy'])) {
$mapping['inversedBy'] = $oneToOneElement['inversedBy'];
}
$joinColumns = [];
if (isset($oneToOneElement['joinColumn'])) {
$joinColumns[] = $this->joinColumnToArray($oneToOneElement['joinColumn']);
} elseif (isset($oneToOneElement['joinColumns'])) {
foreach ($oneToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
$joinColumns[] = $this->joinColumnToArray($joinColumnElement);
}
}
$mapping['joinColumns'] = $joinColumns;
}
if (isset($oneToOneElement['cascade'])) {
$mapping['cascade'] = $oneToOneElement['cascade'];
}
if (isset($oneToOneElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool) $oneToOneElement['orphanRemoval'];
}
// Evaluate second level cache
if (isset($oneToOneElement['cache'])) {
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache']));
}
$metadata->mapOneToOne($mapping);
}
}
// Evaluate oneToMany relationships
if (isset($element['oneToMany'])) {
foreach ($element['oneToMany'] as $name => $oneToManyElement) {
$mapping = [
'fieldName' => $name,
'targetEntity' => $oneToManyElement['targetEntity'],
'mappedBy' => $oneToManyElement['mappedBy'],
];
if (isset($oneToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyElement['fetch']);
}
if (isset($oneToManyElement['cascade'])) {
$mapping['cascade'] = $oneToManyElement['cascade'];
}
if (isset($oneToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool) $oneToManyElement['orphanRemoval'];
}
if (isset($oneToManyElement['orderBy'])) {
$mapping['orderBy'] = $oneToManyElement['orderBy'];
}
if (isset($oneToManyElement['indexBy'])) {
$mapping['indexBy'] = $oneToManyElement['indexBy'];
}
// Evaluate second level cache
if (isset($oneToManyElement['cache'])) {
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache']));
}
$metadata->mapOneToMany($mapping);
}
}
// Evaluate manyToOne relationships
if (isset($element['manyToOne'])) {
foreach ($element['manyToOne'] as $name => $manyToOneElement) {
$mapping = [
'fieldName' => $name,
'targetEntity' => $manyToOneElement['targetEntity'] ?? null,
];
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
if (isset($manyToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']);
}
if (isset($manyToOneElement['inversedBy'])) {
$mapping['inversedBy'] = $manyToOneElement['inversedBy'];
}
$joinColumns = [];
if (isset($manyToOneElement['joinColumn'])) {
$joinColumns[] = $this->joinColumnToArray($manyToOneElement['joinColumn']);
} elseif (isset($manyToOneElement['joinColumns'])) {
foreach ($manyToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
$joinColumns[] = $this->joinColumnToArray($joinColumnElement);
}
}
$mapping['joinColumns'] = $joinColumns;
if (isset($manyToOneElement['cascade'])) {
$mapping['cascade'] = $manyToOneElement['cascade'];
}
// Evaluate second level cache
if (isset($manyToOneElement['cache'])) {
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache']));
}
$metadata->mapManyToOne($mapping);
}
}
// Evaluate manyToMany relationships
if (isset($element['manyToMany'])) {
foreach ($element['manyToMany'] as $name => $manyToManyElement) {
$mapping = [
'fieldName' => $name,
'targetEntity' => $manyToManyElement['targetEntity'],
];
if (isset($manyToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyElement['fetch']);
}
if (isset($manyToManyElement['mappedBy'])) {
$mapping['mappedBy'] = $manyToManyElement['mappedBy'];
} elseif (isset($manyToManyElement['joinTable'])) {
$joinTableElement = $manyToManyElement['joinTable'];
$joinTable = [
'name' => $joinTableElement['name'],
];
if (isset($joinTableElement['schema'])) {
$joinTable['schema'] = $joinTableElement['schema'];
}
if (isset($joinTableElement['joinColumns'])) {
foreach ($joinTableElement['joinColumns'] as $joinColumnName => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
}
if (isset($joinTableElement['inverseJoinColumns'])) {
foreach ($joinTableElement['inverseJoinColumns'] as $joinColumnName => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
}
$mapping['joinTable'] = $joinTable;
}
if (isset($manyToManyElement['inversedBy'])) {
$mapping['inversedBy'] = $manyToManyElement['inversedBy'];
}
if (isset($manyToManyElement['cascade'])) {
$mapping['cascade'] = $manyToManyElement['cascade'];
}
if (isset($manyToManyElement['orderBy'])) {
$mapping['orderBy'] = $manyToManyElement['orderBy'];
}
if (isset($manyToManyElement['indexBy'])) {
$mapping['indexBy'] = $manyToManyElement['indexBy'];
}
if (isset($manyToManyElement['orphanRemoval'])) {
$mapping['orphanRemoval'] = (bool) $manyToManyElement['orphanRemoval'];
}
// Evaluate second level cache
if (isset($manyToManyElement['cache'])) {
$mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache']));
}
$metadata->mapManyToMany($mapping);
}
}
// Evaluate associationOverride
if (isset($element['associationOverride']) && is_array($element['associationOverride'])) {
foreach ($element['associationOverride'] as $fieldName => $associationOverrideElement) {
$override = [];
// Check for joinColumn
if (isset($associationOverrideElement['joinColumn'])) {
$joinColumns = [];
foreach ($associationOverrideElement['joinColumn'] as $name => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinColumns[] = $this->joinColumnToArray($joinColumnElement);
}
$override['joinColumns'] = $joinColumns;
}
// Check for joinTable
if (isset($associationOverrideElement['joinTable'])) {
$joinTableElement = $associationOverrideElement['joinTable'];
$joinTable = [
'name' => $joinTableElement['name'],
];
if (isset($joinTableElement['schema'])) {
$joinTable['schema'] = $joinTableElement['schema'];
}
foreach ($joinTableElement['joinColumns'] as $name => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
foreach ($joinTableElement['inverseJoinColumns'] as $name => $joinColumnElement) {
if (! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
$override['joinTable'] = $joinTable;
}
// Check for inversedBy
if (isset($associationOverrideElement['inversedBy'])) {
$override['inversedBy'] = (string) $associationOverrideElement['inversedBy'];
}
// Check for `fetch`
if (isset($associationOverrideElement['fetch'])) {
$override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverrideElement['fetch']);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
// Evaluate associationOverride
if (isset($element['attributeOverride']) && is_array($element['attributeOverride'])) {
foreach ($element['attributeOverride'] as $fieldName => $attributeOverrideElement) {
$mapping = $this->columnToArray($fieldName, $attributeOverrideElement);
$metadata->setAttributeOverride($fieldName, $mapping);
}
}
// Evaluate lifeCycleCallbacks
if (isset($element['lifecycleCallbacks'])) {
foreach ($element['lifecycleCallbacks'] as $type => $methods) {
foreach ($methods as $method) {
$metadata->addLifecycleCallback($method, constant('Doctrine\ORM\Events::' . $type));
}
}
}
// Evaluate entityListeners
if (isset($element['entityListeners'])) {
foreach ($element['entityListeners'] as $className => $entityListener) {
// Evaluate the listener using naming convention.
if (empty($entityListener)) {
EntityListenerBuilder::bindEntityListener($metadata, $className);
continue;
}
foreach ($entityListener as $eventName => $callbackElement) {
foreach ($callbackElement as $methodName) {
$metadata->addEntityListener($eventName, $className, $methodName);
}
}
}
}
}
/**
* Constructs a joinColumn mapping array based on the information
* found in the given join column element.
*
* @psalm-param array{
* referencedColumnName?: mixed,
* name?: mixed,
* fieldName?: mixed,
* unique?: mixed,
* nullable?: mixed,
* onDelete?: mixed,
* columnDefinition?: mixed
* } $joinColumnElement The array join column element.
*
* @return mixed[] The mapping array.
* @psalm-return array{
* referencedColumnName?: string,
* name?: string,
* fieldName?: string,
* unique?: bool,
* nullable?: bool,
* onDelete?: mixed,
* columnDefinition?: mixed
* }
*/
private function joinColumnToArray(array $joinColumnElement): array
{
$joinColumn = [];
if (isset($joinColumnElement['referencedColumnName'])) {
$joinColumn['referencedColumnName'] = (string) $joinColumnElement['referencedColumnName'];
}
if (isset($joinColumnElement['name'])) {
$joinColumn['name'] = (string) $joinColumnElement['name'];
}
if (isset($joinColumnElement['fieldName'])) {
$joinColumn['fieldName'] = (string) $joinColumnElement['fieldName'];
}
if (isset($joinColumnElement['unique'])) {
$joinColumn['unique'] = (bool) $joinColumnElement['unique'];
}
if (isset($joinColumnElement['nullable'])) {
$joinColumn['nullable'] = (bool) $joinColumnElement['nullable'];
}
if (isset($joinColumnElement['onDelete'])) {
$joinColumn['onDelete'] = $joinColumnElement['onDelete'];
}
if (isset($joinColumnElement['columnDefinition'])) {
$joinColumn['columnDefinition'] = $joinColumnElement['columnDefinition'];
}
return $joinColumn;
}
/**
* Parses the given column as array.
*
* @psalm-param array{
* type?: string,
* column?: string,
* precision?: mixed,
* scale?: mixed,
* unique?: mixed,
* options?: mixed,
* nullable?: mixed,
* insertable?: mixed,
* updatable?: mixed,
* generated?: mixed,
* enumType?: class-string,
* version?: mixed,
* columnDefinition?: mixed
* }|null $column
*
* @return mixed[]
* @psalm-return array{
* fieldName: string,
* type?: string,
* columnName?: string,
* length?: int,
* precision?: mixed,
* scale?: mixed,
* unique?: bool,
* options?: mixed,
* nullable?: mixed,
* notInsertable?: mixed,
* notUpdatable?: mixed,
* generated?: mixed,
* enumType?: class-string,
* version?: mixed,
* columnDefinition?: mixed
* }
*/
private function columnToArray(string $fieldName, ?array $column): array
{
$mapping = ['fieldName' => $fieldName];
if (isset($column['type'])) {
$params = explode('(', $column['type']);
$column['type'] = $params[0];
$mapping['type'] = $column['type'];
if (isset($params[1])) {
$column['length'] = (int) substr($params[1], 0, strlen($params[1]) - 1);
}
}
if (isset($column['column'])) {
$mapping['columnName'] = $column['column'];
}
if (isset($column['length'])) {
$mapping['length'] = $column['length'];
}
if (isset($column['precision'])) {
$mapping['precision'] = $column['precision'];
}
if (isset($column['scale'])) {
$mapping['scale'] = $column['scale'];
}
if (isset($column['unique'])) {
$mapping['unique'] = (bool) $column['unique'];
}
if (isset($column['options'])) {
$mapping['options'] = $column['options'];
}
if (isset($column['nullable'])) {
$mapping['nullable'] = $column['nullable'];
}
if (isset($column['insertable']) && ! (bool) $column['insertable']) {
$mapping['notInsertable'] = true;
}
if (isset($column['updatable']) && ! (bool) $column['updatable']) {
$mapping['notUpdatable'] = true;
}
if (isset($column['generated'])) {
$mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $column['generated']);
}
if (isset($column['version']) && $column['version']) {
$mapping['version'] = $column['version'];
}
if (isset($column['columnDefinition'])) {
$mapping['columnDefinition'] = $column['columnDefinition'];
}
if (isset($column['enumType'])) {
$mapping['enumType'] = $column['enumType'];
}
return $mapping;
}
/**
* Parse / Normalize the cache configuration
*
* @param mixed[] $cacheMapping
* @psalm-param array{usage: string|null, region?: mixed} $cacheMapping
*
* @return mixed[]
* @psalm-return array{usage: int|null, region: string|null}
*/
private function cacheToArray(array $cacheMapping): array
{
$region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null;
$usage = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null;
if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) {
throw new InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage));
}
if ($usage) {
$usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
}
return [
'usage' => $usage,
'region' => $region,
];
}
/**
* {@inheritDoc}
*/
protected function loadMappingFile($file)
{
return Yaml::parse(file_get_contents($file));
}
}