welcome back to dyb-tech
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/** @deprecated Use {@see MappingAttribute} instead. */
|
||||
interface Annotation
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
|
||||
/**
|
||||
* ANSI compliant quote strategy, this strategy does not apply any quote.
|
||||
* To use this strategy all mapped tables and columns should be ANSI compliant.
|
||||
*/
|
||||
class AnsiQuoteStrategy implements QuoteStrategy
|
||||
{
|
||||
use SQLResultCasing;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $class->fieldMappings[$fieldName]['columnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTableName(ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $class->table['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $definition['sequenceName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $joinColumn['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $joinColumn['referencedColumnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $association['joinTable']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return $class->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null)
|
||||
{
|
||||
return $this->getSQLResultCasing($platform, $columnName . '_' . $counter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* This attribute is used to override association mapping of property for an entity relationship.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class AssociationOverride implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name of the relationship property whose mapping is being overridden.
|
||||
*
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The join column that is being mapped to the persistent attribute.
|
||||
*
|
||||
* @var array<JoinColumn>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $joinColumns;
|
||||
|
||||
/**
|
||||
* The join column that is being mapped to the persistent attribute.
|
||||
*
|
||||
* @var array<JoinColumn>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inverseJoinColumns;
|
||||
|
||||
/**
|
||||
* The join table that maps the relationship.
|
||||
*
|
||||
* @var JoinTable|null
|
||||
* @readonly
|
||||
*/
|
||||
public $joinTable;
|
||||
|
||||
/**
|
||||
* The name of the association-field on the inverse-side.
|
||||
*
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* The fetching strategy to use for the association.
|
||||
*
|
||||
* @var string|null
|
||||
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'|null
|
||||
* @readonly
|
||||
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
|
||||
*/
|
||||
public $fetch;
|
||||
|
||||
/**
|
||||
* @param JoinColumn|array<JoinColumn> $joinColumns
|
||||
* @param JoinColumn|array<JoinColumn> $inverseJoinColumns
|
||||
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
$joinColumns = null,
|
||||
$inverseJoinColumns = null,
|
||||
?JoinTable $joinTable = null,
|
||||
?string $inversedBy = null,
|
||||
?string $fetch = null
|
||||
) {
|
||||
if ($joinColumns instanceof JoinColumn) {
|
||||
$joinColumns = [$joinColumns];
|
||||
}
|
||||
|
||||
if ($inverseJoinColumns instanceof JoinColumn) {
|
||||
$inverseJoinColumns = [$inverseJoinColumns];
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->joinColumns = $joinColumns;
|
||||
$this->inverseJoinColumns = $inverseJoinColumns;
|
||||
$this->joinTable = $joinTable;
|
||||
$this->inversedBy = $inversedBy;
|
||||
$this->fetch = $fetch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
use function array_values;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* This attribute is used to override association mappings of relationship properties.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class AssociationOverrides implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* Mapping overrides of relationship properties.
|
||||
*
|
||||
* @var list<AssociationOverride>
|
||||
* @readonly
|
||||
*/
|
||||
public $overrides = [];
|
||||
|
||||
/** @param array<AssociationOverride>|AssociationOverride $overrides */
|
||||
public function __construct($overrides)
|
||||
{
|
||||
if (! is_array($overrides)) {
|
||||
$overrides = [$overrides];
|
||||
}
|
||||
|
||||
foreach ($overrides as $override) {
|
||||
if (! ($override instanceof AssociationOverride)) {
|
||||
throw MappingException::invalidOverrideType('AssociationOverride', $override);
|
||||
}
|
||||
}
|
||||
|
||||
$this->overrides = array_values($overrides);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* This attribute is used to override the mapping of a entity property.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class AttributeOverride implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name of the property whose mapping is being overridden.
|
||||
*
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The column definition.
|
||||
*
|
||||
* @var Column
|
||||
* @readonly
|
||||
*/
|
||||
public $column;
|
||||
|
||||
public function __construct(string $name, Column $column)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->column = $column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
use function array_values;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* This attribute is used to override the mapping of a entity property.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class AttributeOverrides implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* One or more field or property mapping overrides.
|
||||
*
|
||||
* @var list<AttributeOverride>
|
||||
* @readonly
|
||||
*/
|
||||
public $overrides = [];
|
||||
|
||||
/** @param array<AttributeOverride>|AttributeOverride $overrides */
|
||||
public function __construct($overrides)
|
||||
{
|
||||
if (! is_array($overrides)) {
|
||||
$overrides = [$overrides];
|
||||
}
|
||||
|
||||
foreach ($overrides as $override) {
|
||||
if (! ($override instanceof AttributeOverride)) {
|
||||
throw MappingException::invalidOverrideType('AttributeOverride', $override);
|
||||
}
|
||||
}
|
||||
|
||||
$this->overrides = array_values($overrides);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class AssociationBuilder
|
||||
{
|
||||
/** @var ClassMetadataBuilder */
|
||||
protected $builder;
|
||||
|
||||
/** @var mixed[] */
|
||||
protected $mapping;
|
||||
|
||||
/** @var mixed[]|null */
|
||||
protected $joinColumns;
|
||||
|
||||
/** @var int */
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @param mixed[] $mapping
|
||||
* @param int $type
|
||||
*/
|
||||
public function __construct(ClassMetadataBuilder $builder, array $mapping, $type)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->mapping = $mapping;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function mappedBy($fieldName)
|
||||
{
|
||||
$this->mapping['mappedBy'] = $fieldName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inversedBy($fieldName)
|
||||
{
|
||||
$this->mapping['inversedBy'] = $fieldName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function cascadeAll()
|
||||
{
|
||||
$this->mapping['cascade'] = ['ALL'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function cascadePersist()
|
||||
{
|
||||
$this->mapping['cascade'][] = 'persist';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function cascadeRemove()
|
||||
{
|
||||
$this->mapping['cascade'][] = 'remove';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function cascadeMerge()
|
||||
{
|
||||
$this->mapping['cascade'][] = 'merge';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function cascadeDetach()
|
||||
{
|
||||
$this->mapping['cascade'][] = 'detach';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function cascadeRefresh()
|
||||
{
|
||||
$this->mapping['cascade'][] = 'refresh';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function fetchExtraLazy()
|
||||
{
|
||||
$this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function fetchEager()
|
||||
{
|
||||
$this->mapping['fetch'] = ClassMetadata::FETCH_EAGER;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function fetchLazy()
|
||||
{
|
||||
$this->mapping['fetch'] = ClassMetadata::FETCH_LAZY;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Join Columns.
|
||||
*
|
||||
* @param string $columnName
|
||||
* @param string $referencedColumnName
|
||||
* @param bool $nullable
|
||||
* @param bool $unique
|
||||
* @param string|null $onDelete
|
||||
* @param string|null $columnDef
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null)
|
||||
{
|
||||
$this->joinColumns[] = [
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
'nullable' => $nullable,
|
||||
'unique' => $unique,
|
||||
'onDelete' => $onDelete,
|
||||
'columnDefinition' => $columnDef,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field as primary key.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function makePrimaryKey()
|
||||
{
|
||||
$this->mapping['id'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes orphan entities when detached from their parent.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function orphanRemoval()
|
||||
{
|
||||
$this->mapping['orphanRemoval'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadataBuilder
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$mapping = $this->mapping;
|
||||
if ($this->joinColumns) {
|
||||
$mapping['joinColumns'] = $this->joinColumns;
|
||||
}
|
||||
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
if ($this->type === ClassMetadata::MANY_TO_ONE) {
|
||||
$cm->mapManyToOne($mapping);
|
||||
} elseif ($this->type === ClassMetadata::ONE_TO_ONE) {
|
||||
$cm->mapOneToOne($mapping);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Type should be a ToOne Association here');
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* Builder Object for ClassMetadata
|
||||
*
|
||||
* @link www.doctrine-project.com
|
||||
*/
|
||||
class ClassMetadataBuilder
|
||||
{
|
||||
/** @var ClassMetadataInfo */
|
||||
private $cm;
|
||||
|
||||
public function __construct(ClassMetadataInfo $cm)
|
||||
{
|
||||
if (! $cm 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 ClassMetadata instance instead.',
|
||||
get_class($cm),
|
||||
__METHOD__,
|
||||
ClassMetadata::class
|
||||
);
|
||||
}
|
||||
|
||||
$this->cm = $cm;
|
||||
}
|
||||
|
||||
/** @return ClassMetadataInfo */
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the class as mapped superclass.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMappedSuperClass()
|
||||
{
|
||||
$this->cm->isMappedSuperclass = true;
|
||||
$this->cm->isEmbeddedClass = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the class as embeddable.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmbeddable()
|
||||
{
|
||||
$this->cm->isEmbeddedClass = true;
|
||||
$this->cm->isMappedSuperclass = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds and embedded class
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @param string $class
|
||||
* @param string|false|null $columnPrefix
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addEmbedded($fieldName, $class, $columnPrefix = null)
|
||||
{
|
||||
$this->cm->mapEmbedded(
|
||||
[
|
||||
'fieldName' => $fieldName,
|
||||
'class' => $class,
|
||||
'columnPrefix' => $columnPrefix,
|
||||
]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets custom Repository class name.
|
||||
*
|
||||
* @param string $repositoryClassName
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomRepositoryClass($repositoryClassName)
|
||||
{
|
||||
$this->cm->setCustomRepositoryClass($repositoryClassName);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks class read only.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setReadOnly()
|
||||
{
|
||||
$this->cm->markReadOnly();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the table name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTable($name)
|
||||
{
|
||||
$this->cm->setPrimaryTable(['name' => $name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Index.
|
||||
*
|
||||
* @param string $name
|
||||
* @psalm-param list<string> $columns
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addIndex(array $columns, $name)
|
||||
{
|
||||
if (! isset($this->cm->table['indexes'])) {
|
||||
$this->cm->table['indexes'] = [];
|
||||
}
|
||||
|
||||
$this->cm->table['indexes'][$name] = ['columns' => $columns];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Unique Constraint.
|
||||
*
|
||||
* @param string $name
|
||||
* @psalm-param list<string> $columns
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addUniqueConstraint(array $columns, $name)
|
||||
{
|
||||
if (! isset($this->cm->table['uniqueConstraints'])) {
|
||||
$this->cm->table['uniqueConstraints'] = [];
|
||||
}
|
||||
|
||||
$this->cm->table['uniqueConstraints'][$name] = ['columns' => $columns];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds named query.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $dqlQuery
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addNamedQuery($name, $dqlQuery)
|
||||
{
|
||||
$this->cm->addNamedQuery(
|
||||
[
|
||||
'name' => $name,
|
||||
'query' => $dqlQuery,
|
||||
]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets class as root of a joined table inheritance hierarchy.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setJoinedTableInheritance()
|
||||
{
|
||||
$this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets class as root of a single table inheritance hierarchy.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSingleTableInheritance()
|
||||
{
|
||||
$this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the discriminator column details.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param int $length
|
||||
* @psalm-param class-string<BackedEnum>|null $enumType
|
||||
* @psalm-param array<string, mixed> $options
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null, array $options = [])
|
||||
{
|
||||
$this->cm->setDiscriminatorColumn(
|
||||
[
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'length' => $length,
|
||||
'columnDefinition' => $columnDefinition,
|
||||
'enumType' => $enumType,
|
||||
'options' => $options,
|
||||
]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subclass to this inheritance hierarchy.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $class
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDiscriminatorMapClass($name, $class)
|
||||
{
|
||||
$this->cm->addDiscriminatorMapClass($name, $class);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets deferred explicit change tracking policy.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setChangeTrackingPolicyDeferredExplicit()
|
||||
{
|
||||
$this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets notify change tracking policy.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setChangeTrackingPolicyNotify()
|
||||
{
|
||||
$this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds lifecycle event.
|
||||
*
|
||||
* @param string $methodName
|
||||
* @param string $event
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addLifecycleEvent($methodName, $event)
|
||||
{
|
||||
$this->cm->addLifecycleCallback($methodName, $event);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Field.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @psalm-param array<string, mixed> $mapping
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addField($name, $type, array $mapping = [])
|
||||
{
|
||||
$mapping['fieldName'] = $name;
|
||||
$mapping['type'] = $type;
|
||||
|
||||
$this->cm->mapField($mapping);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a field builder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
*
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function createField($name, $type)
|
||||
{
|
||||
return new FieldBuilder(
|
||||
$this,
|
||||
[
|
||||
'fieldName' => $name,
|
||||
'type' => $type,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an embedded builder.
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @param string $class
|
||||
*
|
||||
* @return EmbeddedBuilder
|
||||
*/
|
||||
public function createEmbedded($fieldName, $class)
|
||||
{
|
||||
return new EmbeddedBuilder(
|
||||
$this,
|
||||
[
|
||||
'fieldName' => $fieldName,
|
||||
'class' => $class,
|
||||
'columnPrefix' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a simple many to one association, optionally with the inversed by field.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string|null $inversedBy
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addManyToOne($name, $targetEntity, $inversedBy = null)
|
||||
{
|
||||
$builder = $this->createManyToOne($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ManyToOne Association Builder.
|
||||
*
|
||||
* Note: This method does not add the association, you have to call build() on the AssociationBuilder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
*
|
||||
* @return AssociationBuilder
|
||||
*/
|
||||
public function createManyToOne($name, $targetEntity)
|
||||
{
|
||||
return new AssociationBuilder(
|
||||
$this,
|
||||
[
|
||||
'fieldName' => $name,
|
||||
'targetEntity' => $targetEntity,
|
||||
],
|
||||
ClassMetadata::MANY_TO_ONE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a OneToOne Association Builder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
*
|
||||
* @return AssociationBuilder
|
||||
*/
|
||||
public function createOneToOne($name, $targetEntity)
|
||||
{
|
||||
return new AssociationBuilder(
|
||||
$this,
|
||||
[
|
||||
'fieldName' => $name,
|
||||
'targetEntity' => $targetEntity,
|
||||
],
|
||||
ClassMetadata::ONE_TO_ONE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds simple inverse one-to-one association.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $mappedBy
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addInverseOneToOne($name, $targetEntity, $mappedBy)
|
||||
{
|
||||
$builder = $this->createOneToOne($name, $targetEntity);
|
||||
$builder->mappedBy($mappedBy);
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds simple owning one-to-one association.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string|null $inversedBy
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addOwningOneToOne($name, $targetEntity, $inversedBy = null)
|
||||
{
|
||||
$builder = $this->createOneToOne($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ManyToMany Association Builder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
*
|
||||
* @return ManyToManyAssociationBuilder
|
||||
*/
|
||||
public function createManyToMany($name, $targetEntity)
|
||||
{
|
||||
return new ManyToManyAssociationBuilder(
|
||||
$this,
|
||||
[
|
||||
'fieldName' => $name,
|
||||
'targetEntity' => $targetEntity,
|
||||
],
|
||||
ClassMetadata::MANY_TO_MANY
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a simple owning many to many association.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string|null $inversedBy
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addOwningManyToMany($name, $targetEntity, $inversedBy = null)
|
||||
{
|
||||
$builder = $this->createManyToMany($name, $targetEntity);
|
||||
|
||||
if ($inversedBy) {
|
||||
$builder->inversedBy($inversedBy);
|
||||
}
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a simple inverse many to many association.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $mappedBy
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addInverseManyToMany($name, $targetEntity, $mappedBy)
|
||||
{
|
||||
$builder = $this->createManyToMany($name, $targetEntity);
|
||||
$builder->mappedBy($mappedBy);
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a one to many association builder.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
*
|
||||
* @return OneToManyAssociationBuilder
|
||||
*/
|
||||
public function createOneToMany($name, $targetEntity)
|
||||
{
|
||||
return new OneToManyAssociationBuilder(
|
||||
$this,
|
||||
[
|
||||
'fieldName' => $name,
|
||||
'targetEntity' => $targetEntity,
|
||||
],
|
||||
ClassMetadata::ONE_TO_MANY
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds simple OneToMany association.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $targetEntity
|
||||
* @param string $mappedBy
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function addOneToMany($name, $targetEntity, $mappedBy)
|
||||
{
|
||||
$builder = $this->createOneToMany($name, $targetEntity);
|
||||
$builder->mappedBy($mappedBy);
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
/**
|
||||
* Embedded Builder
|
||||
*
|
||||
* @link www.doctrine-project.com
|
||||
*/
|
||||
class EmbeddedBuilder
|
||||
{
|
||||
/** @var ClassMetadataBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $mapping;
|
||||
|
||||
/** @param mixed[] $mapping */
|
||||
public function __construct(ClassMetadataBuilder $builder, array $mapping)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column prefix for all of the embedded columns.
|
||||
*
|
||||
* @param string $columnPrefix
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumnPrefix($columnPrefix)
|
||||
{
|
||||
$this->mapping['columnPrefix'] = $columnPrefix;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes this embeddable and attach it to the ClassMetadata.
|
||||
*
|
||||
* Without this call an EmbeddedBuilder has no effect on the ClassMetadata.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
|
||||
$cm->mapEmbedded($this->mapping);
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
|
||||
use function class_exists;
|
||||
use function get_class_methods;
|
||||
|
||||
/**
|
||||
* Builder for entity listeners.
|
||||
*/
|
||||
class EntityListenerBuilder
|
||||
{
|
||||
/** @var array<string,bool> Hash-map to handle event names. */
|
||||
private static $events = [
|
||||
Events::preRemove => true,
|
||||
Events::postRemove => true,
|
||||
Events::prePersist => true,
|
||||
Events::postPersist => true,
|
||||
Events::preUpdate => true,
|
||||
Events::postUpdate => true,
|
||||
Events::postLoad => true,
|
||||
Events::preFlush => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Lookup the entity class to find methods that match to event lifecycle names
|
||||
*
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param string $className The listener class name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws MappingException When the listener class not found.
|
||||
*/
|
||||
public static function bindEntityListener(ClassMetadata $metadata, $className)
|
||||
{
|
||||
$class = $metadata->fullyQualifiedClassName($className);
|
||||
|
||||
if (! class_exists($class)) {
|
||||
throw MappingException::entityListenerClassNotFound($class, $className);
|
||||
}
|
||||
|
||||
foreach (get_class_methods($class) as $method) {
|
||||
if (! isset(self::$events[$method])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metadata->addEntityListener($method, $class, $method);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
use function constant;
|
||||
|
||||
/**
|
||||
* Field Builder
|
||||
*
|
||||
* @link www.doctrine-project.com
|
||||
*/
|
||||
class FieldBuilder
|
||||
{
|
||||
/** @var ClassMetadataBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $mapping;
|
||||
|
||||
/** @var bool */
|
||||
private $version;
|
||||
|
||||
/** @var string */
|
||||
private $generatedValue;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $sequenceDef;
|
||||
|
||||
/** @var string|null */
|
||||
private $customIdGenerator;
|
||||
|
||||
/** @param mixed[] $mapping */
|
||||
public function __construct(ClassMetadataBuilder $builder, array $mapping)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets length.
|
||||
*
|
||||
* @param int $length
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function length($length)
|
||||
{
|
||||
$this->mapping['length'] = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets nullable.
|
||||
*
|
||||
* @param bool $flag
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function nullable($flag = true)
|
||||
{
|
||||
$this->mapping['nullable'] = (bool) $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Unique.
|
||||
*
|
||||
* @param bool $flag
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unique($flag = true)
|
||||
{
|
||||
$this->mapping['unique'] = (bool) $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets column name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function columnName($name)
|
||||
{
|
||||
$this->mapping['columnName'] = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Precision.
|
||||
*
|
||||
* @param int $p
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function precision($p)
|
||||
{
|
||||
$this->mapping['precision'] = $p;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets insertable.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function insertable(bool $flag = true): self
|
||||
{
|
||||
if (! $flag) {
|
||||
$this->mapping['notInsertable'] = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets updatable.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function updatable(bool $flag = true): self
|
||||
{
|
||||
if (! $flag) {
|
||||
$this->mapping['notUpdatable'] = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets scale.
|
||||
*
|
||||
* @param int $s
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function scale($s)
|
||||
{
|
||||
$this->mapping['scale'] = $s;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field as primary key.
|
||||
*
|
||||
* @deprecated Use makePrimaryKey() instead
|
||||
*
|
||||
* @return FieldBuilder
|
||||
*/
|
||||
public function isPrimaryKey()
|
||||
{
|
||||
return $this->makePrimaryKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field as primary key.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function makePrimaryKey()
|
||||
{
|
||||
$this->mapping['id'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function option($name, $value)
|
||||
{
|
||||
$this->mapping['options'][$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $strategy
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function generatedValue($strategy = 'AUTO')
|
||||
{
|
||||
$this->generatedValue = $strategy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field versioned.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function isVersionField()
|
||||
{
|
||||
$this->version = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Sequence Generator.
|
||||
*
|
||||
* @param string $sequenceName
|
||||
* @param int $allocationSize
|
||||
* @param int $initialValue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSequenceGenerator($sequenceName, $allocationSize = 1, $initialValue = 1)
|
||||
{
|
||||
$this->sequenceDef = [
|
||||
'sequenceName' => $sequenceName,
|
||||
'allocationSize' => $allocationSize,
|
||||
'initialValue' => $initialValue,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets column definition.
|
||||
*
|
||||
* @param string $def
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function columnDefinition($def)
|
||||
{
|
||||
$this->mapping['columnDefinition'] = $def;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the FQCN of the custom ID generator.
|
||||
* This class must extend \Doctrine\ORM\Id\AbstractIdGenerator.
|
||||
*
|
||||
* @param string $customIdGenerator
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomIdGenerator($customIdGenerator)
|
||||
{
|
||||
$this->customIdGenerator = (string) $customIdGenerator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes this field and attach it to the ClassMetadata.
|
||||
*
|
||||
* Without this call a FieldBuilder has no effect on the ClassMetadata.
|
||||
*
|
||||
* @return ClassMetadataBuilder
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
if ($this->generatedValue) {
|
||||
$cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue));
|
||||
}
|
||||
|
||||
if ($this->version) {
|
||||
$cm->setVersionMapping($this->mapping);
|
||||
}
|
||||
|
||||
$cm->mapField($this->mapping);
|
||||
if ($this->sequenceDef) {
|
||||
$cm->setSequenceGeneratorDefinition($this->sequenceDef);
|
||||
}
|
||||
|
||||
if ($this->customIdGenerator) {
|
||||
$cm->setCustomGeneratorDefinition(['class' => $this->customIdGenerator]);
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
/**
|
||||
* ManyToMany Association Builder
|
||||
*
|
||||
* @link www.doctrine-project.com
|
||||
*/
|
||||
class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
|
||||
{
|
||||
/** @var string|null */
|
||||
private $joinTableName;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $inverseJoinColumns = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setJoinTable($name)
|
||||
{
|
||||
$this->joinTableName = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Inverse Join Columns.
|
||||
*
|
||||
* @param string $columnName
|
||||
* @param string $referencedColumnName
|
||||
* @param bool $nullable
|
||||
* @param bool $unique
|
||||
* @param string|null $onDelete
|
||||
* @param string|null $columnDef
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addInverseJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null)
|
||||
{
|
||||
$this->inverseJoinColumns[] = [
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
'nullable' => $nullable,
|
||||
'unique' => $unique,
|
||||
'onDelete' => $onDelete,
|
||||
'columnDefinition' => $columnDef,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return ClassMetadataBuilder */
|
||||
public function build()
|
||||
{
|
||||
$mapping = $this->mapping;
|
||||
$mapping['joinTable'] = [];
|
||||
if ($this->joinColumns) {
|
||||
$mapping['joinTable']['joinColumns'] = $this->joinColumns;
|
||||
}
|
||||
|
||||
if ($this->inverseJoinColumns) {
|
||||
$mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns;
|
||||
}
|
||||
|
||||
if ($this->joinTableName) {
|
||||
$mapping['joinTable']['name'] = $this->joinTableName;
|
||||
}
|
||||
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
$cm->mapManyToMany($mapping);
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Builder;
|
||||
|
||||
/**
|
||||
* OneToMany Association Builder
|
||||
*
|
||||
* @link www.doctrine-project.com
|
||||
*/
|
||||
class OneToManyAssociationBuilder extends AssociationBuilder
|
||||
{
|
||||
/**
|
||||
* @psalm-param array<string, string> $fieldNames
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOrderBy(array $fieldNames)
|
||||
{
|
||||
$this->mapping['orderBy'] = $fieldNames;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setIndexBy($fieldName)
|
||||
{
|
||||
$this->mapping['indexBy'] = $fieldName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return ClassMetadataBuilder */
|
||||
public function build()
|
||||
{
|
||||
$mapping = $this->mapping;
|
||||
if ($this->joinColumns) {
|
||||
$mapping['joinColumns'] = $this->joinColumns;
|
||||
}
|
||||
|
||||
$cm = $this->builder->getClassMetadata();
|
||||
$cm->mapOneToMany($mapping);
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* Caching to an entity or a collection.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target({"CLASS","PROPERTY"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
|
||||
final class Cache implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The concurrency strategy.
|
||||
*
|
||||
* @Enum({"READ_ONLY", "NONSTRICT_READ_WRITE", "READ_WRITE"})
|
||||
* @var string
|
||||
* @psalm-var 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE'
|
||||
*/
|
||||
public $usage = 'READ_ONLY';
|
||||
|
||||
/** @var string|null Cache region name. */
|
||||
public $region;
|
||||
|
||||
/** @psalm-param 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE' $usage */
|
||||
public function __construct(string $usage = 'READ_ONLY', ?string $region = null)
|
||||
{
|
||||
$this->usage = $usage;
|
||||
$this->region = $region;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use ReflectionProperty;
|
||||
|
||||
final class ChainTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
* @var TypedFieldMapper[] $typedFieldMappers
|
||||
*/
|
||||
private array $typedFieldMappers;
|
||||
|
||||
public function __construct(TypedFieldMapper ...$typedFieldMappers)
|
||||
{
|
||||
$this->typedFieldMappers = $typedFieldMappers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
|
||||
{
|
||||
foreach ($this->typedFieldMappers as $typedFieldMapper) {
|
||||
$mapping = $typedFieldMapper->validateAndComplete($mapping, $field);
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class ChangeTrackingPolicy implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The change tracking policy.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY'
|
||||
* @readonly
|
||||
* @Enum({"DEFERRED_IMPLICIT", "DEFERRED_EXPLICIT", "NOTIFY"})
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @psalm-param 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY' $value */
|
||||
public function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @todo remove or rename ClassMetadataInfo to ClassMetadata
|
||||
* @template-covariant T of object
|
||||
* @template-extends ClassMetadataInfo<T>
|
||||
* @psalm-type FieldMapping = array{
|
||||
* type: string,
|
||||
* fieldName: string,
|
||||
* columnName: string,
|
||||
* length?: int,
|
||||
* id?: bool,
|
||||
* nullable?: bool,
|
||||
* notInsertable?: bool,
|
||||
* notUpdatable?: bool,
|
||||
* generated?: int,
|
||||
* enumType?: class-string<BackedEnum>,
|
||||
* columnDefinition?: string,
|
||||
* precision?: int,
|
||||
* scale?: int,
|
||||
* unique?: bool,
|
||||
* inherited?: class-string,
|
||||
* originalClass?: class-string,
|
||||
* originalField?: string,
|
||||
* quoted?: bool,
|
||||
* requireSQLConversion?: bool,
|
||||
* declared?: class-string,
|
||||
* declaredField?: string,
|
||||
* options?: array<string, mixed>,
|
||||
* version?: string,
|
||||
* default?: string|int,
|
||||
* }
|
||||
* @psalm-type JoinColumnData = array{
|
||||
* name: string,
|
||||
* referencedColumnName: string,
|
||||
* unique?: bool,
|
||||
* quoted?: bool,
|
||||
* fieldName?: string,
|
||||
* onDelete?: string,
|
||||
* columnDefinition?: string,
|
||||
* nullable?: bool,
|
||||
* }
|
||||
* @psalm-type AssociationMapping = array{
|
||||
* cache?: array,
|
||||
* cascade: array<string>,
|
||||
* declared?: class-string,
|
||||
* fetch: mixed,
|
||||
* fieldName: string,
|
||||
* id?: bool,
|
||||
* inherited?: class-string,
|
||||
* indexBy?: string,
|
||||
* inversedBy: string|null,
|
||||
* isCascadeRemove: bool,
|
||||
* isCascadePersist: bool,
|
||||
* isCascadeRefresh: bool,
|
||||
* isCascadeMerge: bool,
|
||||
* isCascadeDetach: bool,
|
||||
* isOnDeleteCascade?: bool,
|
||||
* isOwningSide: bool,
|
||||
* joinColumns?: array<JoinColumnData>,
|
||||
* joinColumnFieldNames?: array<string, string>,
|
||||
* joinTable?: array,
|
||||
* joinTableColumns?: list<mixed>,
|
||||
* mappedBy: string|null,
|
||||
* orderBy?: array,
|
||||
* originalClass?: class-string,
|
||||
* originalField?: string,
|
||||
* orphanRemoval?: bool,
|
||||
* relationToSourceKeyColumns?: array,
|
||||
* relationToTargetKeyColumns?: array,
|
||||
* sourceEntity: class-string,
|
||||
* sourceToTargetKeyColumns?: array<string, string>,
|
||||
* targetEntity: class-string,
|
||||
* targetToSourceKeyColumns?: array<string, string>,
|
||||
* type: int,
|
||||
* unique?: bool,
|
||||
* }
|
||||
* @psalm-type DiscriminatorColumnMapping = array{
|
||||
* name: string,
|
||||
* fieldName: string,
|
||||
* type: string,
|
||||
* length?: int,
|
||||
* columnDefinition?: string|null,
|
||||
* enumType?: class-string<BackedEnum>|null,
|
||||
* options?: array<string, mixed>,
|
||||
* }
|
||||
* @psalm-type EmbeddedClassMapping = array{
|
||||
* class: class-string,
|
||||
* columnPrefix: string|null,
|
||||
* declaredField: string|null,
|
||||
* originalField: string|null,
|
||||
* inherited?: class-string,
|
||||
* declared?: class-string,
|
||||
* }
|
||||
*/
|
||||
class ClassMetadata extends ClassMetadataInfo
|
||||
{
|
||||
/**
|
||||
* Repeating the ClassMetadataInfo constructor to infer correctly the template with PHPStan
|
||||
*
|
||||
* @see https://github.com/doctrine/orm/issues/8709
|
||||
*
|
||||
* @param string $entityName The name of the entity class the new instance is used for.
|
||||
* @psalm-param class-string<T> $entityName
|
||||
*/
|
||||
public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null)
|
||||
{
|
||||
parent::__construct($entityName, $namingStrategy, $typedFieldMapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,876 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Platforms;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLServerPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
|
||||
use Doctrine\ORM\Id\IdentityGenerator;
|
||||
use Doctrine\ORM\Id\SequenceGenerator;
|
||||
use Doctrine\ORM\Id\UuidGenerator;
|
||||
use Doctrine\ORM\Mapping\Exception\CannotGenerateIds;
|
||||
use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator;
|
||||
use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
|
||||
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Persistence\Mapping\ReflectionService;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function end;
|
||||
use function explode;
|
||||
use function get_class;
|
||||
use function in_array;
|
||||
use function is_a;
|
||||
use function is_subclass_of;
|
||||
use function str_contains;
|
||||
use function strlen;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
|
||||
* metadata mapping information of a class which describes how a class should be mapped
|
||||
* to a relational database.
|
||||
*
|
||||
* @extends AbstractClassMetadataFactory<ClassMetadata>
|
||||
* @psalm-import-type AssociationMapping from ClassMetadata
|
||||
* @psalm-import-type EmbeddedClassMapping from ClassMetadata
|
||||
* @psalm-import-type FieldMapping from ClassMetadata
|
||||
*/
|
||||
class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
{
|
||||
/** @var EntityManagerInterface|null */
|
||||
private $em;
|
||||
|
||||
/** @var AbstractPlatform|null */
|
||||
private $targetPlatform;
|
||||
|
||||
/** @var MappingDriver */
|
||||
private $driver;
|
||||
|
||||
/** @var EventManager */
|
||||
private $evm;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $embeddablesActiveNesting = [];
|
||||
|
||||
private const NON_IDENTITY_DEFAULT_STRATEGY = [
|
||||
'Doctrine\DBAL\Platforms\PostgreSqlPlatform' => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
|
||||
Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
|
||||
Platforms\PostgreSQLPlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
|
||||
];
|
||||
|
||||
/** @return void */
|
||||
public function setEntityManager(EntityManagerInterface $em)
|
||||
{
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
|
||||
$this->evm = $this->em->getEventManager();
|
||||
$this->initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function onNotFoundMetadata($className)
|
||||
{
|
||||
if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);
|
||||
|
||||
$this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
|
||||
$classMetadata = $eventArgs->getFoundMetadata();
|
||||
assert($classMetadata instanceof ClassMetadata || $classMetadata === null);
|
||||
|
||||
return $classMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents)
|
||||
{
|
||||
if ($parent) {
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
$this->addInheritedFields($class, $parent);
|
||||
$this->addInheritedRelations($class, $parent);
|
||||
$this->addInheritedEmbeddedClasses($class, $parent);
|
||||
$class->setIdentifier($parent->identifier);
|
||||
$class->setVersioned($parent->isVersioned);
|
||||
$class->setVersionField($parent->versionField);
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
$class->addSubClasses($parent->subClasses);
|
||||
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
|
||||
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
|
||||
|
||||
if (! empty($parent->customGeneratorDefinition)) {
|
||||
$class->setCustomGeneratorDefinition($parent->customGeneratorDefinition);
|
||||
}
|
||||
|
||||
if ($parent->isMappedSuperclass) {
|
||||
$class->setCustomRepositoryClass($parent->customRepositoryClassName);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke driver
|
||||
try {
|
||||
$this->driver->loadMetadataForClass($class->getName(), $class);
|
||||
} catch (ReflectionException $e) {
|
||||
throw MappingException::reflectionFailure($class->getName(), $e);
|
||||
}
|
||||
|
||||
// If this class has a parent the id generator strategy is inherited.
|
||||
// However this is only true if the hierarchy of parents contains the root entity,
|
||||
// if it consists of mapped superclasses these don't necessarily include the id field.
|
||||
if ($parent && $rootEntityFound) {
|
||||
$this->inheritIdGeneratorMapping($class, $parent);
|
||||
} else {
|
||||
$this->completeIdGeneratorMapping($class);
|
||||
}
|
||||
|
||||
if (! $class->isMappedSuperclass) {
|
||||
if ($rootEntityFound && $class->isInheritanceTypeNone()) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10431',
|
||||
"Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared. This is a misconfiguration and will be an error in Doctrine ORM 3.0.",
|
||||
$class->name,
|
||||
end($nonSuperclassParents)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($class->embeddedClasses as $property => $embeddableClass) {
|
||||
if (isset($embeddableClass['inherited'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($this->embeddablesActiveNesting[$embeddableClass['class']])) {
|
||||
throw MappingException::infiniteEmbeddableNesting($class->name, $property);
|
||||
}
|
||||
|
||||
$this->embeddablesActiveNesting[$class->name] = true;
|
||||
|
||||
$embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
|
||||
|
||||
if ($embeddableMetadata->isEmbeddedClass) {
|
||||
$this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property);
|
||||
}
|
||||
|
||||
$identifier = $embeddableMetadata->getIdentifier();
|
||||
|
||||
if (! empty($identifier)) {
|
||||
$this->inheritIdGeneratorMapping($class, $embeddableMetadata);
|
||||
}
|
||||
|
||||
$class->inlineEmbeddable($property, $embeddableMetadata);
|
||||
|
||||
unset($this->embeddablesActiveNesting[$class->name]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent) {
|
||||
if ($parent->isInheritanceTypeSingleTable()) {
|
||||
$class->setPrimaryTable($parent->table);
|
||||
}
|
||||
|
||||
$this->addInheritedIndexes($class, $parent);
|
||||
|
||||
if ($parent->cache) {
|
||||
$class->cache = $parent->cache;
|
||||
}
|
||||
|
||||
if ($parent->containsForeignIdentifier) {
|
||||
$class->containsForeignIdentifier = true;
|
||||
}
|
||||
|
||||
if ($parent->containsEnumIdentifier) {
|
||||
$class->containsEnumIdentifier = true;
|
||||
}
|
||||
|
||||
if (! empty($parent->namedQueries)) {
|
||||
$this->addInheritedNamedQueries($class, $parent);
|
||||
}
|
||||
|
||||
if (! empty($parent->namedNativeQueries)) {
|
||||
$this->addInheritedNamedNativeQueries($class, $parent);
|
||||
}
|
||||
|
||||
if (! empty($parent->sqlResultSetMappings)) {
|
||||
$this->addInheritedSqlResultSetMappings($class, $parent);
|
||||
}
|
||||
|
||||
if (! empty($parent->entityListeners) && empty($class->entityListeners)) {
|
||||
$class->entityListeners = $parent->entityListeners;
|
||||
}
|
||||
}
|
||||
|
||||
$class->setParentClasses($nonSuperclassParents);
|
||||
|
||||
if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) {
|
||||
$this->addDefaultDiscriminatorMap($class);
|
||||
}
|
||||
|
||||
// During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402.
|
||||
// So, we must not discover the missing subclasses before that.
|
||||
|
||||
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
|
||||
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
|
||||
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
}
|
||||
|
||||
$this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
|
||||
|
||||
if ($class->changeTrackingPolicy === ClassMetadata::CHANGETRACKING_NOTIFY) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8383',
|
||||
'NOTIFY Change Tracking policy used in "%s" is deprecated, use deferred explicit instead.',
|
||||
$class->name
|
||||
);
|
||||
}
|
||||
|
||||
$this->validateRuntimeMetadata($class, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate runtime metadata is correctly defined.
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param ClassMetadataInterface|null $parent
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws MappingException
|
||||
*/
|
||||
protected function validateRuntimeMetadata($class, $parent)
|
||||
{
|
||||
if (! $class->reflClass) {
|
||||
// only validate if there is a reflection class instance
|
||||
return;
|
||||
}
|
||||
|
||||
$class->validateIdentifier();
|
||||
$class->validateAssociations();
|
||||
$class->validateLifecycleCallbacks($this->getReflectionService());
|
||||
|
||||
// verify inheritance
|
||||
if (! $class->isMappedSuperclass && ! $class->isInheritanceTypeNone()) {
|
||||
if (! $parent) {
|
||||
if (count($class->discriminatorMap) === 0) {
|
||||
throw MappingException::missingDiscriminatorMap($class->name);
|
||||
}
|
||||
|
||||
if (! $class->discriminatorColumn) {
|
||||
throw MappingException::missingDiscriminatorColumn($class->name);
|
||||
}
|
||||
|
||||
foreach ($class->subClasses as $subClass) {
|
||||
if ((new ReflectionClass($subClass))->name !== $subClass) {
|
||||
throw MappingException::invalidClassInDiscriminatorMap($subClass, $class->name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert($parent instanceof ClassMetadataInfo); // https://github.com/doctrine/orm/issues/8746
|
||||
if (
|
||||
! $class->reflClass->isAbstract()
|
||||
&& ! in_array($class->name, $class->discriminatorMap, true)
|
||||
) {
|
||||
throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName);
|
||||
}
|
||||
}
|
||||
} elseif ($class->isMappedSuperclass && $class->name === $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
|
||||
// second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
|
||||
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function newClassMetadataInstance($className)
|
||||
{
|
||||
return new ClassMetadata(
|
||||
$className,
|
||||
$this->em->getConfiguration()->getNamingStrategy(),
|
||||
$this->em->getConfiguration()->getTypedFieldMapper()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a default discriminator map if no one is given
|
||||
*
|
||||
* If an entity is of any inheritance type and does not contain a
|
||||
* discriminator map, then the map is generated automatically. This process
|
||||
* is expensive computation wise.
|
||||
*
|
||||
* The automatically generated discriminator map contains the lowercase short name of
|
||||
* each class as key.
|
||||
*
|
||||
* @throws MappingException
|
||||
*/
|
||||
private function addDefaultDiscriminatorMap(ClassMetadata $class): void
|
||||
{
|
||||
$allClasses = $this->driver->getAllClassNames();
|
||||
$fqcn = $class->getName();
|
||||
$map = [$this->getShortName($class->name) => $fqcn];
|
||||
|
||||
$duplicates = [];
|
||||
foreach ($allClasses as $subClassCandidate) {
|
||||
if (is_subclass_of($subClassCandidate, $fqcn)) {
|
||||
$shortName = $this->getShortName($subClassCandidate);
|
||||
|
||||
if (isset($map[$shortName])) {
|
||||
$duplicates[] = $shortName;
|
||||
}
|
||||
|
||||
$map[$shortName] = $subClassCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
if ($duplicates) {
|
||||
throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
|
||||
}
|
||||
|
||||
$class->setDiscriminatorMap($map);
|
||||
}
|
||||
|
||||
private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void
|
||||
{
|
||||
// Only root classes in inheritance hierarchies need contain a discriminator map,
|
||||
// so skip for other classes.
|
||||
if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processedClasses = [$rootEntityClass->name => true];
|
||||
foreach ($rootEntityClass->subClasses as $knownSubClass) {
|
||||
$processedClasses[$knownSubClass] = true;
|
||||
}
|
||||
|
||||
foreach ($rootEntityClass->discriminatorMap as $declaredClassName) {
|
||||
// This fetches non-transient parent classes only
|
||||
$parentClasses = $this->getParentClasses($declaredClassName);
|
||||
|
||||
foreach ($parentClasses as $parentClass) {
|
||||
if (isset($processedClasses[$parentClass])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$processedClasses[$parentClass] = true;
|
||||
|
||||
// All non-abstract entity classes must be listed in the discriminator map, and
|
||||
// this will be validated/enforced at runtime (possibly at a later time, when the
|
||||
// subclass is loaded, but anyways). Also, subclasses is about entity classes only.
|
||||
// That means we can ignore non-abstract classes here. The (expensive) driver
|
||||
// check for mapped superclasses need only be run for abstract candidate classes.
|
||||
if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter)
|
||||
$rootEntityClass->addSubClass($parentClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param class-string $className */
|
||||
private function peekIfIsMappedSuperclass(string $className): bool
|
||||
{
|
||||
$reflService = $this->getReflectionService();
|
||||
$class = $this->newClassMetadataInstance($className);
|
||||
$this->initializeReflection($class, $reflService);
|
||||
|
||||
$this->driver->loadMetadataForClass($className, $class);
|
||||
|
||||
return $class->isMappedSuperclass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lower-case short name of a class.
|
||||
*
|
||||
* @psalm-param class-string $className
|
||||
*/
|
||||
private function getShortName(string $className): string
|
||||
{
|
||||
if (! str_contains($className, '\\')) {
|
||||
return strtolower($className);
|
||||
}
|
||||
|
||||
$parts = explode('\\', $className);
|
||||
|
||||
return strtolower(end($parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the `inherited` and `declared` values into mapping information for fields, associations
|
||||
* and embedded classes.
|
||||
*
|
||||
* @param AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping
|
||||
*/
|
||||
private function addMappingInheritanceInformation(array &$mapping, ClassMetadata $parentClass): void
|
||||
{
|
||||
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
|
||||
$mapping['inherited'] = $parentClass->name;
|
||||
}
|
||||
|
||||
if (! isset($mapping['declared'])) {
|
||||
$mapping['declared'] = $parentClass->name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inherited fields to the subclass mapping.
|
||||
*/
|
||||
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->fieldMappings as $mapping) {
|
||||
$this->addMappingInheritanceInformation($mapping, $parentClass);
|
||||
$subClass->addInheritedFieldMapping($mapping);
|
||||
}
|
||||
|
||||
foreach ($parentClass->reflFields as $name => $field) {
|
||||
$subClass->reflFields[$name] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inherited association mappings to the subclass mapping.
|
||||
*
|
||||
* @throws MappingException
|
||||
*/
|
||||
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->associationMappings as $field => $mapping) {
|
||||
$this->addMappingInheritanceInformation($mapping, $parentClass);
|
||||
// When the class inheriting the relation ($subClass) is the first entity class since the
|
||||
// relation has been defined in a mapped superclass (or in a chain
|
||||
// of mapped superclasses) above, then declare this current entity class as the source of
|
||||
// the relationship.
|
||||
// According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
|
||||
// this is the case <=> ! isset($mapping['inherited']).
|
||||
if (! isset($mapping['inherited'])) {
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
|
||||
$subClass->addInheritedAssociationMapping($mapping);
|
||||
}
|
||||
}
|
||||
|
||||
private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
|
||||
$this->addMappingInheritanceInformation($embeddedClass, $parentClass);
|
||||
$subClass->embeddedClasses[$field] = $embeddedClass;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds nested embedded classes metadata to a parent class.
|
||||
*
|
||||
* @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from.
|
||||
* @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to.
|
||||
* @param string $prefix Embedded classes' prefix to use for nested embedded classes field names.
|
||||
*/
|
||||
private function addNestedEmbeddedClasses(
|
||||
ClassMetadata $subClass,
|
||||
ClassMetadata $parentClass,
|
||||
string $prefix
|
||||
): void {
|
||||
foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
|
||||
if (isset($embeddableClass['inherited'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
|
||||
|
||||
$parentClass->mapEmbedded(
|
||||
[
|
||||
'fieldName' => $prefix . '.' . $property,
|
||||
'class' => $embeddableMetadata->name,
|
||||
'columnPrefix' => $embeddableClass['columnPrefix'],
|
||||
'declaredField' => $embeddableClass['declaredField']
|
||||
? $prefix . '.' . $embeddableClass['declaredField']
|
||||
: $prefix,
|
||||
'originalField' => $embeddableClass['originalField'] ?: $property,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the table indices from the parent class superclass to the child class
|
||||
*/
|
||||
private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
if (! $parentClass->isMappedSuperclass) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (['uniqueConstraints', 'indexes'] as $indexType) {
|
||||
if (isset($parentClass->table[$indexType])) {
|
||||
foreach ($parentClass->table[$indexType] as $indexName => $index) {
|
||||
if (isset($subClass->table[$indexType][$indexName])) {
|
||||
continue; // Let the inheriting table override indices
|
||||
}
|
||||
|
||||
$subClass->table[$indexType][$indexName] = $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inherited named queries to the subclass mapping.
|
||||
*/
|
||||
private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->namedQueries as $name => $query) {
|
||||
if (! isset($subClass->namedQueries[$name])) {
|
||||
$subClass->addNamedQuery(
|
||||
[
|
||||
'name' => $query['name'],
|
||||
'query' => $query['query'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inherited named native queries to the subclass mapping.
|
||||
*/
|
||||
private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->namedNativeQueries as $name => $query) {
|
||||
if (! isset($subClass->namedNativeQueries[$name])) {
|
||||
$subClass->addNamedNativeQuery(
|
||||
[
|
||||
'name' => $query['name'],
|
||||
'query' => $query['query'],
|
||||
'isSelfClass' => $query['isSelfClass'],
|
||||
'resultSetMapping' => $query['resultSetMapping'],
|
||||
'resultClass' => $query['isSelfClass'] ? $subClass->name : $query['resultClass'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inherited sql result set mappings to the subclass mapping.
|
||||
*/
|
||||
private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass): void
|
||||
{
|
||||
foreach ($parentClass->sqlResultSetMappings as $name => $mapping) {
|
||||
if (! isset($subClass->sqlResultSetMappings[$name])) {
|
||||
$entities = [];
|
||||
foreach ($mapping['entities'] as $entity) {
|
||||
$entities[] = [
|
||||
'fields' => $entity['fields'],
|
||||
'isSelfClass' => $entity['isSelfClass'],
|
||||
'discriminatorColumn' => $entity['discriminatorColumn'],
|
||||
'entityClass' => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'],
|
||||
];
|
||||
}
|
||||
|
||||
$subClass->addSqlResultSetMapping(
|
||||
[
|
||||
'name' => $mapping['name'],
|
||||
'columns' => $mapping['columns'],
|
||||
'entities' => $entities,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the ID generator mapping. If "auto" is specified we choose the generator
|
||||
* most appropriate for the targeted database platform.
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
private function completeIdGeneratorMapping(ClassMetadataInfo $class): void
|
||||
{
|
||||
$idGenType = $class->generatorType;
|
||||
if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) {
|
||||
$class->setIdGeneratorType($this->determineIdGeneratorStrategy($this->getTargetPlatform()));
|
||||
}
|
||||
|
||||
// Create & assign an appropriate ID generator instance
|
||||
switch ($class->generatorType) {
|
||||
case ClassMetadata::GENERATOR_TYPE_IDENTITY:
|
||||
$sequenceName = null;
|
||||
$fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null;
|
||||
$platform = $this->getTargetPlatform();
|
||||
|
||||
// Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
|
||||
/** @psalm-suppress UndefinedClass, InvalidClass */
|
||||
if (! $platform instanceof MySQLPlatform && ! $platform instanceof SqlitePlatform && ! $platform instanceof SQLServerPlatform && $platform->usesSequenceEmulatedIdentityColumns()) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8850',
|
||||
<<<'DEPRECATION'
|
||||
Context: Loading metadata for class %s
|
||||
Problem: Using identity columns emulated with a sequence is deprecated and will not be possible in Doctrine ORM 3.0.
|
||||
Solution: Use the SEQUENCE generator strategy instead.
|
||||
DEPRECATION
|
||||
,
|
||||
$class->name,
|
||||
get_class($this->getTargetPlatform())
|
||||
);
|
||||
$columnName = $class->getSingleIdentifierColumnName();
|
||||
$quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
|
||||
$sequencePrefix = $class->getSequencePrefix($this->getTargetPlatform());
|
||||
$sequenceName = $this->getTargetPlatform()->getIdentitySequenceName($sequencePrefix, $columnName);
|
||||
$definition = [
|
||||
'sequenceName' => $this->truncateSequenceName($sequenceName),
|
||||
];
|
||||
|
||||
if ($quoted) {
|
||||
$definition['quoted'] = true;
|
||||
}
|
||||
|
||||
$sequenceName = $this
|
||||
->em
|
||||
->getConfiguration()
|
||||
->getQuoteStrategy()
|
||||
->getSequenceName($definition, $class, $this->getTargetPlatform());
|
||||
}
|
||||
|
||||
$generator = $fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint'
|
||||
? new BigIntegerIdentityGenerator($sequenceName)
|
||||
: new IdentityGenerator($sequenceName);
|
||||
|
||||
$class->setIdGenerator($generator);
|
||||
|
||||
break;
|
||||
|
||||
case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
|
||||
// If there is no sequence definition yet, create a default definition
|
||||
$definition = $class->sequenceGeneratorDefinition;
|
||||
|
||||
if (! $definition) {
|
||||
$fieldName = $class->getSingleIdentifierFieldName();
|
||||
$sequenceName = $class->getSequenceName($this->getTargetPlatform());
|
||||
$quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
|
||||
|
||||
$definition = [
|
||||
'sequenceName' => $this->truncateSequenceName($sequenceName),
|
||||
'allocationSize' => 1,
|
||||
'initialValue' => 1,
|
||||
];
|
||||
|
||||
if ($quoted) {
|
||||
$definition['quoted'] = true;
|
||||
}
|
||||
|
||||
$class->setSequenceGeneratorDefinition($definition);
|
||||
}
|
||||
|
||||
$sequenceGenerator = new SequenceGenerator(
|
||||
$this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()),
|
||||
(int) $definition['allocationSize']
|
||||
);
|
||||
$class->setIdGenerator($sequenceGenerator);
|
||||
break;
|
||||
|
||||
case ClassMetadata::GENERATOR_TYPE_NONE:
|
||||
$class->setIdGenerator(new AssignedGenerator());
|
||||
break;
|
||||
|
||||
case ClassMetadata::GENERATOR_TYPE_UUID:
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/7312',
|
||||
'Mapping for %s: the "UUID" id generator strategy is deprecated with no replacement',
|
||||
$class->name
|
||||
);
|
||||
$class->setIdGenerator(new UuidGenerator());
|
||||
break;
|
||||
|
||||
case ClassMetadata::GENERATOR_TYPE_CUSTOM:
|
||||
$definition = $class->customGeneratorDefinition;
|
||||
if ($definition === null) {
|
||||
throw InvalidCustomGenerator::onClassNotConfigured();
|
||||
}
|
||||
|
||||
if (! class_exists($definition['class'])) {
|
||||
throw InvalidCustomGenerator::onMissingClass($definition);
|
||||
}
|
||||
|
||||
$class->setIdGenerator(new $definition['class']());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw UnknownGeneratorType::create($class->generatorType);
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-return ClassMetadata::GENERATOR_TYPE_* */
|
||||
private function determineIdGeneratorStrategy(AbstractPlatform $platform): int
|
||||
{
|
||||
assert($this->em !== null);
|
||||
foreach ($this->em->getConfiguration()->getIdentityGenerationPreferences() as $platformFamily => $strategy) {
|
||||
if (is_a($platform, $platformFamily)) {
|
||||
return $strategy;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::NON_IDENTITY_DEFAULT_STRATEGY as $platformFamily => $strategy) {
|
||||
if (is_a($platform, $platformFamily)) {
|
||||
if ($platform instanceof Platforms\PostgreSQLPlatform || is_a($platform, 'Doctrine\DBAL\Platforms\PostgreSqlPlatform')) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8893',
|
||||
<<<'DEPRECATION'
|
||||
Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY
|
||||
results in SERIAL, which is not recommended.
|
||||
Instead, configure identifier generation strategies explicitly through
|
||||
configuration.
|
||||
We currently recommend "SEQUENCE" for "%s", so you should use
|
||||
$configuration->setIdentityGenerationPreferences([
|
||||
"%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
|
||||
]);
|
||||
DEPRECATION
|
||||
,
|
||||
$platformFamily,
|
||||
$platformFamily
|
||||
);
|
||||
}
|
||||
|
||||
return $strategy;
|
||||
}
|
||||
}
|
||||
|
||||
if ($platform->supportsIdentityColumns()) {
|
||||
return ClassMetadata::GENERATOR_TYPE_IDENTITY;
|
||||
}
|
||||
|
||||
if ($platform->supportsSequences()) {
|
||||
return ClassMetadata::GENERATOR_TYPE_SEQUENCE;
|
||||
}
|
||||
|
||||
throw CannotGenerateIds::withPlatform($platform);
|
||||
}
|
||||
|
||||
private function truncateSequenceName(string $schemaElementName): string
|
||||
{
|
||||
$platform = $this->getTargetPlatform();
|
||||
if (! $platform instanceof Platforms\OraclePlatform && ! $platform instanceof Platforms\SQLAnywherePlatform) {
|
||||
return $schemaElementName;
|
||||
}
|
||||
|
||||
$maxIdentifierLength = $platform->getMaxIdentifierLength();
|
||||
|
||||
if (strlen($schemaElementName) > $maxIdentifierLength) {
|
||||
return substr($schemaElementName, 0, $maxIdentifierLength);
|
||||
}
|
||||
|
||||
return $schemaElementName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits the ID generator mapping from a parent class.
|
||||
*/
|
||||
private function inheritIdGeneratorMapping(ClassMetadataInfo $class, ClassMetadataInfo $parent): void
|
||||
{
|
||||
if ($parent->isIdGeneratorSequence()) {
|
||||
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
|
||||
}
|
||||
|
||||
if ($parent->generatorType) {
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
}
|
||||
|
||||
if ($parent->idGenerator) {
|
||||
$class->setIdGenerator($parent->idGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService)
|
||||
{
|
||||
assert($class instanceof ClassMetadata);
|
||||
$class->wakeupReflection($reflService);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService)
|
||||
{
|
||||
assert($class instanceof ClassMetadata);
|
||||
$class->initializeReflection($reflService);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in ORM 3.0.
|
||||
*
|
||||
* @return class-string
|
||||
*/
|
||||
protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
|
||||
{
|
||||
/** @psalm-var class-string */
|
||||
return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getDriver()
|
||||
{
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function isEntity(ClassMetadataInterface $class)
|
||||
{
|
||||
return ! $class->isMappedSuperclass;
|
||||
}
|
||||
|
||||
private function getTargetPlatform(): Platforms\AbstractPlatform
|
||||
{
|
||||
if (! $this->targetPlatform) {
|
||||
$this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
|
||||
}
|
||||
|
||||
return $this->targetPlatform;
|
||||
}
|
||||
}
|
||||
+3885
File diff suppressed because it is too large
Load Diff
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use BackedEnum;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Column implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
* @readonly
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* The precision for a decimal (exact numeric) column (Applies only for decimal column).
|
||||
*
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $precision = 0;
|
||||
|
||||
/**
|
||||
* The scale for a decimal (exact numeric) column (Applies only for decimal column).
|
||||
*
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $scale = 0;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $unique = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $nullable = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $insertable = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $updatable = true;
|
||||
|
||||
/**
|
||||
* @var class-string<BackedEnum>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $enumType = null;
|
||||
|
||||
/**
|
||||
* @var array<string,mixed>
|
||||
* @readonly
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columnDefinition;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
|
||||
* @Enum({"NEVER", "INSERT", "ALWAYS"})
|
||||
*/
|
||||
public $generated;
|
||||
|
||||
/**
|
||||
* @param class-string<BackedEnum>|null $enumType
|
||||
* @param array<string,mixed> $options
|
||||
* @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?string $type = null,
|
||||
?int $length = null,
|
||||
?int $precision = null,
|
||||
?int $scale = null,
|
||||
bool $unique = false,
|
||||
bool $nullable = false,
|
||||
bool $insertable = true,
|
||||
bool $updatable = true,
|
||||
?string $enumType = null,
|
||||
array $options = [],
|
||||
?string $columnDefinition = null,
|
||||
?string $generated = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->length = $length;
|
||||
$this->precision = $precision;
|
||||
$this->scale = $scale;
|
||||
$this->unique = $unique;
|
||||
$this->nullable = $nullable;
|
||||
$this->insertable = $insertable;
|
||||
$this->updatable = $updatable;
|
||||
$this->enumType = $enumType;
|
||||
$this->options = $options;
|
||||
$this->columnDefinition = $columnDefinition;
|
||||
$this->generated = $generated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* References name of a column in the SELECT clause of a SQL query.
|
||||
* Scalar result types can be included in the query result by specifying this annotation in the metadata.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class ColumnResult implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name of a column in the SELECT clause of a SQL query.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class CustomIdGenerator implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $class;
|
||||
|
||||
public function __construct(?string $class = null)
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function get_class;
|
||||
use function gettype;
|
||||
use function is_object;
|
||||
use function sprintf;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* The default DefaultEntityListener
|
||||
*/
|
||||
class DefaultEntityListenerResolver implements EntityListenerResolver
|
||||
{
|
||||
/** @psalm-var array<class-string, object> Map to store entity listener instances. */
|
||||
private $instances = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clear($className = null)
|
||||
{
|
||||
if ($className === null) {
|
||||
$this->instances = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$className = trim($className, '\\');
|
||||
if (isset($this->instances[$className])) {
|
||||
unset($this->instances[$className]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function register($object)
|
||||
{
|
||||
if (! is_object($object)) {
|
||||
throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
|
||||
}
|
||||
|
||||
$this->instances[get_class($object)] = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function resolve($className)
|
||||
{
|
||||
$className = trim($className, '\\');
|
||||
if (isset($this->instances[$className])) {
|
||||
return $this->instances[$className];
|
||||
}
|
||||
|
||||
return $this->instances[$className] = new $className();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use function str_contains;
|
||||
use function strrpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* The default NamingStrategy
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*/
|
||||
class DefaultNamingStrategy implements NamingStrategy
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function classToTableName($className)
|
||||
{
|
||||
if (str_contains($className, '\\')) {
|
||||
return substr($className, strrpos($className, '\\') + 1);
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function propertyToColumnName($propertyName, $className = null)
|
||||
{
|
||||
return $propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
|
||||
{
|
||||
return $propertyName . '_' . $embeddedColumnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function referenceColumnName()
|
||||
{
|
||||
return 'id';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @param class-string $className
|
||||
*/
|
||||
public function joinColumnName($propertyName, $className = null)
|
||||
{
|
||||
return $propertyName . '_' . $this->referenceColumnName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
|
||||
{
|
||||
return strtolower($this->classToTableName($sourceEntity) . '_' .
|
||||
$this->classToTableName($targetEntity));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function joinKeyColumnName($entityName, $referencedColumnName = null)
|
||||
{
|
||||
return strtolower($this->classToTableName($entityName) . '_' .
|
||||
($referencedColumnName ?: $this->referenceColumnName()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function is_numeric;
|
||||
use function preg_replace;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* A set of rules for determining the physical column, alias and table quotes
|
||||
*/
|
||||
class DefaultQuoteStrategy implements QuoteStrategy
|
||||
{
|
||||
use SQLResultCasing;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return isset($class->fieldMappings[$fieldName]['quoted'])
|
||||
? $platform->quoteIdentifier($class->fieldMappings[$fieldName]['columnName'])
|
||||
: $class->fieldMappings[$fieldName]['columnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @todo Table names should be computed in DBAL depending on the platform
|
||||
*/
|
||||
public function getTableName(ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
$tableName = $class->table['name'];
|
||||
|
||||
if (! empty($class->table['schema'])) {
|
||||
$tableName = $class->table['schema'] . '.' . $class->table['name'];
|
||||
|
||||
if (! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
|
||||
$tableName = $class->table['schema'] . '__' . $class->table['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return isset($class->table['quoted'])
|
||||
? $platform->quoteIdentifier($tableName)
|
||||
: $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return isset($definition['quoted'])
|
||||
? $platform->quoteIdentifier($definition['sequenceName'])
|
||||
: $definition['sequenceName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return isset($joinColumn['quoted'])
|
||||
? $platform->quoteIdentifier($joinColumn['name'])
|
||||
: $joinColumn['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
return isset($joinColumn['quoted'])
|
||||
? $platform->quoteIdentifier($joinColumn['referencedColumnName'])
|
||||
: $joinColumn['referencedColumnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
$schema = '';
|
||||
|
||||
if (isset($association['joinTable']['schema'])) {
|
||||
$schema = $association['joinTable']['schema'];
|
||||
$schema .= ! $platform->supportsSchemas() && $platform->canEmulateSchemas() ? '__' : '.';
|
||||
}
|
||||
|
||||
$tableName = $association['joinTable']['name'];
|
||||
|
||||
if (isset($association['joinTable']['quoted'])) {
|
||||
$tableName = $platform->quoteIdentifier($tableName);
|
||||
}
|
||||
|
||||
return $schema . $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform)
|
||||
{
|
||||
$quotedColumnNames = [];
|
||||
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
if (isset($class->fieldMappings[$fieldName])) {
|
||||
$quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Association defined as Id field
|
||||
$joinColumns = $class->associationMappings[$fieldName]['joinColumns'];
|
||||
$assocQuotedColumnNames = array_map(
|
||||
static function ($joinColumn) use ($platform) {
|
||||
return isset($joinColumn['quoted'])
|
||||
? $platform->quoteIdentifier($joinColumn['name'])
|
||||
: $joinColumn['name'];
|
||||
},
|
||||
$joinColumns
|
||||
);
|
||||
|
||||
$quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
|
||||
}
|
||||
|
||||
return $quotedColumnNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null)
|
||||
{
|
||||
// 1 ) Concatenate column name and counter
|
||||
// 2 ) Trim the column alias to the maximum identifier length of the platform.
|
||||
// If the alias is to long, characters are cut off from the beginning.
|
||||
// 3 ) Strip non alphanumeric characters
|
||||
// 4 ) Prefix with "_" if the result its numeric
|
||||
$columnName .= '_' . $counter;
|
||||
$columnName = substr($columnName, -$platform->getMaxIdentifierLength());
|
||||
$columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName);
|
||||
$columnName = is_numeric($columnName) ? '_' . $columnName : $columnName;
|
||||
|
||||
return $this->getSQLResultCasing($platform, $columnName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use ReflectionEnum;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function enum_exists;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */
|
||||
final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
|
||||
private $typedFieldMappings;
|
||||
|
||||
private const DEFAULT_TYPED_FIELD_MAPPINGS = [
|
||||
DateInterval::class => Types::DATEINTERVAL,
|
||||
DateTime::class => Types::DATETIME_MUTABLE,
|
||||
DateTimeImmutable::class => Types::DATETIME_IMMUTABLE,
|
||||
'array' => Types::JSON,
|
||||
'bool' => Types::BOOLEAN,
|
||||
'float' => Types::FLOAT,
|
||||
'int' => Types::INTEGER,
|
||||
'string' => Types::STRING,
|
||||
];
|
||||
|
||||
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
|
||||
public function __construct(array $typedFieldMappings = [])
|
||||
{
|
||||
$this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
|
||||
{
|
||||
$type = $field->getType();
|
||||
|
||||
if (
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
if (! $reflection->isBacked()) {
|
||||
throw MappingException::backedEnumTypeRequired(
|
||||
$field->class,
|
||||
$mapping['fieldName'],
|
||||
$mapping['enumType']
|
||||
);
|
||||
}
|
||||
|
||||
$type = $reflection->getBackingType();
|
||||
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
}
|
||||
|
||||
if (isset($this->typedFieldMappings[$type->getName()])) {
|
||||
$mapping['type'] = $this->typedFieldMappings[$type->getName()];
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class DiscriminatorColumn implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
* @readonly
|
||||
*/
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columnDefinition;
|
||||
|
||||
/**
|
||||
* @var class-string<\BackedEnum>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $enumType = null;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
* @readonly
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @param class-string<\BackedEnum>|null $enumType
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?string $type = null,
|
||||
?int $length = null,
|
||||
?string $columnDefinition = null,
|
||||
?string $enumType = null,
|
||||
array $options = []
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->length = $length;
|
||||
$this->columnDefinition = $columnDefinition;
|
||||
$this->enumType = $enumType;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class DiscriminatorMap implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var array<int|string, string>
|
||||
* @readonly
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @param array<int|string, string> $value */
|
||||
public function __construct(array $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
+1016
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Embeddable implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Embedded implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @var string|bool|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columnPrefix;
|
||||
|
||||
public function __construct(?string $class = null, $columnPrefix = null)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->columnPrefix = $columnPrefix;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
* @template T of object
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Entity implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var class-string<EntityRepository<T>>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $repositoryClass;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $readOnly = false;
|
||||
|
||||
/** @psalm-param class-string<EntityRepository<T>>|null $repositoryClass */
|
||||
public function __construct(?string $repositoryClass = null, bool $readOnly = false)
|
||||
{
|
||||
$this->repositoryClass = $repositoryClass;
|
||||
$this->readOnly = $readOnly;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* A resolver is used to instantiate an entity listener.
|
||||
*/
|
||||
interface EntityListenerResolver
|
||||
{
|
||||
/**
|
||||
* Clear all instances from the set, or a specific instance when given its identifier.
|
||||
*
|
||||
* @param string|null $className May be any arbitrary string. Name kept for BC only.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear($className = null);
|
||||
|
||||
/**
|
||||
* Returns a entity listener instance for the given identifier.
|
||||
*
|
||||
* @param string $className May be any arbitrary string. Name kept for BC only.
|
||||
*
|
||||
* @return object An entity listener
|
||||
*/
|
||||
public function resolve($className);
|
||||
|
||||
/**
|
||||
* Register a entity listener instance.
|
||||
*
|
||||
* @param object $object An entity listener
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($object);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* The EntityListeners attribute specifies the callback listener classes to be used for an entity or mapped superclass.
|
||||
* The EntityListeners attribute may be applied to an entity class or mapped superclass.
|
||||
*
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class EntityListeners implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* Specifies the names of the entity listeners.
|
||||
*
|
||||
* @var array<string>
|
||||
* @readonly
|
||||
*/
|
||||
public $value = [];
|
||||
|
||||
/** @param array<string> $value */
|
||||
public function __construct(array $value = [])
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* References an entity in the SELECT clause of a SQL query.
|
||||
* If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object.
|
||||
* This should include foreign key columns to related entities.
|
||||
* The results obtained when insufficient data is available are undefined.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class EntityResult implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The class of the result.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* Maps the columns specified in the SELECT list of the query to the properties or fields of the entity class.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\FieldResult>
|
||||
*/
|
||||
public $fields = [];
|
||||
|
||||
/**
|
||||
* Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $discriminatorColumn;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Exception;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
|
||||
use function get_debug_type;
|
||||
use function sprintf;
|
||||
|
||||
final class CannotGenerateIds extends ORMException
|
||||
{
|
||||
public static function withPlatform(AbstractPlatform $platform): self
|
||||
{
|
||||
return new self(sprintf(
|
||||
'Platform %s does not support generating identifiers',
|
||||
get_debug_type($platform)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Exception;
|
||||
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
|
||||
use function sprintf;
|
||||
use function var_export;
|
||||
|
||||
final class InvalidCustomGenerator extends ORMException
|
||||
{
|
||||
public static function onClassNotConfigured(): self
|
||||
{
|
||||
return new self('Cannot instantiate custom generator, no class has been defined');
|
||||
}
|
||||
|
||||
/** @param mixed[] $definition */
|
||||
public static function onMissingClass(array $definition): self
|
||||
{
|
||||
return new self(sprintf(
|
||||
'Cannot instantiate custom generator : %s',
|
||||
var_export($definition, true)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Exception;
|
||||
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
|
||||
final class UnknownGeneratorType extends ORMException
|
||||
{
|
||||
public static function create(int $generatorType): self
|
||||
{
|
||||
return new self('Unknown generator type: ' . $generatorType);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* Is used to map the columns specified in the SELECT list of the query to the properties or fields of the entity class.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class FieldResult implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* Name of the column in the SELECT clause.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Name of the persistent field or property of the class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $column;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class GeneratedValue implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The type of ID generator.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM'
|
||||
* @readonly
|
||||
* @Enum({"AUTO", "SEQUENCE", "TABLE", "IDENTITY", "NONE", "UUID", "CUSTOM"})
|
||||
*/
|
||||
public $strategy = 'AUTO';
|
||||
|
||||
/** @psalm-param 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM' $strategy */
|
||||
public function __construct(string $strategy = 'AUTO')
|
||||
{
|
||||
$this->strategy = $strategy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class HasLifecycleCallbacks implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Id implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final class Index implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columns;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $flags;
|
||||
|
||||
/**
|
||||
* @var array<string,mixed>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @param array<string>|null $columns
|
||||
* @param array<string>|null $fields
|
||||
* @param array<string>|null $flags
|
||||
* @param array<string,mixed>|null $options
|
||||
*/
|
||||
public function __construct(
|
||||
?array $columns = null,
|
||||
?array $fields = null,
|
||||
?string $name = null,
|
||||
?array $flags = null,
|
||||
?array $options = null
|
||||
) {
|
||||
$this->columns = $columns;
|
||||
$this->fields = $fields;
|
||||
$this->name = $name;
|
||||
$this->flags = $flags;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class InheritanceType implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The inheritance type used by the class and its subclasses.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS'
|
||||
* @readonly
|
||||
* @Enum({"NONE", "JOINED", "SINGLE_TABLE", "TABLE_PER_CLASS"})
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @psalm-param 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS' $value */
|
||||
public function __construct(string $value)
|
||||
{
|
||||
if ($value === 'TABLE_PER_CLASS') {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/10414/',
|
||||
'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement'
|
||||
);
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final class InverseJoinColumn implements MappingAttribute
|
||||
{
|
||||
use JoinColumnProperties;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final class JoinColumn implements MappingAttribute
|
||||
{
|
||||
use JoinColumnProperties;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
trait JoinColumnProperties
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @readonly
|
||||
*/
|
||||
public $referencedColumnName = 'id';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $unique = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $nullable = true;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
* @readonly
|
||||
*/
|
||||
public $onDelete;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columnDefinition;
|
||||
|
||||
/**
|
||||
* Field name used in non-object hydration (array/scalar).
|
||||
*
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $fieldName;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
* @readonly
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @param mixed $onDelete
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
string $referencedColumnName = 'id',
|
||||
bool $unique = false,
|
||||
bool $nullable = true,
|
||||
$onDelete = null,
|
||||
?string $columnDefinition = null,
|
||||
?string $fieldName = null,
|
||||
array $options = []
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->referencedColumnName = $referencedColumnName;
|
||||
$this->unique = $unique;
|
||||
$this->nullable = $nullable;
|
||||
$this->onDelete = $onDelete;
|
||||
$this->columnDefinition = $columnDefinition;
|
||||
$this->fieldName = $fieldName;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class JoinColumns implements MappingAttribute
|
||||
{
|
||||
/** @var array<\Doctrine\ORM\Mapping\JoinColumn> */
|
||||
public $value;
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class JoinTable implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $schema;
|
||||
|
||||
/**
|
||||
* @var array<JoinColumn>
|
||||
* @readonly
|
||||
*/
|
||||
public $joinColumns = [];
|
||||
|
||||
/**
|
||||
* @var array<JoinColumn>
|
||||
* @readonly
|
||||
*/
|
||||
public $inverseJoinColumns = [];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
* @readonly
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/** @param array<string, mixed> $options */
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?string $schema = null,
|
||||
$joinColumns = [],
|
||||
$inverseJoinColumns = [],
|
||||
array $options = []
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->schema = $schema;
|
||||
$this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns;
|
||||
$this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn
|
||||
? [$inverseJoinColumns]
|
||||
: $inverseJoinColumns;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class ManyToMany implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var class-string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $targetEntity;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $mappedBy;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* @var string[]|null
|
||||
* @readonly
|
||||
*/
|
||||
public $cascade;
|
||||
|
||||
/**
|
||||
* The fetching strategy to use for the association.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'
|
||||
* @readonly
|
||||
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
|
||||
*/
|
||||
public $fetch = 'LAZY';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $orphanRemoval = false;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $indexBy;
|
||||
|
||||
/**
|
||||
* @param class-string|null $targetEntity
|
||||
* @param string[]|null $cascade
|
||||
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch
|
||||
*/
|
||||
public function __construct(
|
||||
?string $targetEntity = null,
|
||||
?string $mappedBy = null,
|
||||
?string $inversedBy = null,
|
||||
?array $cascade = null,
|
||||
string $fetch = 'LAZY',
|
||||
bool $orphanRemoval = false,
|
||||
?string $indexBy = null
|
||||
) {
|
||||
if ($targetEntity === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8753',
|
||||
'Passing no target entity is deprecated.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->targetEntity = $targetEntity;
|
||||
$this->mappedBy = $mappedBy;
|
||||
$this->inversedBy = $inversedBy;
|
||||
$this->cascade = $cascade;
|
||||
$this->fetch = $fetch;
|
||||
$this->orphanRemoval = $orphanRemoval;
|
||||
$this->indexBy = $indexBy;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class ManyToOne implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var class-string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $targetEntity;
|
||||
|
||||
/**
|
||||
* @var string[]|null
|
||||
* @readonly
|
||||
*/
|
||||
public $cascade;
|
||||
|
||||
/**
|
||||
* The fetching strategy to use for the association.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'
|
||||
* @readonly
|
||||
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
|
||||
*/
|
||||
public $fetch = 'LAZY';
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* @param class-string|null $targetEntity
|
||||
* @param string[]|null $cascade
|
||||
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch
|
||||
*/
|
||||
public function __construct(
|
||||
?string $targetEntity = null,
|
||||
?array $cascade = null,
|
||||
string $fetch = 'LAZY',
|
||||
?string $inversedBy = null
|
||||
) {
|
||||
$this->targetEntity = $targetEntity;
|
||||
$this->cascade = $cascade;
|
||||
$this->fetch = $fetch;
|
||||
$this->inversedBy = $inversedBy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class MappedSuperclass implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var class-string<EntityRepository>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $repositoryClass;
|
||||
|
||||
/** @psalm-param class-string<EntityRepository>|null $repositoryClass */
|
||||
public function __construct(?string $repositoryClass = null)
|
||||
{
|
||||
$this->repositoryClass = $repositoryClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/** A marker interface for mapping attributes. */
|
||||
interface MappingAttribute extends Annotation
|
||||
{
|
||||
}
|
||||
+1016
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* Is used to specify an array of native SQL named queries.
|
||||
* The NamedNativeQueries annotation can be applied to an entity or mapped superclass.
|
||||
*
|
||||
* @deprecated Named queries won't be supported in ORM 3.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class NamedNativeQueries implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* One or more NamedNativeQuery annotations.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\NamedNativeQuery>
|
||||
*/
|
||||
public $value = [];
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* Is used to specify a native SQL named query.
|
||||
* The NamedNativeQuery annotation can be applied to an entity or mapped superclass.
|
||||
*
|
||||
* @deprecated Named queries won't be supported in ORM 3.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class NamedNativeQuery implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name used to refer to the query with the EntityManager methods that create query objects.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The SQL query string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $query;
|
||||
|
||||
/**
|
||||
* The class of the result.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $resultClass;
|
||||
|
||||
/**
|
||||
* The name of a SqlResultSetMapping, as defined in metadata.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $resultSetMapping;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* @deprecated Named queries won't be supported in ORM 3.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class NamedQueries implements MappingAttribute
|
||||
{
|
||||
/** @var array<\Doctrine\ORM\Mapping\NamedQuery> */
|
||||
public $value;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* @deprecated Named queries won't be supported in ORM 3.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class NamedQuery implements MappingAttribute
|
||||
{
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/** @var string */
|
||||
public $query;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* A set of rules for determining the physical column and table names
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*/
|
||||
interface NamingStrategy
|
||||
{
|
||||
/**
|
||||
* Returns a table name for an entity class.
|
||||
*
|
||||
* @param class-string $className
|
||||
*
|
||||
* @return string A table name.
|
||||
*/
|
||||
public function classToTableName($className);
|
||||
|
||||
/**
|
||||
* Returns a column name for a property.
|
||||
*
|
||||
* @param string $propertyName A property name.
|
||||
* @param class-string $className The fully-qualified class name.
|
||||
*
|
||||
* @return string A column name.
|
||||
*/
|
||||
public function propertyToColumnName($propertyName, $className = null);
|
||||
|
||||
/**
|
||||
* Returns a column name for an embedded property.
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @param string $embeddedColumnName
|
||||
* @param class-string $className
|
||||
* @param class-string $embeddedClassName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function embeddedFieldToColumnName(
|
||||
$propertyName,
|
||||
$embeddedColumnName,
|
||||
$className = null,
|
||||
$embeddedClassName = null
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the default reference column name.
|
||||
*
|
||||
* @return string A column name.
|
||||
*/
|
||||
public function referenceColumnName();
|
||||
|
||||
/**
|
||||
* Returns a join column name for a property.
|
||||
*
|
||||
* @param string $propertyName A property name.
|
||||
*
|
||||
* @return string A join column name.
|
||||
*/
|
||||
public function joinColumnName($propertyName/*, string $className */);
|
||||
|
||||
/**
|
||||
* Returns a join table name.
|
||||
*
|
||||
* @param class-string $sourceEntity The source entity.
|
||||
* @param class-string $targetEntity The target entity.
|
||||
* @param string $propertyName A property name.
|
||||
*
|
||||
* @return string A join table name.
|
||||
*/
|
||||
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null);
|
||||
|
||||
/**
|
||||
* Returns the foreign key column name for the given parameters.
|
||||
*
|
||||
* @param class-string $entityName An entity.
|
||||
* @param string|null $referencedColumnName A property name or null in
|
||||
* case of a self-referencing
|
||||
* entity with join columns
|
||||
* defined in the mapping
|
||||
*
|
||||
* @return string A join column name.
|
||||
*/
|
||||
public function joinKeyColumnName($entityName, $referencedColumnName = null);
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class OneToMany implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $mappedBy;
|
||||
|
||||
/**
|
||||
* @var class-string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $targetEntity;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $cascade;
|
||||
|
||||
/**
|
||||
* The fetching strategy to use for the association.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'
|
||||
* @readonly
|
||||
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
|
||||
*/
|
||||
public $fetch = 'LAZY';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $orphanRemoval = false;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $indexBy;
|
||||
|
||||
/**
|
||||
* @param class-string|null $targetEntity
|
||||
* @param string[]|null $cascade
|
||||
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch
|
||||
*/
|
||||
public function __construct(
|
||||
?string $mappedBy = null,
|
||||
?string $targetEntity = null,
|
||||
?array $cascade = null,
|
||||
string $fetch = 'LAZY',
|
||||
bool $orphanRemoval = false,
|
||||
?string $indexBy = null
|
||||
) {
|
||||
$this->mappedBy = $mappedBy;
|
||||
$this->targetEntity = $targetEntity;
|
||||
$this->cascade = $cascade;
|
||||
$this->fetch = $fetch;
|
||||
$this->orphanRemoval = $orphanRemoval;
|
||||
$this->indexBy = $indexBy;
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class OneToOne implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var class-string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $targetEntity;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $mappedBy;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $cascade;
|
||||
|
||||
/**
|
||||
* The fetching strategy to use for the association.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'
|
||||
* @readonly
|
||||
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
|
||||
*/
|
||||
public $fetch = 'LAZY';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @readonly
|
||||
*/
|
||||
public $orphanRemoval = false;
|
||||
|
||||
/**
|
||||
* @param class-string|null $targetEntity
|
||||
* @param array<string>|null $cascade
|
||||
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch
|
||||
*/
|
||||
public function __construct(
|
||||
?string $mappedBy = null,
|
||||
?string $inversedBy = null,
|
||||
?string $targetEntity = null,
|
||||
?array $cascade = null,
|
||||
string $fetch = 'LAZY',
|
||||
bool $orphanRemoval = false
|
||||
) {
|
||||
$this->mappedBy = $mappedBy;
|
||||
$this->inversedBy = $inversedBy;
|
||||
$this->targetEntity = $targetEntity;
|
||||
$this->cascade = $cascade;
|
||||
$this->fetch = $fetch;
|
||||
$this->orphanRemoval = $orphanRemoval;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class OrderBy implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var array<string>
|
||||
* @readonly
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @param array<string> $value */
|
||||
public function __construct(array $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PostLoad implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PostPersist implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PostRemove implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PostUpdate implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PreFlush implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PrePersist implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PreRemove implements MappingAttribute
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class PreUpdate implements MappingAttribute
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
/**
|
||||
* A set of rules for determining the column, alias and table quotes.
|
||||
*
|
||||
* @psalm-import-type AssociationMapping from ClassMetadata
|
||||
* @psalm-import-type JoinColumnData from ClassMetadata
|
||||
*/
|
||||
interface QuoteStrategy
|
||||
{
|
||||
/**
|
||||
* Gets the (possibly quoted) column name for safe use in an SQL statement.
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) primary table name for safe use in an SQL statement.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTableName(ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) sequence name for safe use in an SQL statement.
|
||||
*
|
||||
* @param mixed[] $definition
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) name of the join table.
|
||||
*
|
||||
* @param AssociationMapping $association
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) join column name.
|
||||
*
|
||||
* @param JoinColumnData $joinColumn
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) join column name.
|
||||
*
|
||||
* @param JoinColumnData $joinColumn
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
|
||||
*
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Gets the column alias.
|
||||
*
|
||||
* @param string $columnName
|
||||
* @param int $counter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Reflection;
|
||||
|
||||
use Doctrine\Persistence\Mapping\ReflectionService;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function array_combine;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
|
||||
/**
|
||||
* Utility class to retrieve all reflection instance properties of a given class, including
|
||||
* private inherited properties and transient properties.
|
||||
*
|
||||
* @private This API is for internal use only
|
||||
*/
|
||||
final class ReflectionPropertiesGetter
|
||||
{
|
||||
/** @var ReflectionProperty[][] indexed by class name and property internal name */
|
||||
private $properties = [];
|
||||
|
||||
/** @var ReflectionService */
|
||||
private $reflectionService;
|
||||
|
||||
public function __construct(ReflectionService $reflectionService)
|
||||
{
|
||||
$this->reflectionService = $reflectionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @psalm-param class-string $className
|
||||
*
|
||||
* @return ReflectionProperty[] indexed by property internal name
|
||||
*/
|
||||
public function getProperties($className): array
|
||||
{
|
||||
if (isset($this->properties[$className])) {
|
||||
return $this->properties[$className];
|
||||
}
|
||||
|
||||
return $this->properties[$className] = array_merge(
|
||||
// first merge because `array_merge` expects >= 1 params
|
||||
...array_merge(
|
||||
[[]],
|
||||
array_map(
|
||||
[$this, 'getClassProperties'],
|
||||
$this->getHierarchyClasses($className)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string $className
|
||||
*
|
||||
* @return ReflectionClass[]
|
||||
* @psalm-return list<ReflectionClass<object>>
|
||||
*/
|
||||
private function getHierarchyClasses(string $className): array
|
||||
{
|
||||
$classes = [];
|
||||
$parentClassName = $className;
|
||||
|
||||
while ($parentClassName && $currentClass = $this->reflectionService->getClass($parentClassName)) {
|
||||
$classes[] = $currentClass;
|
||||
$parentClassName = null;
|
||||
|
||||
$parentClass = $currentClass->getParentClass();
|
||||
if ($parentClass) {
|
||||
$parentClassName = $parentClass->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
// phpcs:disable SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod
|
||||
|
||||
/**
|
||||
* @return ReflectionProperty[]
|
||||
* @psalm-return array<string, ReflectionProperty>
|
||||
*/
|
||||
private function getClassProperties(ReflectionClass $reflectionClass): array
|
||||
{
|
||||
// phpcs:enable SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod
|
||||
$properties = $reflectionClass->getProperties();
|
||||
|
||||
return array_filter(
|
||||
array_filter(array_map(
|
||||
[$this, 'getAccessibleProperty'],
|
||||
array_combine(
|
||||
array_map([$this, 'getLogicalName'], $properties),
|
||||
$properties
|
||||
)
|
||||
)),
|
||||
[$this, 'isInstanceProperty']
|
||||
);
|
||||
}
|
||||
|
||||
private function isInstanceProperty(ReflectionProperty $reflectionProperty): bool
|
||||
{
|
||||
return ! $reflectionProperty->isStatic();
|
||||
}
|
||||
|
||||
private function getAccessibleProperty(ReflectionProperty $property): ?ReflectionProperty
|
||||
{
|
||||
return $this->reflectionService->getAccessibleProperty(
|
||||
$property->class,
|
||||
$property->name
|
||||
);
|
||||
}
|
||||
|
||||
private function getLogicalName(ReflectionProperty $property): string
|
||||
{
|
||||
$propertyName = $property->name;
|
||||
|
||||
if ($property->isPublic()) {
|
||||
return $propertyName;
|
||||
}
|
||||
|
||||
if ($property->isProtected()) {
|
||||
return "\0*\0" . $propertyName;
|
||||
}
|
||||
|
||||
return "\0" . $property->class . "\0" . $propertyName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Instantiator\Instantiator;
|
||||
use ReflectionProperty;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* Acts as a proxy to a nested Property structure, making it look like
|
||||
* just a single scalar property.
|
||||
*
|
||||
* This way value objects "just work" without UnitOfWork, Persisters or Hydrators
|
||||
* needing any changes.
|
||||
*
|
||||
* TODO: Move this class into Common\Reflection
|
||||
*/
|
||||
class ReflectionEmbeddedProperty extends ReflectionProperty
|
||||
{
|
||||
/** @var ReflectionProperty reflection property of the class where the embedded object has to be put */
|
||||
private $parentProperty;
|
||||
|
||||
/** @var ReflectionProperty reflection property of the embedded object */
|
||||
private $childProperty;
|
||||
|
||||
/** @var string name of the embedded class to be eventually instantiated */
|
||||
private $embeddedClass;
|
||||
|
||||
/** @var Instantiator|null */
|
||||
private $instantiator;
|
||||
|
||||
/** @param string $embeddedClass */
|
||||
public function __construct(ReflectionProperty $parentProperty, ReflectionProperty $childProperty, $embeddedClass)
|
||||
{
|
||||
$this->parentProperty = $parentProperty;
|
||||
$this->childProperty = $childProperty;
|
||||
$this->embeddedClass = (string) $embeddedClass;
|
||||
|
||||
parent::__construct($childProperty->class, $childProperty->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getValue($object = null)
|
||||
{
|
||||
$embeddedObject = $this->parentProperty->getValue($object);
|
||||
|
||||
if ($embeddedObject === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->childProperty->getValue($embeddedObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function setValue($object, $value = null)
|
||||
{
|
||||
$embeddedObject = $this->parentProperty->getValue($object);
|
||||
|
||||
if ($embeddedObject === null) {
|
||||
$this->instantiator = $this->instantiator ?: new Instantiator();
|
||||
|
||||
$embeddedObject = $this->instantiator->instantiate($this->embeddedClass);
|
||||
|
||||
$this->parentProperty->setValue($object, $embeddedObject);
|
||||
}
|
||||
|
||||
$this->childProperty->setValue($embeddedObject, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use ReflectionProperty;
|
||||
use ReturnTypeWillChange;
|
||||
use ValueError;
|
||||
|
||||
use function array_map;
|
||||
use function get_class;
|
||||
use function is_array;
|
||||
|
||||
class ReflectionEnumProperty extends ReflectionProperty
|
||||
{
|
||||
/** @var ReflectionProperty */
|
||||
private $originalReflectionProperty;
|
||||
|
||||
/** @var class-string<BackedEnum> */
|
||||
private $enumType;
|
||||
|
||||
/** @param class-string<BackedEnum> $enumType */
|
||||
public function __construct(ReflectionProperty $originalReflectionProperty, string $enumType)
|
||||
{
|
||||
$this->originalReflectionProperty = $originalReflectionProperty;
|
||||
$this->enumType = $enumType;
|
||||
|
||||
parent::__construct(
|
||||
$originalReflectionProperty->class,
|
||||
$originalReflectionProperty->name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param object|null $object
|
||||
*
|
||||
* @return int|string|int[]|string[]|null
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getValue($object = null)
|
||||
{
|
||||
if ($object === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$enum = $this->originalReflectionProperty->getValue($object);
|
||||
|
||||
if ($enum === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_array($enum)) {
|
||||
return array_map(static function (BackedEnum $item): mixed {
|
||||
return $item->value;
|
||||
}, $enum);
|
||||
}
|
||||
|
||||
return $enum->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @param int|string|int[]|string[]|BackedEnum|BackedEnum[]|null $value
|
||||
*/
|
||||
public function setValue($object, $value = null): void
|
||||
{
|
||||
if ($value !== null) {
|
||||
if (is_array($value)) {
|
||||
$value = array_map(function ($item) use ($object): BackedEnum {
|
||||
return $this->initializeEnumValue($object, $item);
|
||||
}, $value);
|
||||
} else {
|
||||
$value = $this->initializeEnumValue($object, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->originalReflectionProperty->setValue($object, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @param int|string|BackedEnum $value
|
||||
*/
|
||||
private function initializeEnumValue($object, $value): BackedEnum
|
||||
{
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$enumType = $this->enumType;
|
||||
|
||||
try {
|
||||
return $enumType::from($value);
|
||||
} catch (ValueError $e) {
|
||||
throw MappingException::invalidEnumValue(
|
||||
get_class($object),
|
||||
$this->originalReflectionProperty->name,
|
||||
(string) $value,
|
||||
$enumType,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function assert;
|
||||
use function func_get_args;
|
||||
use function func_num_args;
|
||||
use function is_object;
|
||||
use function sprintf;
|
||||
|
||||
/** @internal */
|
||||
final class ReflectionReadonlyProperty extends ReflectionProperty
|
||||
{
|
||||
public function __construct(
|
||||
private ReflectionProperty $wrappedProperty
|
||||
) {
|
||||
if (! $wrappedProperty->isReadOnly()) {
|
||||
throw new InvalidArgumentException('Given property is not readonly.');
|
||||
}
|
||||
|
||||
parent::__construct($wrappedProperty->class, $wrappedProperty->name);
|
||||
}
|
||||
|
||||
public function getValue(?object $object = null): mixed
|
||||
{
|
||||
return $this->wrappedProperty->getValue(...func_get_args());
|
||||
}
|
||||
|
||||
public function setValue(mixed $objectOrValue, mixed $value = null): void
|
||||
{
|
||||
if (func_num_args() < 2 || $objectOrValue === null || ! $this->isInitialized($objectOrValue)) {
|
||||
$this->wrappedProperty->setValue(...func_get_args());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert(is_object($objectOrValue));
|
||||
|
||||
if (parent::getValue($objectOrValue) !== $value) {
|
||||
throw new LogicException(sprintf('Attempting to change readonly property %s::$%s.', $this->class, $this->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class SequenceGenerator implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $sequenceName;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @readonly
|
||||
*/
|
||||
public $allocationSize = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @readonly
|
||||
*/
|
||||
public $initialValue = 1;
|
||||
|
||||
public function __construct(
|
||||
?string $sequenceName = null,
|
||||
int $allocationSize = 1,
|
||||
int $initialValue = 1
|
||||
) {
|
||||
$this->sequenceName = $sequenceName;
|
||||
$this->allocationSize = $allocationSize;
|
||||
$this->initialValue = $initialValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* The SqlResultSetMapping annotation is used to specify the mapping of the result of a native SQL query.
|
||||
* The SqlResultSetMapping annotation can be applied to an entity or mapped superclass.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
final class SqlResultSetMapping implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* The name given to the result set mapping, and used to refer to it in the methods of the Query API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Specifies the result set mapping to entities.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\EntityResult>
|
||||
*/
|
||||
public $entities = [];
|
||||
|
||||
/**
|
||||
* Specifies the result set mapping to scalar values.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\ColumnResult>
|
||||
*/
|
||||
public $columns = [];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
/**
|
||||
* Is used to specify an array of mappings.
|
||||
* The SqlResultSetMappings annotation can be applied to an entity or mapped superclass.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class SqlResultSetMappings implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* One or more SqlResultSetMapping annotations.
|
||||
*
|
||||
* @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping>
|
||||
*/
|
||||
public $value = [];
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Table implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $schema;
|
||||
|
||||
/**
|
||||
* @var array<Index>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $indexes;
|
||||
|
||||
/**
|
||||
* @var array<UniqueConstraint>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $uniqueConstraints;
|
||||
|
||||
/**
|
||||
* @var array<string,mixed>
|
||||
* @readonly
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @param array<Index>|null $indexes
|
||||
* @param array<UniqueConstraint>|null $uniqueConstraints
|
||||
* @param array<string,mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?string $schema = null,
|
||||
?array $indexes = null,
|
||||
?array $uniqueConstraints = null,
|
||||
array $options = []
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->schema = $schema;
|
||||
$this->indexes = $indexes;
|
||||
$this->uniqueConstraints = $uniqueConstraints;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use ReflectionProperty;
|
||||
|
||||
interface TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* Validates & completes the given field mapping based on typed property.
|
||||
*
|
||||
* @param array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} $mapping The field mapping to validate & complete.
|
||||
*
|
||||
* @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping.
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function preg_replace;
|
||||
use function str_contains;
|
||||
use function strrpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
|
||||
use const CASE_LOWER;
|
||||
use const CASE_UPPER;
|
||||
|
||||
/**
|
||||
* Naming strategy implementing the underscore naming convention.
|
||||
* Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'.
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*/
|
||||
class UnderscoreNamingStrategy implements NamingStrategy
|
||||
{
|
||||
private const DEFAULT_PATTERN = '/(?<=[a-z])([A-Z])/';
|
||||
private const NUMBER_AWARE_PATTERN = '/(?<=[a-z0-9])([A-Z])/';
|
||||
|
||||
/** @var int */
|
||||
private $case;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var non-empty-string
|
||||
*/
|
||||
private $pattern;
|
||||
|
||||
/**
|
||||
* Underscore naming strategy construct.
|
||||
*
|
||||
* @param int $case CASE_LOWER | CASE_UPPER
|
||||
*/
|
||||
public function __construct($case = CASE_LOWER, bool $numberAware = false)
|
||||
{
|
||||
if (! $numberAware) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/7908',
|
||||
'Creating %s without setting second argument $numberAware=true is deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
self::class
|
||||
);
|
||||
}
|
||||
|
||||
$this->case = $case;
|
||||
$this->pattern = $numberAware ? self::NUMBER_AWARE_PATTERN : self::DEFAULT_PATTERN;
|
||||
}
|
||||
|
||||
/** @return int CASE_LOWER | CASE_UPPER */
|
||||
public function getCase()
|
||||
{
|
||||
return $this->case;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets string case CASE_LOWER | CASE_UPPER.
|
||||
* Alphabetic characters converted to lowercase or uppercase.
|
||||
*
|
||||
* @param int $case
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCase($case)
|
||||
{
|
||||
$this->case = $case;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function classToTableName($className)
|
||||
{
|
||||
if (str_contains($className, '\\')) {
|
||||
$className = substr($className, strrpos($className, '\\') + 1);
|
||||
}
|
||||
|
||||
return $this->underscore($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function propertyToColumnName($propertyName, $className = null)
|
||||
{
|
||||
return $this->underscore($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
|
||||
{
|
||||
return $this->underscore($propertyName) . '_' . $embeddedColumnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function referenceColumnName()
|
||||
{
|
||||
return $this->case === CASE_UPPER ? 'ID' : 'id';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @param class-string $className
|
||||
*/
|
||||
public function joinColumnName($propertyName, $className = null)
|
||||
{
|
||||
return $this->underscore($propertyName) . '_' . $this->referenceColumnName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
|
||||
{
|
||||
return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function joinKeyColumnName($entityName, $referencedColumnName = null)
|
||||
{
|
||||
return $this->classToTableName($entityName) . '_' .
|
||||
($referencedColumnName ?: $this->referenceColumnName());
|
||||
}
|
||||
|
||||
private function underscore(string $string): string
|
||||
{
|
||||
$string = preg_replace($this->pattern, '_$1', $string);
|
||||
|
||||
if ($this->case === CASE_UPPER) {
|
||||
return strtoupper($string);
|
||||
}
|
||||
|
||||
return strtolower($string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("ANNOTATION")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final class UniqueConstraint implements MappingAttribute
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @readonly
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $columns;
|
||||
|
||||
/**
|
||||
* @var array<string>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* @var array<string,mixed>|null
|
||||
* @readonly
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @param array<string>|null $columns
|
||||
* @param array<string>|null $fields
|
||||
* @param array<string,mixed>|null $options
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?array $columns = null,
|
||||
?array $fields = null,
|
||||
?array $options = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->columns = $columns;
|
||||
$this->fields = $fields;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Version implements MappingAttribute
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user