vendor/doctrine/orm/src/Mapping/ClassMetadata.php line 907

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping;
  4. use BackedEnum;
  5. use BadMethodCallException;
  6. use Doctrine\DBAL\Platforms\AbstractPlatform;
  7. use Doctrine\Deprecations\Deprecation;
  8. use Doctrine\Instantiator\Instantiator;
  9. use Doctrine\Instantiator\InstantiatorInterface;
  10. use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
  11. use Doctrine\ORM\EntityRepository;
  12. use Doctrine\ORM\Id\AbstractIdGenerator;
  13. use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
  14. use Doctrine\Persistence\Mapping\ReflectionService;
  15. use Doctrine\Persistence\Reflection\EnumReflectionProperty;
  16. use InvalidArgumentException;
  17. use LogicException;
  18. use ReflectionClass;
  19. use ReflectionNamedType;
  20. use ReflectionProperty;
  21. use Stringable;
  22. use function array_diff;
  23. use function array_intersect;
  24. use function array_key_exists;
  25. use function array_keys;
  26. use function array_map;
  27. use function array_merge;
  28. use function array_pop;
  29. use function array_values;
  30. use function assert;
  31. use function class_exists;
  32. use function count;
  33. use function enum_exists;
  34. use function explode;
  35. use function in_array;
  36. use function interface_exists;
  37. use function is_string;
  38. use function is_subclass_of;
  39. use function ltrim;
  40. use function method_exists;
  41. use function spl_object_id;
  42. use function sprintf;
  43. use function str_contains;
  44. use function str_replace;
  45. use function strtolower;
  46. use function trait_exists;
  47. use function trim;
  48. /**
  49.  * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
  50.  * of an entity and its associations.
  51.  *
  52.  * Once populated, ClassMetadata instances are usually cached in a serialized form.
  53.  *
  54.  * <b>IMPORTANT NOTE:</b>
  55.  *
  56.  * The fields of this class are only public for 2 reasons:
  57.  * 1) To allow fast READ access.
  58.  * 2) To drastically reduce the size of a serialized instance (private/protected members
  59.  *    get the whole class name, namespace inclusive, prepended to every property in
  60.  *    the serialized representation).
  61.  *
  62.  * @psalm-type ConcreteAssociationMapping = OneToOneOwningSideMapping|OneToOneInverseSideMapping|ManyToOneAssociationMapping|OneToManyAssociationMapping|ManyToManyOwningSideMapping|ManyToManyInverseSideMapping
  63.  * @template-covariant T of object
  64.  * @template-implements PersistenceClassMetadata<T>
  65.  */
  66. class ClassMetadata implements PersistenceClassMetadataStringable
  67. {
  68.     /* The inheritance mapping types */
  69.     /**
  70.      * NONE means the class does not participate in an inheritance hierarchy
  71.      * and therefore does not need an inheritance mapping type.
  72.      */
  73.     public const INHERITANCE_TYPE_NONE 1;
  74.     /**
  75.      * JOINED means the class will be persisted according to the rules of
  76.      * <tt>Class Table Inheritance</tt>.
  77.      */
  78.     public const INHERITANCE_TYPE_JOINED 2;
  79.     /**
  80.      * SINGLE_TABLE means the class will be persisted according to the rules of
  81.      * <tt>Single Table Inheritance</tt>.
  82.      */
  83.     public const INHERITANCE_TYPE_SINGLE_TABLE 3;
  84.     /* The Id generator types. */
  85.     /**
  86.      * AUTO means the generator type will depend on what the used platform prefers.
  87.      * Offers full portability.
  88.      */
  89.     public const GENERATOR_TYPE_AUTO 1;
  90.     /**
  91.      * SEQUENCE means a separate sequence object will be used. Platforms that do
  92.      * not have native sequence support may emulate it. Full portability is currently
  93.      * not guaranteed.
  94.      */
  95.     public const GENERATOR_TYPE_SEQUENCE 2;
  96.     /**
  97.      * IDENTITY means an identity column is used for id generation. The database
  98.      * will fill in the id column on insertion. Platforms that do not support
  99.      * native identity columns may emulate them. Full portability is currently
  100.      * not guaranteed.
  101.      */
  102.     public const GENERATOR_TYPE_IDENTITY 4;
  103.     /**
  104.      * NONE means the class does not have a generated id. That means the class
  105.      * must have a natural, manually assigned id.
  106.      */
  107.     public const GENERATOR_TYPE_NONE 5;
  108.     /**
  109.      * CUSTOM means that customer will use own ID generator that supposedly work
  110.      */
  111.     public const GENERATOR_TYPE_CUSTOM 7;
  112.     /**
  113.      * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
  114.      * by doing a property-by-property comparison with the original data. This will
  115.      * be done for all entities that are in MANAGED state at commit-time.
  116.      *
  117.      * This is the default change tracking policy.
  118.      */
  119.     public const CHANGETRACKING_DEFERRED_IMPLICIT 1;
  120.     /**
  121.      * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
  122.      * by doing a property-by-property comparison with the original data. This will
  123.      * be done only for entities that were explicitly saved (through persist() or a cascade).
  124.      */
  125.     public const CHANGETRACKING_DEFERRED_EXPLICIT 2;
  126.     /**
  127.      * Specifies that an association is to be fetched when it is first accessed.
  128.      */
  129.     public const FETCH_LAZY 2;
  130.     /**
  131.      * Specifies that an association is to be fetched when the owner of the
  132.      * association is fetched.
  133.      */
  134.     public const FETCH_EAGER 3;
  135.     /**
  136.      * Specifies that an association is to be fetched lazy (on first access) and that
  137.      * commands such as Collection#count, Collection#slice are issued directly against
  138.      * the database if the collection is not yet initialized.
  139.      */
  140.     public const FETCH_EXTRA_LAZY 4;
  141.     /**
  142.      * Identifies a one-to-one association.
  143.      */
  144.     public const ONE_TO_ONE 1;
  145.     /**
  146.      * Identifies a many-to-one association.
  147.      */
  148.     public const MANY_TO_ONE 2;
  149.     /**
  150.      * Identifies a one-to-many association.
  151.      */
  152.     public const ONE_TO_MANY 4;
  153.     /**
  154.      * Identifies a many-to-many association.
  155.      */
  156.     public const MANY_TO_MANY 8;
  157.     /**
  158.      * Combined bitmask for to-one (single-valued) associations.
  159.      */
  160.     public const TO_ONE 3;
  161.     /**
  162.      * Combined bitmask for to-many (collection-valued) associations.
  163.      */
  164.     public const TO_MANY 12;
  165.     /**
  166.      * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
  167.      */
  168.     public const CACHE_USAGE_READ_ONLY 1;
  169.     /**
  170.      * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes.
  171.      */
  172.     public const CACHE_USAGE_NONSTRICT_READ_WRITE 2;
  173.     /**
  174.      * Read Write Attempts to lock the entity before update/delete.
  175.      */
  176.     public const CACHE_USAGE_READ_WRITE 3;
  177.     /**
  178.      * The value of this column is never generated by the database.
  179.      */
  180.     public const GENERATED_NEVER 0;
  181.     /**
  182.      * The value of this column is generated by the database on INSERT, but not on UPDATE.
  183.      */
  184.     public const GENERATED_INSERT 1;
  185.     /**
  186.      * The value of this column is generated by the database on both INSERT and UDPATE statements.
  187.      */
  188.     public const GENERATED_ALWAYS 2;
  189.     /**
  190.      * READ-ONLY: The namespace the entity class is contained in.
  191.      *
  192.      * @todo Not really needed. Usage could be localized.
  193.      */
  194.     public string|null $namespace null;
  195.     /**
  196.      * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance
  197.      * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same
  198.      * as {@link $name}.
  199.      *
  200.      * @psalm-var class-string
  201.      */
  202.     public string $rootEntityName;
  203.     /**
  204.      * READ-ONLY: The definition of custom generator. Only used for CUSTOM
  205.      * generator type
  206.      *
  207.      * The definition has the following structure:
  208.      * <code>
  209.      * array(
  210.      *     'class' => 'ClassName',
  211.      * )
  212.      * </code>
  213.      *
  214.      * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
  215.      * @var array<string, string>|null
  216.      */
  217.     public array|null $customGeneratorDefinition null;
  218.     /**
  219.      * The name of the custom repository class used for the entity class.
  220.      * (Optional).
  221.      *
  222.      * @psalm-var ?class-string<EntityRepository>
  223.      */
  224.     public string|null $customRepositoryClassName null;
  225.     /**
  226.      * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
  227.      */
  228.     public bool $isMappedSuperclass false;
  229.     /**
  230.      * READ-ONLY: Whether this class describes the mapping of an embeddable class.
  231.      */
  232.     public bool $isEmbeddedClass false;
  233.     /**
  234.      * READ-ONLY: The names of the parent <em>entity</em> classes (ancestors), starting with the
  235.      * nearest one and ending with the root entity class.
  236.      *
  237.      * @psalm-var list<class-string>
  238.      */
  239.     public array $parentClasses = [];
  240.     /**
  241.      * READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all
  242.      * <em>entity</em> subclasses of this class. These may also be abstract classes.
  243.      *
  244.      * This list is used, for example, to enumerate all necessary tables in JTI when querying for root
  245.      * or subclass entities, or to gather all fields comprised in an entity inheritance tree.
  246.      *
  247.      * For classes that do not use STI/JTI, this list is empty.
  248.      *
  249.      * Implementation note:
  250.      *
  251.      * In PHP, there is no general way to discover all subclasses of a given class at runtime. For that
  252.      * reason, the list of classes given in the discriminator map at the root entity is considered
  253.      * authoritative. The discriminator map must contain all <em>concrete</em> classes that can
  254.      * appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract
  255.      * entity classes, users are not required to list such classes with a discriminator value.
  256.      *
  257.      * The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the
  258.      * root entity has been loaded.
  259.      *
  260.      * For subclasses of such root entities, the list can be reused/passed downwards, it only needs to
  261.      * be filtered accordingly (only keep remaining subclasses)
  262.      *
  263.      * @psalm-var list<class-string>
  264.      */
  265.     public array $subClasses = [];
  266.     /**
  267.      * READ-ONLY: The names of all embedded classes based on properties.
  268.      *
  269.      * @psalm-var array<string, EmbeddedClassMapping>
  270.      */
  271.     public array $embeddedClasses = [];
  272.     /**
  273.      * READ-ONLY: The field names of all fields that are part of the identifier/primary key
  274.      * of the mapped entity class.
  275.      *
  276.      * @psalm-var list<string>
  277.      */
  278.     public array $identifier = [];
  279.     /**
  280.      * READ-ONLY: The inheritance mapping type used by the class.
  281.      *
  282.      * @psalm-var self::INHERITANCE_TYPE_*
  283.      */
  284.     public int $inheritanceType self::INHERITANCE_TYPE_NONE;
  285.     /**
  286.      * READ-ONLY: The Id generator type used by the class.
  287.      *
  288.      * @psalm-var self::GENERATOR_TYPE_*
  289.      */
  290.     public int $generatorType self::GENERATOR_TYPE_NONE;
  291.     /**
  292.      * READ-ONLY: The field mappings of the class.
  293.      * Keys are field names and values are FieldMapping instances
  294.      *
  295.      * @var array<string, FieldMapping>
  296.      */
  297.     public array $fieldMappings = [];
  298.     /**
  299.      * READ-ONLY: An array of field names. Used to look up field names from column names.
  300.      * Keys are column names and values are field names.
  301.      *
  302.      * @psalm-var array<string, string>
  303.      */
  304.     public array $fieldNames = [];
  305.     /**
  306.      * READ-ONLY: A map of field names to column names. Keys are field names and values column names.
  307.      * Used to look up column names from field names.
  308.      * This is the reverse lookup map of $_fieldNames.
  309.      *
  310.      * @deprecated 3.0 Remove this.
  311.      *
  312.      * @var mixed[]
  313.      */
  314.     public array $columnNames = [];
  315.     /**
  316.      * READ-ONLY: The discriminator value of this class.
  317.      *
  318.      * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
  319.      * where a discriminator column is used.</b>
  320.      *
  321.      * @see discriminatorColumn
  322.      */
  323.     public mixed $discriminatorValue null;
  324.     /**
  325.      * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
  326.      *
  327.      * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
  328.      * where a discriminator column is used.</b>
  329.      *
  330.      * @see discriminatorColumn
  331.      *
  332.      * @var array<int|string, string>
  333.      *
  334.      * @psalm-var array<int|string, class-string>
  335.      */
  336.     public array $discriminatorMap = [];
  337.     /**
  338.      * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
  339.      * inheritance mappings.
  340.      */
  341.     public DiscriminatorColumnMapping|null $discriminatorColumn null;
  342.     /**
  343.      * READ-ONLY: The primary table definition. The definition is an array with the
  344.      * following entries:
  345.      *
  346.      * name => <tableName>
  347.      * schema => <schemaName>
  348.      * indexes => array
  349.      * uniqueConstraints => array
  350.      *
  351.      * @var mixed[]
  352.      * @psalm-var array{
  353.      *               name: string,
  354.      *               schema?: string,
  355.      *               indexes?: array,
  356.      *               uniqueConstraints?: array,
  357.      *               options?: array<string, mixed>,
  358.      *               quoted?: bool
  359.      *           }
  360.      */
  361.     public array $table;
  362.     /**
  363.      * READ-ONLY: The registered lifecycle callbacks for entities of this class.
  364.      *
  365.      * @psalm-var array<string, list<string>>
  366.      */
  367.     public array $lifecycleCallbacks = [];
  368.     /**
  369.      * READ-ONLY: The registered entity listeners.
  370.      *
  371.      * @psalm-var array<string, list<array{class: class-string, method: string}>>
  372.      */
  373.     public array $entityListeners = [];
  374.     /**
  375.      * READ-ONLY: The association mappings of this class.
  376.      *
  377.      * A join table definition has the following structure:
  378.      * <pre>
  379.      * array(
  380.      *     'name' => <join table name>,
  381.      *      'joinColumns' => array(<join column mapping from join table to source table>),
  382.      *      'inverseJoinColumns' => array(<join column mapping from join table to target table>)
  383.      * )
  384.      * </pre>
  385.      *
  386.      * @psalm-var array<string, ConcreteAssociationMapping>
  387.      */
  388.     public array $associationMappings = [];
  389.     /**
  390.      * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite.
  391.      */
  392.     public bool $isIdentifierComposite false;
  393.     /**
  394.      * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association.
  395.      *
  396.      * This flag is necessary because some code blocks require special treatment of this cases.
  397.      */
  398.     public bool $containsForeignIdentifier false;
  399.     /**
  400.      * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one ENUM type.
  401.      *
  402.      * This flag is necessary because some code blocks require special treatment of this cases.
  403.      */
  404.     public bool $containsEnumIdentifier false;
  405.     /**
  406.      * READ-ONLY: The ID generator used for generating IDs for this class.
  407.      *
  408.      * @todo Remove!
  409.      */
  410.     public AbstractIdGenerator $idGenerator;
  411.     /**
  412.      * READ-ONLY: The definition of the sequence generator of this class. Only used for the
  413.      * SEQUENCE generation strategy.
  414.      *
  415.      * The definition has the following structure:
  416.      * <code>
  417.      * array(
  418.      *     'sequenceName' => 'name',
  419.      *     'allocationSize' => '20',
  420.      *     'initialValue' => '1'
  421.      * )
  422.      * </code>
  423.      *
  424.      * @var array<string, mixed>|null
  425.      * @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}|null
  426.      * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
  427.      */
  428.     public array|null $sequenceGeneratorDefinition null;
  429.     /**
  430.      * READ-ONLY: The policy used for change-tracking on entities of this class.
  431.      */
  432.     public int $changeTrackingPolicy self::CHANGETRACKING_DEFERRED_IMPLICIT;
  433.     /**
  434.      * READ-ONLY: A Flag indicating whether one or more columns of this class
  435.      * have to be reloaded after insert / update operations.
  436.      */
  437.     public bool $requiresFetchAfterChange false;
  438.     /**
  439.      * READ-ONLY: A flag for whether or not instances of this class are to be versioned
  440.      * with optimistic locking.
  441.      */
  442.     public bool $isVersioned false;
  443.     /**
  444.      * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
  445.      */
  446.     public string|null $versionField null;
  447.     /** @var mixed[]|null */
  448.     public array|null $cache null;
  449.     /**
  450.      * The ReflectionClass instance of the mapped class.
  451.      *
  452.      * @var ReflectionClass<T>|null
  453.      */
  454.     public ReflectionClass|null $reflClass null;
  455.     /**
  456.      * Is this entity marked as "read-only"?
  457.      *
  458.      * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance
  459.      * optimization for entities that are immutable, either in your domain or through the relation database
  460.      * (coming from a view, or a history table for example).
  461.      */
  462.     public bool $isReadOnly false;
  463.     /**
  464.      * NamingStrategy determining the default column and table names.
  465.      */
  466.     protected NamingStrategy $namingStrategy;
  467.     /**
  468.      * The ReflectionProperty instances of the mapped class.
  469.      *
  470.      * @var array<string, ReflectionProperty|null>
  471.      */
  472.     public array $reflFields = [];
  473.     private InstantiatorInterface|null $instantiator null;
  474.     private readonly TypedFieldMapper $typedFieldMapper;
  475.     /**
  476.      * Initializes a new ClassMetadata instance that will hold the object-relational mapping
  477.      * metadata of the class with the given name.
  478.      *
  479.      * @param string $name The name of the entity class the new instance is used for.
  480.      * @psalm-param class-string<T> $name
  481.      */
  482.     public function __construct(public string $nameNamingStrategy|null $namingStrategy nullTypedFieldMapper|null $typedFieldMapper null)
  483.     {
  484.         $this->rootEntityName   $name;
  485.         $this->namingStrategy   $namingStrategy ?? new DefaultNamingStrategy();
  486.         $this->instantiator     = new Instantiator();
  487.         $this->typedFieldMapper $typedFieldMapper ?? new DefaultTypedFieldMapper();
  488.     }
  489.     /**
  490.      * Gets the ReflectionProperties of the mapped class.
  491.      *
  492.      * @return ReflectionProperty[]|null[] An array of ReflectionProperty instances.
  493.      * @psalm-return array<ReflectionProperty|null>
  494.      */
  495.     public function getReflectionProperties(): array
  496.     {
  497.         return $this->reflFields;
  498.     }
  499.     /**
  500.      * Gets a ReflectionProperty for a specific field of the mapped class.
  501.      */
  502.     public function getReflectionProperty(string $name): ReflectionProperty|null
  503.     {
  504.         return $this->reflFields[$name];
  505.     }
  506.     /**
  507.      * Gets the ReflectionProperty for the single identifier field.
  508.      *
  509.      * @throws BadMethodCallException If the class has a composite identifier.
  510.      */
  511.     public function getSingleIdReflectionProperty(): ReflectionProperty|null
  512.     {
  513.         if ($this->isIdentifierComposite) {
  514.             throw new BadMethodCallException('Class ' $this->name ' has a composite identifier.');
  515.         }
  516.         return $this->reflFields[$this->identifier[0]];
  517.     }
  518.     /**
  519.      * Extracts the identifier values of an entity of this class.
  520.      *
  521.      * For composite identifiers, the identifier values are returned as an array
  522.      * with the same order as the field order in {@link identifier}.
  523.      *
  524.      * @return array<string, mixed>
  525.      */
  526.     public function getIdentifierValues(object $entity): array
  527.     {
  528.         if ($this->isIdentifierComposite) {
  529.             $id = [];
  530.             foreach ($this->identifier as $idField) {
  531.                 $value $this->reflFields[$idField]->getValue($entity);
  532.                 if ($value !== null) {
  533.                     $id[$idField] = $value;
  534.                 }
  535.             }
  536.             return $id;
  537.         }
  538.         $id    $this->identifier[0];
  539.         $value $this->reflFields[$id]->getValue($entity);
  540.         if ($value === null) {
  541.             return [];
  542.         }
  543.         return [$id => $value];
  544.     }
  545.     /**
  546.      * Populates the entity identifier of an entity.
  547.      *
  548.      * @psalm-param array<string, mixed> $id
  549.      *
  550.      * @todo Rename to assignIdentifier()
  551.      */
  552.     public function setIdentifierValues(object $entity, array $id): void
  553.     {
  554.         foreach ($id as $idField => $idValue) {
  555.             $this->reflFields[$idField]->setValue($entity$idValue);
  556.         }
  557.     }
  558.     /**
  559.      * Sets the specified field to the specified value on the given entity.
  560.      */
  561.     public function setFieldValue(object $entitystring $fieldmixed $value): void
  562.     {
  563.         $this->reflFields[$field]->setValue($entity$value);
  564.     }
  565.     /**
  566.      * Gets the specified field's value off the given entity.
  567.      */
  568.     public function getFieldValue(object $entitystring $field): mixed
  569.     {
  570.         return $this->reflFields[$field]->getValue($entity);
  571.     }
  572.     /**
  573.      * Creates a string representation of this instance.
  574.      *
  575.      * @return string The string representation of this instance.
  576.      *
  577.      * @todo Construct meaningful string representation.
  578.      */
  579.     public function __toString(): string
  580.     {
  581.         return self::class . '@' spl_object_id($this);
  582.     }
  583.     /**
  584.      * Determines which fields get serialized.
  585.      *
  586.      * It is only serialized what is necessary for best unserialization performance.
  587.      * That means any metadata properties that are not set or empty or simply have
  588.      * their default value are NOT serialized.
  589.      *
  590.      * Parts that are also NOT serialized because they can not be properly unserialized:
  591.      *      - reflClass (ReflectionClass)
  592.      *      - reflFields (ReflectionProperty array)
  593.      *
  594.      * @return string[] The names of all the fields that should be serialized.
  595.      */
  596.     public function __sleep(): array
  597.     {
  598.         // This metadata is always serialized/cached.
  599.         $serialized = [
  600.             'associationMappings',
  601.             'columnNames'//TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName']
  602.             'fieldMappings',
  603.             'fieldNames',
  604.             'embeddedClasses',
  605.             'identifier',
  606.             'isIdentifierComposite'// TODO: REMOVE
  607.             'name',
  608.             'namespace'// TODO: REMOVE
  609.             'table',
  610.             'rootEntityName',
  611.             'idGenerator'//TODO: Does not really need to be serialized. Could be moved to runtime.
  612.         ];
  613.         // The rest of the metadata is only serialized if necessary.
  614.         if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
  615.             $serialized[] = 'changeTrackingPolicy';
  616.         }
  617.         if ($this->customRepositoryClassName) {
  618.             $serialized[] = 'customRepositoryClassName';
  619.         }
  620.         if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE) {
  621.             $serialized[] = 'inheritanceType';
  622.             $serialized[] = 'discriminatorColumn';
  623.             $serialized[] = 'discriminatorValue';
  624.             $serialized[] = 'discriminatorMap';
  625.             $serialized[] = 'parentClasses';
  626.             $serialized[] = 'subClasses';
  627.         }
  628.         if ($this->generatorType !== self::GENERATOR_TYPE_NONE) {
  629.             $serialized[] = 'generatorType';
  630.             if ($this->generatorType === self::GENERATOR_TYPE_SEQUENCE) {
  631.                 $serialized[] = 'sequenceGeneratorDefinition';
  632.             }
  633.         }
  634.         if ($this->isMappedSuperclass) {
  635.             $serialized[] = 'isMappedSuperclass';
  636.         }
  637.         if ($this->isEmbeddedClass) {
  638.             $serialized[] = 'isEmbeddedClass';
  639.         }
  640.         if ($this->containsForeignIdentifier) {
  641.             $serialized[] = 'containsForeignIdentifier';
  642.         }
  643.         if ($this->containsEnumIdentifier) {
  644.             $serialized[] = 'containsEnumIdentifier';
  645.         }
  646.         if ($this->isVersioned) {
  647.             $serialized[] = 'isVersioned';
  648.             $serialized[] = 'versionField';
  649.         }
  650.         if ($this->lifecycleCallbacks) {
  651.             $serialized[] = 'lifecycleCallbacks';
  652.         }
  653.         if ($this->entityListeners) {
  654.             $serialized[] = 'entityListeners';
  655.         }
  656.         if ($this->isReadOnly) {
  657.             $serialized[] = 'isReadOnly';
  658.         }
  659.         if ($this->customGeneratorDefinition) {
  660.             $serialized[] = 'customGeneratorDefinition';
  661.         }
  662.         if ($this->cache) {
  663.             $serialized[] = 'cache';
  664.         }
  665.         if ($this->requiresFetchAfterChange) {
  666.             $serialized[] = 'requiresFetchAfterChange';
  667.         }
  668.         return $serialized;
  669.     }
  670.     /**
  671.      * Creates a new instance of the mapped class, without invoking the constructor.
  672.      */
  673.     public function newInstance(): object
  674.     {
  675.         return $this->instantiator->instantiate($this->name);
  676.     }
  677.     /**
  678.      * Restores some state that can not be serialized/unserialized.
  679.      */
  680.     public function wakeupReflection(ReflectionService $reflService): void
  681.     {
  682.         // Restore ReflectionClass and properties
  683.         $this->reflClass    $reflService->getClass($this->name);
  684.         $this->instantiator $this->instantiator ?: new Instantiator();
  685.         $parentReflFields = [];
  686.         foreach ($this->embeddedClasses as $property => $embeddedClass) {
  687.             if (isset($embeddedClass->declaredField)) {
  688.                 assert($embeddedClass->originalField !== null);
  689.                 $childProperty $this->getAccessibleProperty(
  690.                     $reflService,
  691.                     $this->embeddedClasses[$embeddedClass->declaredField]->class,
  692.                     $embeddedClass->originalField,
  693.                 );
  694.                 assert($childProperty !== null);
  695.                 $parentReflFields[$property] = new ReflectionEmbeddedProperty(
  696.                     $parentReflFields[$embeddedClass->declaredField],
  697.                     $childProperty,
  698.                     $this->embeddedClasses[$embeddedClass->declaredField]->class,
  699.                 );
  700.                 continue;
  701.             }
  702.             $fieldRefl $this->getAccessibleProperty(
  703.                 $reflService,
  704.                 $embeddedClass->declared ?? $this->name,
  705.                 $property,
  706.             );
  707.             $parentReflFields[$property] = $fieldRefl;
  708.             $this->reflFields[$property] = $fieldRefl;
  709.         }
  710.         foreach ($this->fieldMappings as $field => $mapping) {
  711.             if (isset($mapping->declaredField) && isset($parentReflFields[$mapping->declaredField])) {
  712.                 assert($mapping->originalField !== null);
  713.                 assert($mapping->originalClass !== null);
  714.                 $childProperty $this->getAccessibleProperty($reflService$mapping->originalClass$mapping->originalField);
  715.                 assert($childProperty !== null);
  716.                 if (isset($mapping->enumType)) {
  717.                     $childProperty = new EnumReflectionProperty(
  718.                         $childProperty,
  719.                         $mapping->enumType,
  720.                     );
  721.                 }
  722.                 $this->reflFields[$field] = new ReflectionEmbeddedProperty(
  723.                     $parentReflFields[$mapping->declaredField],
  724.                     $childProperty,
  725.                     $mapping->originalClass,
  726.                 );
  727.                 continue;
  728.             }
  729.             $this->reflFields[$field] = isset($mapping->declared)
  730.                 ? $this->getAccessibleProperty($reflService$mapping->declared$field)
  731.                 : $this->getAccessibleProperty($reflService$this->name$field);
  732.             if (isset($mapping->enumType) && $this->reflFields[$field] !== null) {
  733.                 $this->reflFields[$field] = new EnumReflectionProperty(
  734.                     $this->reflFields[$field],
  735.                     $mapping->enumType,
  736.                 );
  737.             }
  738.         }
  739.         foreach ($this->associationMappings as $field => $mapping) {
  740.             $this->reflFields[$field] = isset($mapping->declared)
  741.                 ? $this->getAccessibleProperty($reflService$mapping->declared$field)
  742.                 : $this->getAccessibleProperty($reflService$this->name$field);
  743.         }
  744.     }
  745.     /**
  746.      * Initializes a new ClassMetadata instance that will hold the object-relational mapping
  747.      * metadata of the class with the given name.
  748.      *
  749.      * @param ReflectionService $reflService The reflection service.
  750.      */
  751.     public function initializeReflection(ReflectionService $reflService): void
  752.     {
  753.         $this->reflClass $reflService->getClass($this->name);
  754.         $this->namespace $reflService->getClassNamespace($this->name);
  755.         if ($this->reflClass) {
  756.             $this->name $this->rootEntityName $this->reflClass->name;
  757.         }
  758.         $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
  759.     }
  760.     /**
  761.      * Validates Identifier.
  762.      *
  763.      * @throws MappingException
  764.      */
  765.     public function validateIdentifier(): void
  766.     {
  767.         if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
  768.             return;
  769.         }
  770.         // Verify & complete identifier mapping
  771.         if (! $this->identifier) {
  772.             throw MappingException::identifierRequired($this->name);
  773.         }
  774.         if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
  775.             throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
  776.         }
  777.     }
  778.     /**
  779.      * Validates association targets actually exist.
  780.      *
  781.      * @throws MappingException
  782.      */
  783.     public function validateAssociations(): void
  784.     {
  785.         foreach ($this->associationMappings as $mapping) {
  786.             if (
  787.                 ! class_exists($mapping->targetEntity)
  788.                 && ! interface_exists($mapping->targetEntity)
  789.                 && ! trait_exists($mapping->targetEntity)
  790.             ) {
  791.                 throw MappingException::invalidTargetEntityClass($mapping->targetEntity$this->name$mapping->fieldName);
  792.             }
  793.         }
  794.     }
  795.     /**
  796.      * Validates lifecycle callbacks.
  797.      *
  798.      * @throws MappingException
  799.      */
  800.     public function validateLifecycleCallbacks(ReflectionService $reflService): void
  801.     {
  802.         foreach ($this->lifecycleCallbacks as $callbacks) {
  803.             foreach ($callbacks as $callbackFuncName) {
  804.                 if (! $reflService->hasPublicMethod($this->name$callbackFuncName)) {
  805.                     throw MappingException::lifecycleCallbackMethodNotFound($this->name$callbackFuncName);
  806.                 }
  807.             }
  808.         }
  809.     }
  810.     /**
  811.      * {@inheritDoc}
  812.      *
  813.      * Can return null when using static reflection, in violation of the LSP
  814.      */
  815.     public function getReflectionClass(): ReflectionClass|null
  816.     {
  817.         return $this->reflClass;
  818.     }
  819.     /** @psalm-param array{usage?: mixed, region?: mixed} $cache */
  820.     public function enableCache(array $cache): void
  821.     {
  822.         if (! isset($cache['usage'])) {
  823.             $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
  824.         }
  825.         if (! isset($cache['region'])) {
  826.             $cache['region'] = strtolower(str_replace('\\''_'$this->rootEntityName));
  827.         }
  828.         $this->cache $cache;
  829.     }
  830.     /** @psalm-param array{usage?: int, region?: string} $cache */
  831.     public function enableAssociationCache(string $fieldName, array $cache): void
  832.     {
  833.         $this->associationMappings[$fieldName]->cache $this->getAssociationCacheDefaults($fieldName$cache);
  834.     }
  835.     /**
  836.      * @psalm-param array{usage?: int, region?: string|null} $cache
  837.      *
  838.      * @return int[]|string[]
  839.      * @psalm-return array{usage: int, region: string|null}
  840.      */
  841.     public function getAssociationCacheDefaults(string $fieldName, array $cache): array
  842.     {
  843.         if (! isset($cache['usage'])) {
  844.             $cache['usage'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY;
  845.         }
  846.         if (! isset($cache['region'])) {
  847.             $cache['region'] = strtolower(str_replace('\\''_'$this->rootEntityName)) . '__' $fieldName;
  848.         }
  849.         return $cache;
  850.     }
  851.     /**
  852.      * Sets the change tracking policy used by this class.
  853.      */
  854.     public function setChangeTrackingPolicy(int $policy): void
  855.     {
  856.         $this->changeTrackingPolicy $policy;
  857.     }
  858.     /**
  859.      * Whether the change tracking policy of this class is "deferred explicit".
  860.      */
  861.     public function isChangeTrackingDeferredExplicit(): bool
  862.     {
  863.         return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
  864.     }
  865.     /**
  866.      * Whether the change tracking policy of this class is "deferred implicit".
  867.      */
  868.     public function isChangeTrackingDeferredImplicit(): bool
  869.     {
  870.         return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
  871.     }
  872.     /**
  873.      * Checks whether a field is part of the identifier/primary key field(s).
  874.      */
  875.     public function isIdentifier(string $fieldName): bool
  876.     {
  877.         if (! $this->identifier) {
  878.             return false;
  879.         }
  880.         if (! $this->isIdentifierComposite) {
  881.             return $fieldName === $this->identifier[0];
  882.         }
  883.         return in_array($fieldName$this->identifiertrue);
  884.     }
  885.     public function isUniqueField(string $fieldName): bool
  886.     {
  887.         $mapping $this->getFieldMapping($fieldName);
  888.         return $mapping !== false && isset($mapping->unique) && $mapping->unique;
  889.     }
  890.     public function isNullable(string $fieldName): bool
  891.     {
  892.         $mapping $this->getFieldMapping($fieldName);
  893.         return $mapping !== false && isset($mapping->nullable) && $mapping->nullable;
  894.     }
  895.     /**
  896.      * Gets a column name for a field name.
  897.      * If the column name for the field cannot be found, the given field name
  898.      * is returned.
  899.      */
  900.     public function getColumnName(string $fieldName): string
  901.     {
  902.         return $this->columnNames[$fieldName] ?? $fieldName;
  903.     }
  904.     /**
  905.      * Gets the mapping of a (regular) field that holds some data but not a
  906.      * reference to another object.
  907.      *
  908.      * @throws MappingException
  909.      */
  910.     public function getFieldMapping(string $fieldName): FieldMapping
  911.     {
  912.         if (! isset($this->fieldMappings[$fieldName])) {
  913.             throw MappingException::mappingNotFound($this->name$fieldName);
  914.         }
  915.         return $this->fieldMappings[$fieldName];
  916.     }
  917.     /**
  918.      * Gets the mapping of an association.
  919.      *
  920.      * @see ClassMetadata::$associationMappings
  921.      *
  922.      * @param string $fieldName The field name that represents the association in
  923.      *                          the object model.
  924.      *
  925.      * @throws MappingException
  926.      */
  927.     public function getAssociationMapping(string $fieldName): AssociationMapping
  928.     {
  929.         if (! isset($this->associationMappings[$fieldName])) {
  930.             throw MappingException::mappingNotFound($this->name$fieldName);
  931.         }
  932.         return $this->associationMappings[$fieldName];
  933.     }
  934.     /**
  935.      * Gets all association mappings of the class.
  936.      *
  937.      * @psalm-return array<string, AssociationMapping>
  938.      */
  939.     public function getAssociationMappings(): array
  940.     {
  941.         return $this->associationMappings;
  942.     }
  943.     /**
  944.      * Gets the field name for a column name.
  945.      * If no field name can be found the column name is returned.
  946.      *
  947.      * @return string The column alias.
  948.      */
  949.     public function getFieldName(string $columnName): string
  950.     {
  951.         return $this->fieldNames[$columnName] ?? $columnName;
  952.     }
  953.     /**
  954.      * Checks whether given property has type
  955.      */
  956.     private function isTypedProperty(string $name): bool
  957.     {
  958.         return isset($this->reflClass)
  959.                && $this->reflClass->hasProperty($name)
  960.                && $this->reflClass->getProperty($name)->hasType();
  961.     }
  962.     /**
  963.      * Validates & completes the given field mapping based on typed property.
  964.      *
  965.      * @param  array{fieldName: string, type?: string} $mapping The field mapping to validate & complete.
  966.      *
  967.      * @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping.
  968.      */
  969.     private function validateAndCompleteTypedFieldMapping(array $mapping): array
  970.     {
  971.         $field $this->reflClass->getProperty($mapping['fieldName']);
  972.         $mapping $this->typedFieldMapper->validateAndComplete($mapping$field);
  973.         return $mapping;
  974.     }
  975.     /**
  976.      * Validates & completes the basic mapping information based on typed property.
  977.      *
  978.      * @param array{type: self::ONE_TO_ONE|self::MANY_TO_ONE|self::ONE_TO_MANY|self::MANY_TO_MANY, fieldName: string, targetEntity?: class-string} $mapping The mapping.
  979.      *
  980.      * @return mixed[] The updated mapping.
  981.      */
  982.     private function validateAndCompleteTypedAssociationMapping(array $mapping): array
  983.     {
  984.         $type $this->reflClass->getProperty($mapping['fieldName'])->getType();
  985.         if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) {
  986.             return $mapping;
  987.         }
  988.         if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) {
  989.             $mapping['targetEntity'] = $type->getName();
  990.         }
  991.         return $mapping;
  992.     }
  993.     /**
  994.      * Validates & completes the given field mapping.
  995.      *
  996.      * @psalm-param array{
  997.      *     fieldName?: string,
  998.      *     columnName?: string,
  999.      *     id?: bool,
  1000.      *     generated?: self::GENERATED_*,
  1001.      *     enumType?: class-string,
  1002.      * } $mapping The field mapping to validate & complete.
  1003.      *
  1004.      * @return FieldMapping The updated mapping.
  1005.      *
  1006.      * @throws MappingException
  1007.      */
  1008.     protected function validateAndCompleteFieldMapping(array $mapping): FieldMapping
  1009.     {
  1010.         // Check mandatory fields
  1011.         if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) {
  1012.             throw MappingException::missingFieldName($this->name);
  1013.         }
  1014.         if ($this->isTypedProperty($mapping['fieldName'])) {
  1015.             $mapping $this->validateAndCompleteTypedFieldMapping($mapping);
  1016.         }
  1017.         if (! isset($mapping['type'])) {
  1018.             // Default to string
  1019.             $mapping['type'] = 'string';
  1020.         }
  1021.         // Complete fieldName and columnName mapping
  1022.         if (! isset($mapping['columnName'])) {
  1023.             $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
  1024.         }
  1025.         $mapping FieldMapping::fromMappingArray($mapping);
  1026.         if ($mapping->columnName[0] === '`') {
  1027.             $mapping->columnName trim($mapping->columnName'`');
  1028.             $mapping->quoted     true;
  1029.         }
  1030.         $this->columnNames[$mapping->fieldName] = $mapping->columnName;
  1031.         if (isset($this->fieldNames[$mapping->columnName]) || ($this->discriminatorColumn && $this->discriminatorColumn->name === $mapping->columnName)) {
  1032.             throw MappingException::duplicateColumnName($this->name$mapping->columnName);
  1033.         }
  1034.         $this->fieldNames[$mapping->columnName] = $mapping->fieldName;
  1035.         // Complete id mapping
  1036.         if (isset($mapping->id) && $mapping->id === true) {
  1037.             if ($this->versionField === $mapping->fieldName) {
  1038.                 throw MappingException::cannotVersionIdField($this->name$mapping->fieldName);
  1039.             }
  1040.             if (! in_array($mapping->fieldName$this->identifiertrue)) {
  1041.                 $this->identifier[] = $mapping->fieldName;
  1042.             }
  1043.             // Check for composite key
  1044.             if (! $this->isIdentifierComposite && count($this->identifier) > 1) {
  1045.                 $this->isIdentifierComposite true;
  1046.             }
  1047.         }
  1048.         if (isset($mapping->generated)) {
  1049.             if (! in_array($mapping->generated, [self::GENERATED_NEVERself::GENERATED_INSERTself::GENERATED_ALWAYS])) {
  1050.                 throw MappingException::invalidGeneratedMode($mapping->generated);
  1051.             }
  1052.             if ($mapping->generated === self::GENERATED_NEVER) {
  1053.                 unset($mapping->generated);
  1054.             }
  1055.         }
  1056.         if (isset($mapping->enumType)) {
  1057.             if (! enum_exists($mapping->enumType)) {
  1058.                 throw MappingException::nonEnumTypeMapped($this->name$mapping->fieldName$mapping->enumType);
  1059.             }
  1060.             if (! empty($mapping->id)) {
  1061.                 $this->containsEnumIdentifier true;
  1062.             }
  1063.         }
  1064.         return $mapping;
  1065.     }
  1066.     /**
  1067.      * Validates & completes the basic mapping information that is common to all
  1068.      * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
  1069.      *
  1070.      * @psalm-param array<string, mixed> $mapping The mapping.
  1071.      *
  1072.      * @return ConcreteAssociationMapping
  1073.      *
  1074.      * @throws MappingException If something is wrong with the mapping.
  1075.      */
  1076.     protected function _validateAndCompleteAssociationMapping(array $mapping): AssociationMapping
  1077.     {
  1078.         if (array_key_exists('mappedBy'$mapping) && $mapping['mappedBy'] === null) {
  1079.             unset($mapping['mappedBy']);
  1080.         }
  1081.         if (array_key_exists('inversedBy'$mapping) && $mapping['inversedBy'] === null) {
  1082.             unset($mapping['inversedBy']);
  1083.         }
  1084.         if (array_key_exists('joinColumns'$mapping) && in_array($mapping['joinColumns'], [null, []], true)) {
  1085.             unset($mapping['joinColumns']);
  1086.         }
  1087.         $mapping['isOwningSide'] = true// assume owning side until we hit mappedBy
  1088.         if (empty($mapping['indexBy'])) {
  1089.             unset($mapping['indexBy']);
  1090.         }
  1091.         // If targetEntity is unqualified, assume it is in the same namespace as
  1092.         // the sourceEntity.
  1093.         $mapping['sourceEntity'] = $this->name;
  1094.         if ($this->isTypedProperty($mapping['fieldName'])) {
  1095.             $mapping $this->validateAndCompleteTypedAssociationMapping($mapping);
  1096.         }
  1097.         if (isset($mapping['targetEntity'])) {
  1098.             $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
  1099.             $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
  1100.         }
  1101.         if (($mapping['type'] & self::MANY_TO_ONE) > && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
  1102.             throw MappingException::illegalOrphanRemoval($this->name$mapping['fieldName']);
  1103.         }
  1104.         // Complete id mapping
  1105.         if (isset($mapping['id']) && $mapping['id'] === true) {
  1106.             if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
  1107.                 throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name$mapping['fieldName']);
  1108.             }
  1109.             if (! in_array($mapping['fieldName'], $this->identifiertrue)) {
  1110.                 if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
  1111.                     throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
  1112.                         $mapping['targetEntity'],
  1113.                         $this->name,
  1114.                         $mapping['fieldName'],
  1115.                     );
  1116.                 }
  1117.                 assert(is_string($mapping['fieldName']));
  1118.                 $this->identifier[]              = $mapping['fieldName'];
  1119.                 $this->containsForeignIdentifier true;
  1120.             }
  1121.             // Check for composite key
  1122.             if (! $this->isIdentifierComposite && count($this->identifier) > 1) {
  1123.                 $this->isIdentifierComposite true;
  1124.             }
  1125.             if ($this->cache && ! isset($mapping['cache'])) {
  1126.                 throw NonCacheableEntityAssociation::fromEntityAndField(
  1127.                     $this->name,
  1128.                     $mapping['fieldName'],
  1129.                 );
  1130.             }
  1131.         }
  1132.         // Mandatory attributes for both sides
  1133.         // Mandatory: fieldName, targetEntity
  1134.         if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) {
  1135.             throw MappingException::missingFieldName($this->name);
  1136.         }
  1137.         if (! isset($mapping['targetEntity'])) {
  1138.             throw MappingException::missingTargetEntity($mapping['fieldName']);
  1139.         }
  1140.         // Mandatory and optional attributes for either side
  1141.         if (! isset($mapping['mappedBy'])) {
  1142.             if (isset($mapping['joinTable'])) {
  1143.                 if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
  1144.                     $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
  1145.                     $mapping['joinTable']['quoted'] = true;
  1146.                 }
  1147.             }
  1148.         } else {
  1149.             $mapping['isOwningSide'] = false;
  1150.         }
  1151.         if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
  1152.             throw MappingException::illegalToManyIdentifierAssociation($this->name$mapping['fieldName']);
  1153.         }
  1154.         // Fetch mode. Default fetch mode to LAZY, if not set.
  1155.         if (! isset($mapping['fetch'])) {
  1156.             $mapping['fetch'] = self::FETCH_LAZY;
  1157.         }
  1158.         // Cascades
  1159.         $cascades = isset($mapping['cascade']) ? array_map('strtolower'$mapping['cascade']) : [];
  1160.         $allCascades = ['remove''persist''refresh''detach'];
  1161.         if (in_array('all'$cascadestrue)) {
  1162.             $cascades $allCascades;
  1163.         } elseif (count($cascades) !== count(array_intersect($cascades$allCascades))) {
  1164.             throw MappingException::invalidCascadeOption(
  1165.                 array_diff($cascades$allCascades),
  1166.                 $this->name,
  1167.                 $mapping['fieldName'],
  1168.             );
  1169.         }
  1170.         $mapping['cascade'] = $cascades;
  1171.         switch ($mapping['type']) {
  1172.             case self::ONE_TO_ONE:
  1173.                 if (isset($mapping['joinColumns']) && $mapping['joinColumns'] && ! $mapping['isOwningSide']) {
  1174.                     throw MappingException::joinColumnNotAllowedOnOneToOneInverseSide(
  1175.                         $this->name,
  1176.                         $mapping['fieldName'],
  1177.                     );
  1178.                 }
  1179.                 return $mapping['isOwningSide'] ?
  1180.                     OneToOneOwningSideMapping::fromMappingArrayAndName(
  1181.                         $mapping,
  1182.                         $this->namingStrategy,
  1183.                         $this->name,
  1184.                         $this->table ?? null,
  1185.                         $this->isInheritanceTypeSingleTable(),
  1186.                     ) :
  1187.                     OneToOneInverseSideMapping::fromMappingArrayAndName($mapping$this->name);
  1188.             case self::MANY_TO_ONE:
  1189.                 return ManyToOneAssociationMapping::fromMappingArrayAndName(
  1190.                     $mapping,
  1191.                     $this->namingStrategy,
  1192.                     $this->name,
  1193.                     $this->table ?? null,
  1194.                     $this->isInheritanceTypeSingleTable(),
  1195.                 );
  1196.             case self::ONE_TO_MANY:
  1197.                 return OneToManyAssociationMapping::fromMappingArrayAndName($mapping$this->name);
  1198.             case self::MANY_TO_MANY:
  1199.                 if (isset($mapping['joinColumns'])) {
  1200.                     unset($mapping['joinColumns']);
  1201.                 }
  1202.                 return $mapping['isOwningSide'] ?
  1203.                     ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy($mapping$this->namingStrategy) :
  1204.                     ManyToManyInverseSideMapping::fromMappingArray($mapping);
  1205.             default:
  1206.                 throw MappingException::invalidAssociationType(
  1207.                     $this->name,
  1208.                     $mapping['fieldName'],
  1209.                     $mapping['type'],
  1210.                 );
  1211.         }
  1212.     }
  1213.     /**
  1214.      * {@inheritDoc}
  1215.      */
  1216.     public function getIdentifierFieldNames(): array
  1217.     {
  1218.         return $this->identifier;
  1219.     }
  1220.     /**
  1221.      * Gets the name of the single id field. Note that this only works on
  1222.      * entity classes that have a single-field pk.
  1223.      *
  1224.      * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
  1225.      */
  1226.     public function getSingleIdentifierFieldName(): string
  1227.     {
  1228.         if ($this->isIdentifierComposite) {
  1229.             throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
  1230.         }
  1231.         if (! isset($this->identifier[0])) {
  1232.             throw MappingException::noIdDefined($this->name);
  1233.         }
  1234.         return $this->identifier[0];
  1235.     }
  1236.     /**
  1237.      * Gets the column name of the single id column. Note that this only works on
  1238.      * entity classes that have a single-field pk.
  1239.      *
  1240.      * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
  1241.      */
  1242.     public function getSingleIdentifierColumnName(): string
  1243.     {
  1244.         return $this->getColumnName($this->getSingleIdentifierFieldName());
  1245.     }
  1246.     /**
  1247.      * INTERNAL:
  1248.      * Sets the mapped identifier/primary key fields of this class.
  1249.      * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
  1250.      *
  1251.      * @psalm-param list<mixed> $identifier
  1252.      */
  1253.     public function setIdentifier(array $identifier): void
  1254.     {
  1255.         $this->identifier            $identifier;
  1256.         $this->isIdentifierComposite = (count($this->identifier) > 1);
  1257.     }
  1258.     /**
  1259.      * {@inheritDoc}
  1260.      */
  1261.     public function getIdentifier(): array
  1262.     {
  1263.         return $this->identifier;
  1264.     }
  1265.     public function hasField(string $fieldName): bool
  1266.     {
  1267.         return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
  1268.     }
  1269.     /**
  1270.      * Gets an array containing all the column names.
  1271.      *
  1272.      * @psalm-param list<string>|null $fieldNames
  1273.      *
  1274.      * @return mixed[]
  1275.      * @psalm-return list<string>
  1276.      */
  1277.     public function getColumnNames(array|null $fieldNames null): array
  1278.     {
  1279.         if ($fieldNames === null) {
  1280.             return array_keys($this->fieldNames);
  1281.         }
  1282.         return array_values(array_map($this->getColumnName(...), $fieldNames));
  1283.     }
  1284.     /**
  1285.      * Returns an array with all the identifier column names.
  1286.      *
  1287.      * @psalm-return list<string>
  1288.      */
  1289.     public function getIdentifierColumnNames(): array
  1290.     {
  1291.         $columnNames = [];
  1292.         foreach ($this->identifier as $idProperty) {
  1293.             if (isset($this->fieldMappings[$idProperty])) {
  1294.                 $columnNames[] = $this->fieldMappings[$idProperty]->columnName;
  1295.                 continue;
  1296.             }
  1297.             // Association defined as Id field
  1298.             assert($this->associationMappings[$idProperty]->isToOneOwningSide());
  1299.             $joinColumns      $this->associationMappings[$idProperty]->joinColumns;
  1300.             $assocColumnNames array_map(static fn (JoinColumnMapping $joinColumn): string => $joinColumn->name$joinColumns);
  1301.             $columnNames array_merge($columnNames$assocColumnNames);
  1302.         }
  1303.         return $columnNames;
  1304.     }
  1305.     /**
  1306.      * Sets the type of Id generator to use for the mapped class.
  1307.      *
  1308.      * @psalm-param self::GENERATOR_TYPE_* $generatorType
  1309.      */
  1310.     public function setIdGeneratorType(int $generatorType): void
  1311.     {
  1312.         $this->generatorType $generatorType;
  1313.     }
  1314.     /**
  1315.      * Checks whether the mapped class uses an Id generator.
  1316.      */
  1317.     public function usesIdGenerator(): bool
  1318.     {
  1319.         return $this->generatorType !== self::GENERATOR_TYPE_NONE;
  1320.     }
  1321.     public function isInheritanceTypeNone(): bool
  1322.     {
  1323.         return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
  1324.     }
  1325.     /**
  1326.      * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
  1327.      *
  1328.      * @return bool TRUE if the class participates in a JOINED inheritance mapping,
  1329.      * FALSE otherwise.
  1330.      */
  1331.     public function isInheritanceTypeJoined(): bool
  1332.     {
  1333.         return $this->inheritanceType === self::INHERITANCE_TYPE_JOINED;
  1334.     }
  1335.     /**
  1336.      * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
  1337.      *
  1338.      * @return bool TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
  1339.      * FALSE otherwise.
  1340.      */
  1341.     public function isInheritanceTypeSingleTable(): bool
  1342.     {
  1343.         return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_TABLE;
  1344.     }
  1345.     /**
  1346.      * Checks whether the class uses an identity column for the Id generation.
  1347.      */
  1348.     public function isIdGeneratorIdentity(): bool
  1349.     {
  1350.         return $this->generatorType === self::GENERATOR_TYPE_IDENTITY;
  1351.     }
  1352.     /**
  1353.      * Checks whether the class uses a sequence for id generation.
  1354.      *
  1355.      * @psalm-assert-if-true !null $this->sequenceGeneratorDefinition
  1356.      */
  1357.     public function isIdGeneratorSequence(): bool
  1358.     {
  1359.         return $this->generatorType === self::GENERATOR_TYPE_SEQUENCE;
  1360.     }
  1361.     /**
  1362.      * Checks whether the class has a natural identifier/pk (which means it does
  1363.      * not use any Id generator.
  1364.      */
  1365.     public function isIdentifierNatural(): bool
  1366.     {
  1367.         return $this->generatorType === self::GENERATOR_TYPE_NONE;
  1368.     }
  1369.     /**
  1370.      * Gets the type of a field.
  1371.      *
  1372.      * @todo 3.0 Remove this. PersisterHelper should fix it somehow
  1373.      */
  1374.     public function getTypeOfField(string $fieldName): string|null
  1375.     {
  1376.         return isset($this->fieldMappings[$fieldName])
  1377.             ? $this->fieldMappings[$fieldName]->type
  1378.             null;
  1379.     }
  1380.     /**
  1381.      * Gets the name of the primary table.
  1382.      */
  1383.     public function getTableName(): string
  1384.     {
  1385.         return $this->table['name'];
  1386.     }
  1387.     /**
  1388.      * Gets primary table's schema name.
  1389.      */
  1390.     public function getSchemaName(): string|null
  1391.     {
  1392.         return $this->table['schema'] ?? null;
  1393.     }
  1394.     /**
  1395.      * Gets the table name to use for temporary identifier tables of this class.
  1396.      */
  1397.     public function getTemporaryIdTableName(): string
  1398.     {
  1399.         // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
  1400.         return str_replace('.''_'$this->getTableName() . '_id_tmp');
  1401.     }
  1402.     /**
  1403.      * Sets the mapped subclasses of this class.
  1404.      *
  1405.      * @psalm-param list<string> $subclasses The names of all mapped subclasses.
  1406.      */
  1407.     public function setSubclasses(array $subclasses): void
  1408.     {
  1409.         foreach ($subclasses as $subclass) {
  1410.             $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
  1411.         }
  1412.     }
  1413.     /**
  1414.      * Sets the parent class names. Only <em>entity</em> classes may be given.
  1415.      *
  1416.      * Assumes that the class names in the passed array are in the order:
  1417.      * directParent -> directParentParent -> directParentParentParent ... -> root.
  1418.      *
  1419.      * @psalm-param list<class-string> $classNames
  1420.      */
  1421.     public function setParentClasses(array $classNames): void
  1422.     {
  1423.         $this->parentClasses $classNames;
  1424.         if (count($classNames) > 0) {
  1425.             $this->rootEntityName array_pop($classNames);
  1426.         }
  1427.     }
  1428.     /**
  1429.      * Sets the inheritance type used by the class and its subclasses.
  1430.      *
  1431.      * @psalm-param self::INHERITANCE_TYPE_* $type
  1432.      *
  1433.      * @throws MappingException
  1434.      */
  1435.     public function setInheritanceType(int $type): void
  1436.     {
  1437.         if (! $this->isInheritanceType($type)) {
  1438.             throw MappingException::invalidInheritanceType($this->name$type);
  1439.         }
  1440.         $this->inheritanceType $type;
  1441.     }
  1442.     /**
  1443.      * Sets the association to override association mapping of property for an entity relationship.
  1444.      *
  1445.      * @psalm-param array<string, mixed> $overrideMapping
  1446.      *
  1447.      * @throws MappingException
  1448.      */
  1449.     public function setAssociationOverride(string $fieldName, array $overrideMapping): void
  1450.     {
  1451.         if (! isset($this->associationMappings[$fieldName])) {
  1452.             throw MappingException::invalidOverrideFieldName($this->name$fieldName);
  1453.         }
  1454.         $mapping $this->associationMappings[$fieldName]->toArray();
  1455.         if (isset($mapping['inherited'])) {
  1456.             throw MappingException::illegalOverrideOfInheritedProperty(
  1457.                 $this->name,
  1458.                 $fieldName,
  1459.                 $mapping['inherited'],
  1460.             );
  1461.         }
  1462.         if (isset($overrideMapping['joinColumns'])) {
  1463.             $mapping['joinColumns'] = $overrideMapping['joinColumns'];
  1464.         }
  1465.         if (isset($overrideMapping['inversedBy'])) {
  1466.             $mapping['inversedBy'] = $overrideMapping['inversedBy'];
  1467.         }
  1468.         if (isset($overrideMapping['joinTable'])) {
  1469.             $mapping['joinTable'] = $overrideMapping['joinTable'];
  1470.         }
  1471.         if (isset($overrideMapping['fetch'])) {
  1472.             $mapping['fetch'] = $overrideMapping['fetch'];
  1473.         }
  1474.         switch ($mapping['type']) {
  1475.             case self::ONE_TO_ONE:
  1476.             case self::MANY_TO_ONE:
  1477.                 $mapping['joinColumnFieldNames']     = [];
  1478.                 $mapping['sourceToTargetKeyColumns'] = [];
  1479.                 break;
  1480.             case self::MANY_TO_MANY:
  1481.                 $mapping['relationToSourceKeyColumns'] = [];
  1482.                 $mapping['relationToTargetKeyColumns'] = [];
  1483.                 break;
  1484.         }
  1485.         $this->associationMappings[$fieldName] = $this->_validateAndCompleteAssociationMapping($mapping);
  1486.     }
  1487.     /**
  1488.      * Sets the override for a mapped field.
  1489.      *
  1490.      * @psalm-param array<string, mixed> $overrideMapping
  1491.      *
  1492.      * @throws MappingException
  1493.      */
  1494.     public function setAttributeOverride(string $fieldName, array $overrideMapping): void
  1495.     {
  1496.         if (! isset($this->fieldMappings[$fieldName])) {
  1497.             throw MappingException::invalidOverrideFieldName($this->name$fieldName);
  1498.         }
  1499.         $mapping $this->fieldMappings[$fieldName];
  1500.         if (isset($mapping->inherited)) {
  1501.             throw MappingException::illegalOverrideOfInheritedProperty($this->name$fieldName$mapping->inherited);
  1502.         }
  1503.         if (isset($mapping->id)) {
  1504.             $overrideMapping['id'] = $mapping->id;
  1505.         }
  1506.         if (isset($mapping->declared)) {
  1507.             $overrideMapping['declared'] = $mapping->declared;
  1508.         }
  1509.         if (! isset($overrideMapping['type'])) {
  1510.             $overrideMapping['type'] = $mapping->type;
  1511.         }
  1512.         if (! isset($overrideMapping['fieldName'])) {
  1513.             $overrideMapping['fieldName'] = $mapping->fieldName;
  1514.         }
  1515.         if ($overrideMapping['type'] !== $mapping->type) {
  1516.             throw MappingException::invalidOverrideFieldType($this->name$fieldName);
  1517.         }
  1518.         unset($this->fieldMappings[$fieldName]);
  1519.         unset($this->fieldNames[$mapping->columnName]);
  1520.         unset($this->columnNames[$mapping->fieldName]);
  1521.         $overrideMapping $this->validateAndCompleteFieldMapping($overrideMapping);
  1522.         $this->fieldMappings[$fieldName] = $overrideMapping;
  1523.     }
  1524.     /**
  1525.      * Checks whether a mapped field is inherited from an entity superclass.
  1526.      */
  1527.     public function isInheritedField(string $fieldName): bool
  1528.     {
  1529.         return isset($this->fieldMappings[$fieldName]->inherited);
  1530.     }
  1531.     /**
  1532.      * Checks if this entity is the root in any entity-inheritance-hierarchy.
  1533.      */
  1534.     public function isRootEntity(): bool
  1535.     {
  1536.         return $this->name === $this->rootEntityName;
  1537.     }
  1538.     /**
  1539.      * Checks whether a mapped association field is inherited from a superclass.
  1540.      */
  1541.     public function isInheritedAssociation(string $fieldName): bool
  1542.     {
  1543.         return isset($this->associationMappings[$fieldName]->inherited);
  1544.     }
  1545.     public function isInheritedEmbeddedClass(string $fieldName): bool
  1546.     {
  1547.         return isset($this->embeddedClasses[$fieldName]->inherited);
  1548.     }
  1549.     /**
  1550.      * Sets the name of the primary table the class is mapped to.
  1551.      *
  1552.      * @deprecated Use {@link setPrimaryTable}.
  1553.      */
  1554.     public function setTableName(string $tableName): void
  1555.     {
  1556.         $this->table['name'] = $tableName;
  1557.     }
  1558.     /**
  1559.      * Sets the primary table definition. The provided array supports the
  1560.      * following structure:
  1561.      *
  1562.      * name => <tableName> (optional, defaults to class name)
  1563.      * indexes => array of indexes (optional)
  1564.      * uniqueConstraints => array of constraints (optional)
  1565.      *
  1566.      * If a key is omitted, the current value is kept.
  1567.      *
  1568.      * @psalm-param array<string, mixed> $table The table description.
  1569.      */
  1570.     public function setPrimaryTable(array $table): void
  1571.     {
  1572.         if (isset($table['name'])) {
  1573.             // Split schema and table name from a table name like "myschema.mytable"
  1574.             if (str_contains($table['name'], '.')) {
  1575.                 [$this->table['schema'], $table['name']] = explode('.'$table['name'], 2);
  1576.             }
  1577.             if ($table['name'][0] === '`') {
  1578.                 $table['name']         = trim($table['name'], '`');
  1579.                 $this->table['quoted'] = true;
  1580.             }
  1581.             $this->table['name'] = $table['name'];
  1582.         }
  1583.         if (isset($table['quoted'])) {
  1584.             $this->table['quoted'] = $table['quoted'];
  1585.         }
  1586.         if (isset($table['schema'])) {
  1587.             $this->table['schema'] = $table['schema'];
  1588.         }
  1589.         if (isset($table['indexes'])) {
  1590.             $this->table['indexes'] = $table['indexes'];
  1591.         }
  1592.         if (isset($table['uniqueConstraints'])) {
  1593.             $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
  1594.         }
  1595.         if (isset($table['options'])) {
  1596.             $this->table['options'] = $table['options'];
  1597.         }
  1598.     }
  1599.     /**
  1600.      * Checks whether the given type identifies an inheritance type.
  1601.      */
  1602.     private function isInheritanceType(int $type): bool
  1603.     {
  1604.         return $type === self::INHERITANCE_TYPE_NONE ||
  1605.                 $type === self::INHERITANCE_TYPE_SINGLE_TABLE ||
  1606.                 $type === self::INHERITANCE_TYPE_JOINED;
  1607.     }
  1608.     /**
  1609.      * Adds a mapped field to the class.
  1610.      *
  1611.      * @psalm-param array<string, mixed> $mapping The field mapping.
  1612.      *
  1613.      * @throws MappingException
  1614.      */
  1615.     public function mapField(array $mapping): void
  1616.     {
  1617.         $mapping $this->validateAndCompleteFieldMapping($mapping);
  1618.         $this->assertFieldNotMapped($mapping->fieldName);
  1619.         if (isset($mapping->generated)) {
  1620.             $this->requiresFetchAfterChange true;
  1621.         }
  1622.         $this->fieldMappings[$mapping->fieldName] = $mapping;
  1623.     }
  1624.     /**
  1625.      * INTERNAL:
  1626.      * Adds an association mapping without completing/validating it.
  1627.      * This is mainly used to add inherited association mappings to derived classes.
  1628.      *
  1629.      * @param ConcreteAssociationMapping $mapping
  1630.      *
  1631.      * @throws MappingException
  1632.      */
  1633.     public function addInheritedAssociationMapping(AssociationMapping $mapping/*, $owningClassName = null*/): void
  1634.     {
  1635.         if (isset($this->associationMappings[$mapping->fieldName])) {
  1636.             throw MappingException::duplicateAssociationMapping($this->name$mapping->fieldName);
  1637.         }
  1638.         $this->associationMappings[$mapping->fieldName] = $mapping;
  1639.     }
  1640.     /**
  1641.      * INTERNAL:
  1642.      * Adds a field mapping without completing/validating it.
  1643.      * This is mainly used to add inherited field mappings to derived classes.
  1644.      */
  1645.     public function addInheritedFieldMapping(FieldMapping $fieldMapping): void
  1646.     {
  1647.         $this->fieldMappings[$fieldMapping->fieldName] = $fieldMapping;
  1648.         $this->columnNames[$fieldMapping->fieldName]   = $fieldMapping->columnName;
  1649.         $this->fieldNames[$fieldMapping->columnName]   = $fieldMapping->fieldName;
  1650.         if (isset($fieldMapping->generated)) {
  1651.             $this->requiresFetchAfterChange true;
  1652.         }
  1653.     }
  1654.     /**
  1655.      * Adds a one-to-one mapping.
  1656.      *
  1657.      * @param array<string, mixed> $mapping The mapping.
  1658.      */
  1659.     public function mapOneToOne(array $mapping): void
  1660.     {
  1661.         $mapping['type'] = self::ONE_TO_ONE;
  1662.         $mapping $this->_validateAndCompleteAssociationMapping($mapping);
  1663.         $this->_storeAssociationMapping($mapping);
  1664.     }
  1665.     /**
  1666.      * Adds a one-to-many mapping.
  1667.      *
  1668.      * @psalm-param array<string, mixed> $mapping The mapping.
  1669.      */
  1670.     public function mapOneToMany(array $mapping): void
  1671.     {
  1672.         $mapping['type'] = self::ONE_TO_MANY;
  1673.         $mapping $this->_validateAndCompleteAssociationMapping($mapping);
  1674.         $this->_storeAssociationMapping($mapping);
  1675.     }
  1676.     /**
  1677.      * Adds a many-to-one mapping.
  1678.      *
  1679.      * @psalm-param array<string, mixed> $mapping The mapping.
  1680.      */
  1681.     public function mapManyToOne(array $mapping): void
  1682.     {
  1683.         $mapping['type'] = self::MANY_TO_ONE;
  1684.         $mapping $this->_validateAndCompleteAssociationMapping($mapping);
  1685.         $this->_storeAssociationMapping($mapping);
  1686.     }
  1687.     /**
  1688.      * Adds a many-to-many mapping.
  1689.      *
  1690.      * @psalm-param array<string, mixed> $mapping The mapping.
  1691.      */
  1692.     public function mapManyToMany(array $mapping): void
  1693.     {
  1694.         $mapping['type'] = self::MANY_TO_MANY;
  1695.         $mapping $this->_validateAndCompleteAssociationMapping($mapping);
  1696.         $this->_storeAssociationMapping($mapping);
  1697.     }
  1698.     /**
  1699.      * Stores the association mapping.
  1700.      *
  1701.      * @param ConcreteAssociationMapping $assocMapping
  1702.      *
  1703.      * @throws MappingException
  1704.      */
  1705.     protected function _storeAssociationMapping(AssociationMapping $assocMapping): void
  1706.     {
  1707.         $sourceFieldName $assocMapping->fieldName;
  1708.         $this->assertFieldNotMapped($sourceFieldName);
  1709.         $this->associationMappings[$sourceFieldName] = $assocMapping;
  1710.     }
  1711.     /**
  1712.      * Registers a custom repository class for the entity class.
  1713.      *
  1714.      * @param string|null $repositoryClassName The class name of the custom mapper.
  1715.      * @psalm-param class-string<EntityRepository>|null $repositoryClassName
  1716.      */
  1717.     public function setCustomRepositoryClass(string|null $repositoryClassName): void
  1718.     {
  1719.         if ($repositoryClassName === null) {
  1720.             $this->customRepositoryClassName null;
  1721.             return;
  1722.         }
  1723.         $this->customRepositoryClassName $this->fullyQualifiedClassName($repositoryClassName);
  1724.     }
  1725.     /**
  1726.      * Dispatches the lifecycle event of the given entity to the registered
  1727.      * lifecycle callbacks and lifecycle listeners.
  1728.      *
  1729.      * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
  1730.      *
  1731.      * @param string $lifecycleEvent The lifecycle event.
  1732.      */
  1733.     public function invokeLifecycleCallbacks(string $lifecycleEventobject $entity): void
  1734.     {
  1735.         foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
  1736.             $entity->$callback();
  1737.         }
  1738.     }
  1739.     /**
  1740.      * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
  1741.      */
  1742.     public function hasLifecycleCallbacks(string $lifecycleEvent): bool
  1743.     {
  1744.         return isset($this->lifecycleCallbacks[$lifecycleEvent]);
  1745.     }
  1746.     /**
  1747.      * Gets the registered lifecycle callbacks for an event.
  1748.      *
  1749.      * @return string[]
  1750.      * @psalm-return list<string>
  1751.      */
  1752.     public function getLifecycleCallbacks(string $event): array
  1753.     {
  1754.         return $this->lifecycleCallbacks[$event] ?? [];
  1755.     }
  1756.     /**
  1757.      * Adds a lifecycle callback for entities of this class.
  1758.      */
  1759.     public function addLifecycleCallback(string $callbackstring $event): void
  1760.     {
  1761.         if ($this->isEmbeddedClass) {
  1762.             throw MappingException::illegalLifecycleCallbackOnEmbeddedClass($callback$this->name);
  1763.         }
  1764.         if (isset($this->lifecycleCallbacks[$event]) && in_array($callback$this->lifecycleCallbacks[$event], true)) {
  1765.             return;
  1766.         }
  1767.         $this->lifecycleCallbacks[$event][] = $callback;
  1768.     }
  1769.     /**
  1770.      * Sets the lifecycle callbacks for entities of this class.
  1771.      * Any previously registered callbacks are overwritten.
  1772.      *
  1773.      * @psalm-param array<string, list<string>> $callbacks
  1774.      */
  1775.     public function setLifecycleCallbacks(array $callbacks): void
  1776.     {
  1777.         $this->lifecycleCallbacks $callbacks;
  1778.     }
  1779.     /**
  1780.      * Adds a entity listener for entities of this class.
  1781.      *
  1782.      * @param string $eventName The entity lifecycle event.
  1783.      * @param string $class     The listener class.
  1784.      * @param string $method    The listener callback method.
  1785.      *
  1786.      * @throws MappingException
  1787.      */
  1788.     public function addEntityListener(string $eventNamestring $classstring $method): void
  1789.     {
  1790.         $class $this->fullyQualifiedClassName($class);
  1791.         $listener = [
  1792.             'class'  => $class,
  1793.             'method' => $method,
  1794.         ];
  1795.         if (! class_exists($class)) {
  1796.             throw MappingException::entityListenerClassNotFound($class$this->name);
  1797.         }
  1798.         if (! method_exists($class$method)) {
  1799.             throw MappingException::entityListenerMethodNotFound($class$method$this->name);
  1800.         }
  1801.         if (isset($this->entityListeners[$eventName]) && in_array($listener$this->entityListeners[$eventName], true)) {
  1802.             throw MappingException::duplicateEntityListener($class$method$this->name);
  1803.         }
  1804.         $this->entityListeners[$eventName][] = $listener;
  1805.     }
  1806.     /**
  1807.      * Sets the discriminator column definition.
  1808.      *
  1809.      * @see getDiscriminatorColumn()
  1810.      *
  1811.      * @param DiscriminatorColumnMapping|mixed[]|null $columnDef
  1812.      * @psalm-param DiscriminatorColumnMapping|array{
  1813.      *     name: string|null,
  1814.      *     fieldName?: string|null,
  1815.      *     type?: string|null,
  1816.      *     length?: int|null,
  1817.      *     columnDefinition?: string|null,
  1818.      *     enumType?: class-string<BackedEnum>|null,
  1819.      *     options?: array<string, mixed>|null
  1820.      * }|null $columnDef
  1821.      *
  1822.      * @throws MappingException
  1823.      */
  1824.     public function setDiscriminatorColumn(DiscriminatorColumnMapping|array|null $columnDef): void
  1825.     {
  1826.         if ($columnDef instanceof DiscriminatorColumnMapping) {
  1827.             $this->discriminatorColumn $columnDef;
  1828.             return;
  1829.         }
  1830.         if ($columnDef !== null) {
  1831.             if (! isset($columnDef['name'])) {
  1832.                 throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
  1833.             }
  1834.             if (isset($this->fieldNames[$columnDef['name']])) {
  1835.                 throw MappingException::duplicateColumnName($this->name$columnDef['name']);
  1836.             }
  1837.             $columnDef['fieldName'] ??= $columnDef['name'];
  1838.             $columnDef['type']      ??= 'string';
  1839.             $columnDef['options']   ??= [];
  1840.             if (in_array($columnDef['type'], ['boolean''array''object''datetime''time''date'], true)) {
  1841.                 throw MappingException::invalidDiscriminatorColumnType($this->name$columnDef['type']);
  1842.             }
  1843.             $this->discriminatorColumn DiscriminatorColumnMapping::fromMappingArray($columnDef);
  1844.         }
  1845.     }
  1846.     final public function getDiscriminatorColumn(): DiscriminatorColumnMapping
  1847.     {
  1848.         if ($this->discriminatorColumn === null) {
  1849.             throw new LogicException('The discriminator column was not set.');
  1850.         }
  1851.         return $this->discriminatorColumn;
  1852.     }
  1853.     /**
  1854.      * Sets the discriminator values used by this class.
  1855.      * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
  1856.      *
  1857.      * @param array<int|string, string> $map
  1858.      */
  1859.     public function setDiscriminatorMap(array $map): void
  1860.     {
  1861.         foreach ($map as $value => $className) {
  1862.             $this->addDiscriminatorMapClass($value$className);
  1863.         }
  1864.     }
  1865.     /**
  1866.      * Adds one entry of the discriminator map with a new class and corresponding name.
  1867.      *
  1868.      * @throws MappingException
  1869.      */
  1870.     public function addDiscriminatorMapClass(int|string $namestring $className): void
  1871.     {
  1872.         $className $this->fullyQualifiedClassName($className);
  1873.         $className ltrim($className'\\');
  1874.         $this->discriminatorMap[$name] = $className;
  1875.         if ($this->name === $className) {
  1876.             $this->discriminatorValue $name;
  1877.             return;
  1878.         }
  1879.         if (! (class_exists($className) || interface_exists($className))) {
  1880.             throw MappingException::invalidClassInDiscriminatorMap($className$this->name);
  1881.         }
  1882.         $this->addSubClass($className);
  1883.     }
  1884.     /** @param array<class-string> $classes */
  1885.     public function addSubClasses(array $classes): void
  1886.     {
  1887.         foreach ($classes as $className) {
  1888.             $this->addSubClass($className);
  1889.         }
  1890.     }
  1891.     public function addSubClass(string $className): void
  1892.     {
  1893.         // By ignoring classes that are not subclasses of the current class, we simplify inheriting
  1894.         // the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata.
  1895.         if (is_subclass_of($className$this->name) && ! in_array($className$this->subClassestrue)) {
  1896.             $this->subClasses[] = $className;
  1897.         }
  1898.     }
  1899.     public function hasAssociation(string $fieldName): bool
  1900.     {
  1901.         return isset($this->associationMappings[$fieldName]);
  1902.     }
  1903.     public function isSingleValuedAssociation(string $fieldName): bool
  1904.     {
  1905.         return isset($this->associationMappings[$fieldName])
  1906.             && ($this->associationMappings[$fieldName]->isToOne());
  1907.     }
  1908.     public function isCollectionValuedAssociation(string $fieldName): bool
  1909.     {
  1910.         return isset($this->associationMappings[$fieldName])
  1911.             && ! $this->associationMappings[$fieldName]->isToOne();
  1912.     }
  1913.     /**
  1914.      * Is this an association that only has a single join column?
  1915.      */
  1916.     public function isAssociationWithSingleJoinColumn(string $fieldName): bool
  1917.     {
  1918.         return isset($this->associationMappings[$fieldName])
  1919.             && isset($this->associationMappings[$fieldName]->joinColumns[0])
  1920.             && ! isset($this->associationMappings[$fieldName]->joinColumns[1]);
  1921.     }
  1922.     /**
  1923.      * Returns the single association join column (if any).
  1924.      *
  1925.      * @throws MappingException
  1926.      */
  1927.     public function getSingleAssociationJoinColumnName(string $fieldName): string
  1928.     {
  1929.         if (! $this->isAssociationWithSingleJoinColumn($fieldName)) {
  1930.             throw MappingException::noSingleAssociationJoinColumnFound($this->name$fieldName);
  1931.         }
  1932.         $assoc $this->associationMappings[$fieldName];
  1933.         assert($assoc->isToOneOwningSide());
  1934.         return $assoc->joinColumns[0]->name;
  1935.     }
  1936.     /**
  1937.      * Returns the single association referenced join column name (if any).
  1938.      *
  1939.      * @throws MappingException
  1940.      */
  1941.     public function getSingleAssociationReferencedJoinColumnName(string $fieldName): string
  1942.     {
  1943.         if (! $this->isAssociationWithSingleJoinColumn($fieldName)) {
  1944.             throw MappingException::noSingleAssociationJoinColumnFound($this->name$fieldName);
  1945.         }
  1946.         $assoc $this->associationMappings[$fieldName];
  1947.         assert($assoc->isToOneOwningSide());
  1948.         return $assoc->joinColumns[0]->referencedColumnName;
  1949.     }
  1950.     /**
  1951.      * Used to retrieve a fieldname for either field or association from a given column.
  1952.      *
  1953.      * This method is used in foreign-key as primary-key contexts.
  1954.      *
  1955.      * @throws MappingException
  1956.      */
  1957.     public function getFieldForColumn(string $columnName): string
  1958.     {
  1959.         if (isset($this->fieldNames[$columnName])) {
  1960.             return $this->fieldNames[$columnName];
  1961.         }
  1962.         foreach ($this->associationMappings as $assocName => $mapping) {
  1963.             if (
  1964.                 $this->isAssociationWithSingleJoinColumn($assocName) &&
  1965.                 assert($this->associationMappings[$assocName]->isToOneOwningSide()) &&
  1966.                 $this->associationMappings[$assocName]->joinColumns[0]->name === $columnName
  1967.             ) {
  1968.                 return $assocName;
  1969.             }
  1970.         }
  1971.         throw MappingException::noFieldNameFoundForColumn($this->name$columnName);
  1972.     }
  1973.     /**
  1974.      * Sets the ID generator used to generate IDs for instances of this class.
  1975.      */
  1976.     public function setIdGenerator(AbstractIdGenerator $generator): void
  1977.     {
  1978.         $this->idGenerator $generator;
  1979.     }
  1980.     /**
  1981.      * Sets definition.
  1982.      *
  1983.      * @psalm-param array<string, string|null> $definition
  1984.      */
  1985.     public function setCustomGeneratorDefinition(array $definition): void
  1986.     {
  1987.         $this->customGeneratorDefinition $definition;
  1988.     }
  1989.     /**
  1990.      * Sets the definition of the sequence ID generator for this class.
  1991.      *
  1992.      * The definition must have the following structure:
  1993.      * <code>
  1994.      * array(
  1995.      *     'sequenceName'   => 'name',
  1996.      *     'allocationSize' => 20,
  1997.      *     'initialValue'   => 1
  1998.      *     'quoted'         => 1
  1999.      * )
  2000.      * </code>
  2001.      *
  2002.      * @psalm-param array{sequenceName?: string, allocationSize?: int|string, initialValue?: int|string, quoted?: mixed} $definition
  2003.      *
  2004.      * @throws MappingException
  2005.      */
  2006.     public function setSequenceGeneratorDefinition(array $definition): void
  2007.     {
  2008.         if (! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') {
  2009.             throw MappingException::missingSequenceName($this->name);
  2010.         }
  2011.         if ($definition['sequenceName'][0] === '`') {
  2012.             $definition['sequenceName'] = trim($definition['sequenceName'], '`');
  2013.             $definition['quoted']       = true;
  2014.         }
  2015.         if (! isset($definition['allocationSize']) || trim((string) $definition['allocationSize']) === '') {
  2016.             $definition['allocationSize'] = '1';
  2017.         }
  2018.         if (! isset($definition['initialValue']) || trim((string) $definition['initialValue']) === '') {
  2019.             $definition['initialValue'] = '1';
  2020.         }
  2021.         $definition['allocationSize'] = (string) $definition['allocationSize'];
  2022.         $definition['initialValue']   = (string) $definition['initialValue'];
  2023.         $this->sequenceGeneratorDefinition $definition;
  2024.     }
  2025.     /**
  2026.      * Sets the version field mapping used for versioning. Sets the default
  2027.      * value to use depending on the column type.
  2028.      *
  2029.      * @psalm-param array<string, mixed> $mapping The version field mapping array.
  2030.      *
  2031.      * @throws MappingException
  2032.      */
  2033.     public function setVersionMapping(array &$mapping): void
  2034.     {
  2035.         $this->isVersioned              true;
  2036.         $this->versionField             $mapping['fieldName'];
  2037.         $this->requiresFetchAfterChange true;
  2038.         if (! isset($mapping['default'])) {
  2039.             if (in_array($mapping['type'], ['integer''bigint''smallint'], true)) {
  2040.                 $mapping['default'] = 1;
  2041.             } elseif ($mapping['type'] === 'datetime') {
  2042.                 $mapping['default'] = 'CURRENT_TIMESTAMP';
  2043.             } else {
  2044.                 throw MappingException::unsupportedOptimisticLockingType($this->name$mapping['fieldName'], $mapping['type']);
  2045.             }
  2046.         }
  2047.     }
  2048.     /**
  2049.      * Sets whether this class is to be versioned for optimistic locking.
  2050.      */
  2051.     public function setVersioned(bool $bool): void
  2052.     {
  2053.         $this->isVersioned $bool;
  2054.         if ($bool) {
  2055.             $this->requiresFetchAfterChange true;
  2056.         }
  2057.     }
  2058.     /**
  2059.      * Sets the name of the field that is to be used for versioning if this class is
  2060.      * versioned for optimistic locking.
  2061.      */
  2062.     public function setVersionField(string|null $versionField): void
  2063.     {
  2064.         $this->versionField $versionField;
  2065.     }
  2066.     /**
  2067.      * Marks this class as read only, no change tracking is applied to it.
  2068.      */
  2069.     public function markReadOnly(): void
  2070.     {
  2071.         $this->isReadOnly true;
  2072.     }
  2073.     /**
  2074.      * {@inheritDoc}
  2075.      */
  2076.     public function getFieldNames(): array
  2077.     {
  2078.         return array_keys($this->fieldMappings);
  2079.     }
  2080.     /**
  2081.      * {@inheritDoc}
  2082.      */
  2083.     public function getAssociationNames(): array
  2084.     {
  2085.         return array_keys($this->associationMappings);
  2086.     }
  2087.     /**
  2088.      * {@inheritDoc}
  2089.      *
  2090.      * @psalm-return class-string
  2091.      *
  2092.      * @throws InvalidArgumentException
  2093.      */
  2094.     public function getAssociationTargetClass(string $assocName): string
  2095.     {
  2096.         return $this->associationMappings[$assocName]->targetEntity
  2097.             ?? throw new InvalidArgumentException("Association name expected, '" $assocName "' is not an association.");
  2098.     }
  2099.     public function getName(): string
  2100.     {
  2101.         return $this->name;
  2102.     }
  2103.     public function isAssociationInverseSide(string $assocName): bool
  2104.     {
  2105.         return isset($this->associationMappings[$assocName])
  2106.             && ! $this->associationMappings[$assocName]->isOwningSide();
  2107.     }
  2108.     public function getAssociationMappedByTargetField(string $assocName): string
  2109.     {
  2110.         $assoc $this->getAssociationMapping($assocName);
  2111.         if (! $assoc instanceof InverseSideMapping) {
  2112.             throw new LogicException(sprintf(
  2113.                 <<<'EXCEPTION'
  2114.                 Context: Calling %s() with "%s", which is the owning side of an association.
  2115.                 Problem: The owning side of an association has no "mappedBy" field.
  2116.                 Solution: Call %s::isAssociationInverseSide() to check first.
  2117.                 EXCEPTION,
  2118.                 __METHOD__,
  2119.                 $assocName,
  2120.                 self::class,
  2121.             ));
  2122.         }
  2123.         return $assoc->mappedBy;
  2124.     }
  2125.     /**
  2126.      * @param C $className
  2127.      *
  2128.      * @return string|null null if and only if the input value is null
  2129.      * @psalm-return (C is class-string ? class-string : (C is string ? string : null))
  2130.      *
  2131.      * @template C of string|null
  2132.      */
  2133.     public function fullyQualifiedClassName(string|null $className): string|null
  2134.     {
  2135.         if ($className === null) {
  2136.             Deprecation::trigger(
  2137.                 'doctrine/orm',
  2138.                 'https://github.com/doctrine/orm/pull/11294',
  2139.                 'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0',
  2140.                 __METHOD__,
  2141.             );
  2142.             return null;
  2143.         }
  2144.         if (! str_contains($className'\\') && $this->namespace) {
  2145.             return $this->namespace '\\' $className;
  2146.         }
  2147.         return $className;
  2148.     }
  2149.     public function getMetadataValue(string $name): mixed
  2150.     {
  2151.         return $this->$name ?? null;
  2152.     }
  2153.     /**
  2154.      * Map Embedded Class
  2155.      *
  2156.      * @psalm-param array{
  2157.      *     fieldName: string,
  2158.      *     class?: class-string,
  2159.      *     declaredField?: string,
  2160.      *     columnPrefix?: string|false|null,
  2161.      *     originalField?: string
  2162.      * } $mapping
  2163.      *
  2164.      * @throws MappingException
  2165.      */
  2166.     public function mapEmbedded(array $mapping): void
  2167.     {
  2168.         $this->assertFieldNotMapped($mapping['fieldName']);
  2169.         if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) {
  2170.             $type $this->reflClass->getProperty($mapping['fieldName'])->getType();
  2171.             if ($type instanceof ReflectionNamedType) {
  2172.                 $mapping['class'] = $type->getName();
  2173.             }
  2174.         }
  2175.         if (! (isset($mapping['class']) && $mapping['class'])) {
  2176.             throw MappingException::missingEmbeddedClass($mapping['fieldName']);
  2177.         }
  2178.         $this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([
  2179.             'class' => $this->fullyQualifiedClassName($mapping['class']),
  2180.             'columnPrefix' => $mapping['columnPrefix'] ?? null,
  2181.             'declaredField' => $mapping['declaredField'] ?? null,
  2182.             'originalField' => $mapping['originalField'] ?? null,
  2183.         ]);
  2184.     }
  2185.     /**
  2186.      * Inline the embeddable class
  2187.      */
  2188.     public function inlineEmbeddable(string $propertyClassMetadata $embeddable): void
  2189.     {
  2190.         foreach ($embeddable->fieldMappings as $originalFieldMapping) {
  2191.             $fieldMapping                    = (array) $originalFieldMapping;
  2192.             $fieldMapping['originalClass'] ??= $embeddable->name;
  2193.             $fieldMapping['declaredField']   = isset($fieldMapping['declaredField'])
  2194.                 ? $property '.' $fieldMapping['declaredField']
  2195.                 : $property;
  2196.             $fieldMapping['originalField'] ??= $fieldMapping['fieldName'];
  2197.             $fieldMapping['fieldName']       = $property '.' $fieldMapping['fieldName'];
  2198.             if (! empty($this->embeddedClasses[$property]->columnPrefix)) {
  2199.                 $fieldMapping['columnName'] = $this->embeddedClasses[$property]->columnPrefix $fieldMapping['columnName'];
  2200.             } elseif ($this->embeddedClasses[$property]->columnPrefix !== false) {
  2201.                 assert($this->reflClass !== null);
  2202.                 assert($embeddable->reflClass !== null);
  2203.                 $fieldMapping['columnName'] = $this->namingStrategy
  2204.                     ->embeddedFieldToColumnName(
  2205.                         $property,
  2206.                         $fieldMapping['columnName'],
  2207.                         $this->reflClass->name,
  2208.                         $embeddable->reflClass->name,
  2209.                     );
  2210.             }
  2211.             $this->mapField($fieldMapping);
  2212.         }
  2213.     }
  2214.     /** @throws MappingException */
  2215.     private function assertFieldNotMapped(string $fieldName): void
  2216.     {
  2217.         if (
  2218.             isset($this->fieldMappings[$fieldName]) ||
  2219.             isset($this->associationMappings[$fieldName]) ||
  2220.             isset($this->embeddedClasses[$fieldName])
  2221.         ) {
  2222.             throw MappingException::duplicateFieldMapping($this->name$fieldName);
  2223.         }
  2224.     }
  2225.     /**
  2226.      * Gets the sequence name based on class metadata.
  2227.      *
  2228.      * @todo Sequence names should be computed in DBAL depending on the platform
  2229.      */
  2230.     public function getSequenceName(AbstractPlatform $platform): string
  2231.     {
  2232.         $sequencePrefix $this->getSequencePrefix($platform);
  2233.         $columnName     $this->getSingleIdentifierColumnName();
  2234.         return $sequencePrefix '_' $columnName '_seq';
  2235.     }
  2236.     /**
  2237.      * Gets the sequence name prefix based on class metadata.
  2238.      *
  2239.      * @todo Sequence names should be computed in DBAL depending on the platform
  2240.      */
  2241.     public function getSequencePrefix(AbstractPlatform $platform): string
  2242.     {
  2243.         $tableName      $this->getTableName();
  2244.         $sequencePrefix $tableName;
  2245.         // Prepend the schema name to the table name if there is one
  2246.         $schemaName $this->getSchemaName();
  2247.         if ($schemaName) {
  2248.             $sequencePrefix $schemaName '.' $tableName;
  2249.         }
  2250.         return $sequencePrefix;
  2251.     }
  2252.     /** @psalm-param class-string $class */
  2253.     private function getAccessibleProperty(ReflectionService $reflServicestring $classstring $field): ReflectionProperty|null
  2254.     {
  2255.         $reflectionProperty $reflService->getAccessibleProperty($class$field);
  2256.         if ($reflectionProperty?->isReadOnly()) {
  2257.             $declaringClass $reflectionProperty->class;
  2258.             if ($declaringClass !== $class) {
  2259.                 $reflectionProperty $reflService->getAccessibleProperty($declaringClass$field);
  2260.             }
  2261.             if ($reflectionProperty !== null) {
  2262.                 $reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
  2263.             }
  2264.         }
  2265.         return $reflectionProperty;
  2266.     }
  2267. }