PageRenderTime 67ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php

http://github.com/doctrine/doctrine2
PHP | 3745 lines | 2131 code | 314 blank | 1300 comment | 137 complexity | f316fbe6d97995a1e1bf8318d79db696 MD5 | raw file
Possible License(s): Unlicense
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping;
  4. use BadMethodCallException;
  5. use DateInterval;
  6. use DateTime;
  7. use DateTimeImmutable;
  8. use Doctrine\DBAL\Platforms\AbstractPlatform;
  9. use Doctrine\DBAL\Types\Type;
  10. use Doctrine\DBAL\Types\Types;
  11. use Doctrine\Deprecations\Deprecation;
  12. use Doctrine\Instantiator\Instantiator;
  13. use Doctrine\Instantiator\InstantiatorInterface;
  14. use Doctrine\ORM\Cache\Exception\CacheException;
  15. use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
  16. use Doctrine\ORM\Id\AbstractIdGenerator;
  17. use Doctrine\Persistence\Mapping\ClassMetadata;
  18. use Doctrine\Persistence\Mapping\ReflectionService;
  19. use InvalidArgumentException;
  20. use ReflectionClass;
  21. use ReflectionNamedType;
  22. use ReflectionProperty;
  23. use RuntimeException;
  24. use function array_diff;
  25. use function array_flip;
  26. use function array_intersect;
  27. use function array_keys;
  28. use function array_map;
  29. use function array_merge;
  30. use function array_pop;
  31. use function array_values;
  32. use function assert;
  33. use function class_exists;
  34. use function count;
  35. use function explode;
  36. use function gettype;
  37. use function in_array;
  38. use function interface_exists;
  39. use function is_array;
  40. use function is_subclass_of;
  41. use function ltrim;
  42. use function method_exists;
  43. use function spl_object_id;
  44. use function str_replace;
  45. use function strpos;
  46. use function strtolower;
  47. use function trait_exists;
  48. use function trim;
  49. use const PHP_VERSION_ID;
  50. /**
  51. * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
  52. * of an entity and its associations.
  53. *
  54. * Once populated, ClassMetadata instances are usually cached in a serialized form.
  55. *
  56. * <b>IMPORTANT NOTE:</b>
  57. *
  58. * The fields of this class are only public for 2 reasons:
  59. * 1) To allow fast READ access.
  60. * 2) To drastically reduce the size of a serialized instance (private/protected members
  61. * get the whole class name, namespace inclusive, prepended to every property in
  62. * the serialized representation).
  63. *
  64. * @template-covariant T of object
  65. * @template-implements ClassMetadata<T>
  66. */
  67. class ClassMetadataInfo implements ClassMetadata
  68. {
  69. /* The inheritance mapping types */
  70. /**
  71. * NONE means the class does not participate in an inheritance hierarchy
  72. * and therefore does not need an inheritance mapping type.
  73. */
  74. public const INHERITANCE_TYPE_NONE = 1;
  75. /**
  76. * JOINED means the class will be persisted according to the rules of
  77. * <tt>Class Table Inheritance</tt>.
  78. */
  79. public const INHERITANCE_TYPE_JOINED = 2;
  80. /**
  81. * SINGLE_TABLE means the class will be persisted according to the rules of
  82. * <tt>Single Table Inheritance</tt>.
  83. */
  84. public const INHERITANCE_TYPE_SINGLE_TABLE = 3;
  85. /**
  86. * TABLE_PER_CLASS means the class will be persisted according to the rules
  87. * of <tt>Concrete Table Inheritance</tt>.
  88. */
  89. public const INHERITANCE_TYPE_TABLE_PER_CLASS = 4;
  90. /* The Id generator types. */
  91. /**
  92. * AUTO means the generator type will depend on what the used platform prefers.
  93. * Offers full portability.
  94. */
  95. public const GENERATOR_TYPE_AUTO = 1;
  96. /**
  97. * SEQUENCE means a separate sequence object will be used. Platforms that do
  98. * not have native sequence support may emulate it. Full portability is currently
  99. * not guaranteed.
  100. */
  101. public const GENERATOR_TYPE_SEQUENCE = 2;
  102. /**
  103. * TABLE means a separate table is used for id generation.
  104. * Offers full portability (in that it results in an exception being thrown
  105. * no matter the platform).
  106. *
  107. * @deprecated no replacement planned
  108. */
  109. public const GENERATOR_TYPE_TABLE = 3;
  110. /**
  111. * IDENTITY means an identity column is used for id generation. The database
  112. * will fill in the id column on insertion. Platforms that do not support
  113. * native identity columns may emulate them. Full portability is currently
  114. * not guaranteed.
  115. */
  116. public const GENERATOR_TYPE_IDENTITY = 4;
  117. /**
  118. * NONE means the class does not have a generated id. That means the class
  119. * must have a natural, manually assigned id.
  120. */
  121. public const GENERATOR_TYPE_NONE = 5;
  122. /**
  123. * UUID means that a UUID/GUID expression is used for id generation. Full
  124. * portability is currently not guaranteed.
  125. *
  126. * @deprecated use an application-side generator instead
  127. */
  128. public const GENERATOR_TYPE_UUID = 6;
  129. /**
  130. * CUSTOM means that customer will use own ID generator that supposedly work
  131. */
  132. public const GENERATOR_TYPE_CUSTOM = 7;
  133. /**
  134. * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
  135. * by doing a property-by-property comparison with the original data. This will
  136. * be done for all entities that are in MANAGED state at commit-time.
  137. *
  138. * This is the default change tracking policy.
  139. */
  140. public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
  141. /**
  142. * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
  143. * by doing a property-by-property comparison with the original data. This will
  144. * be done only for entities that were explicitly saved (through persist() or a cascade).
  145. */
  146. public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
  147. /**
  148. * NOTIFY means that Doctrine relies on the entities sending out notifications
  149. * when their properties change. Such entity classes must implement
  150. * the <tt>NotifyPropertyChanged</tt> interface.
  151. */
  152. public const CHANGETRACKING_NOTIFY = 3;
  153. /**
  154. * Specifies that an association is to be fetched when it is first accessed.
  155. */
  156. public const FETCH_LAZY = 2;
  157. /**
  158. * Specifies that an association is to be fetched when the owner of the
  159. * association is fetched.
  160. */
  161. public const FETCH_EAGER = 3;
  162. /**
  163. * Specifies that an association is to be fetched lazy (on first access) and that
  164. * commands such as Collection#count, Collection#slice are issued directly against
  165. * the database if the collection is not yet initialized.
  166. */
  167. public const FETCH_EXTRA_LAZY = 4;
  168. /**
  169. * Identifies a one-to-one association.
  170. */
  171. public const ONE_TO_ONE = 1;
  172. /**
  173. * Identifies a many-to-one association.
  174. */
  175. public const MANY_TO_ONE = 2;
  176. /**
  177. * Identifies a one-to-many association.
  178. */
  179. public const ONE_TO_MANY = 4;
  180. /**
  181. * Identifies a many-to-many association.
  182. */
  183. public const MANY_TO_MANY = 8;
  184. /**
  185. * Combined bitmask for to-one (single-valued) associations.
  186. */
  187. public const TO_ONE = 3;
  188. /**
  189. * Combined bitmask for to-many (collection-valued) associations.
  190. */
  191. public const TO_MANY = 12;
  192. /**
  193. * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
  194. */
  195. public const CACHE_USAGE_READ_ONLY = 1;
  196. /**
  197. * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes.
  198. */
  199. public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
  200. /**
  201. * Read Write Attempts to lock the entity before update/delete.
  202. */
  203. public const CACHE_USAGE_READ_WRITE = 3;
  204. /**
  205. * READ-ONLY: The name of the entity class.
  206. *
  207. * @var string
  208. * @psalm-var class-string<T>
  209. */
  210. public $name;
  211. /**
  212. * READ-ONLY: The namespace the entity class is contained in.
  213. *
  214. * @var string
  215. * @todo Not really needed. Usage could be localized.
  216. */
  217. public $namespace;
  218. /**
  219. * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance
  220. * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same
  221. * as {@link $name}.
  222. *
  223. * @var string
  224. * @psalm-var class-string
  225. */
  226. public $rootEntityName;
  227. /**
  228. * READ-ONLY: The definition of custom generator. Only used for CUSTOM
  229. * generator type
  230. *
  231. * The definition has the following structure:
  232. * <code>
  233. * array(
  234. * 'class' => 'ClassName',
  235. * )
  236. * </code>
  237. *
  238. * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
  239. * @var array<string, string>|null
  240. */
  241. public $customGeneratorDefinition;
  242. /**
  243. * The name of the custom repository class used for the entity class.
  244. * (Optional).
  245. *
  246. * @var string|null
  247. * @psalm-var ?class-string
  248. */
  249. public $customRepositoryClassName;
  250. /**
  251. * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
  252. *
  253. * @var bool
  254. */
  255. public $isMappedSuperclass = false;
  256. /**
  257. * READ-ONLY: Whether this class describes the mapping of an embeddable class.
  258. *
  259. * @var bool
  260. */
  261. public $isEmbeddedClass = false;
  262. /**
  263. * READ-ONLY: The names of the parent classes (ancestors).
  264. *
  265. * @psalm-var list<class-string>
  266. */
  267. public $parentClasses = [];
  268. /**
  269. * READ-ONLY: The names of all subclasses (descendants).
  270. *
  271. * @psalm-var list<class-string>
  272. */
  273. public $subClasses = [];
  274. /**
  275. * READ-ONLY: The names of all embedded classes based on properties.
  276. *
  277. * @psalm-var array<string, mixed[]>
  278. */
  279. public $embeddedClasses = [];
  280. /**
  281. * READ-ONLY: The named queries allowed to be called directly from Repository.
  282. *
  283. * @psalm-var array<string, array<string, mixed>>
  284. */
  285. public $namedQueries = [];
  286. /**
  287. * READ-ONLY: The named native queries allowed to be called directly from Repository.
  288. *
  289. * A native SQL named query definition has the following structure:
  290. * <pre>
  291. * array(
  292. * 'name' => <query name>,
  293. * 'query' => <sql query>,
  294. * 'resultClass' => <class of the result>,
  295. * 'resultSetMapping' => <name of a SqlResultSetMapping>
  296. * )
  297. * </pre>
  298. *
  299. * @psalm-var array<string, array<string, mixed>>
  300. */
  301. public $namedNativeQueries = [];
  302. /**
  303. * READ-ONLY: The mappings of the results of native SQL queries.
  304. *
  305. * A native result mapping definition has the following structure:
  306. * <pre>
  307. * array(
  308. * 'name' => <result name>,
  309. * 'entities' => array(<entity result mapping>),
  310. * 'columns' => array(<column result mapping>)
  311. * )
  312. * </pre>
  313. *
  314. * @psalm-var array<string, array{
  315. * name: string,
  316. * entities: mixed[],
  317. * columns: mixed[]
  318. * }>
  319. */
  320. public $sqlResultSetMappings = [];
  321. /**
  322. * READ-ONLY: The field names of all fields that are part of the identifier/primary key
  323. * of the mapped entity class.
  324. *
  325. * @psalm-var list<string>
  326. */
  327. public $identifier = [];
  328. /**
  329. * READ-ONLY: The inheritance mapping type used by the class.
  330. *
  331. * @var int
  332. * @psalm-var self::$INHERITANCE_TYPE_*
  333. */
  334. public $inheritanceType = self::INHERITANCE_TYPE_NONE;
  335. /**
  336. * READ-ONLY: The Id generator type used by the class.
  337. *
  338. * @var int
  339. */
  340. public $generatorType = self::GENERATOR_TYPE_NONE;
  341. /**
  342. * READ-ONLY: The field mappings of the class.
  343. * Keys are field names and values are mapping definitions.
  344. *
  345. * The mapping definition array has the following values:
  346. *
  347. * - <b>fieldName</b> (string)
  348. * The name of the field in the Entity.
  349. *
  350. * - <b>type</b> (string)
  351. * The type name of the mapped field. Can be one of Doctrine's mapping types
  352. * or a custom mapping type.
  353. *
  354. * - <b>columnName</b> (string, optional)
  355. * The column name. Optional. Defaults to the field name.
  356. *
  357. * - <b>length</b> (integer, optional)
  358. * The database length of the column. Optional. Default value taken from
  359. * the type.
  360. *
  361. * - <b>id</b> (boolean, optional)
  362. * Marks the field as the primary key of the entity. Multiple fields of an
  363. * entity can have the id attribute, forming a composite key.
  364. *
  365. * - <b>nullable</b> (boolean, optional)
  366. * Whether the column is nullable. Defaults to FALSE.
  367. *
  368. * - <b>columnDefinition</b> (string, optional, schema-only)
  369. * The SQL fragment that is used when generating the DDL for the column.
  370. *
  371. * - <b>precision</b> (integer, optional, schema-only)
  372. * The precision of a decimal column. Only valid if the column type is decimal.
  373. *
  374. * - <b>scale</b> (integer, optional, schema-only)
  375. * The scale of a decimal column. Only valid if the column type is decimal.
  376. *
  377. * - <b>'unique'</b> (string, optional, schema-only)
  378. * Whether a unique constraint should be generated for the column.
  379. *
  380. * @var mixed[]
  381. * @psalm-var array<string, array{
  382. * type: string,
  383. * fieldName: string,
  384. * columnName?: string,
  385. * length?: int,
  386. * id?: bool,
  387. * nullable?: bool,
  388. * columnDefinition?: string,
  389. * precision?: int,
  390. * scale?: int,
  391. * unique?: string,
  392. * inherited?: class-string,
  393. * originalClass?: class-string,
  394. * originalField?: string,
  395. * quoted?: bool,
  396. * requireSQLConversion?: bool,
  397. * declared?: class-string,
  398. * declaredField?: string,
  399. * options: array<mixed>
  400. * }>
  401. */
  402. public $fieldMappings = [];
  403. /**
  404. * READ-ONLY: An array of field names. Used to look up field names from column names.
  405. * Keys are column names and values are field names.
  406. *
  407. * @psalm-var array<string, string>
  408. */
  409. public $fieldNames = [];
  410. /**
  411. * READ-ONLY: A map of field names to column names. Keys are field names and values column names.
  412. * Used to look up column names from field names.
  413. * This is the reverse lookup map of $_fieldNames.
  414. *
  415. * @deprecated 3.0 Remove this.
  416. *
  417. * @var mixed[]
  418. */
  419. public $columnNames = [];
  420. /**
  421. * READ-ONLY: The discriminator value of this class.
  422. *
  423. * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
  424. * where a discriminator column is used.</b>
  425. *
  426. * @see discriminatorColumn
  427. *
  428. * @var mixed
  429. */
  430. public $discriminatorValue;
  431. /**
  432. * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
  433. *
  434. * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
  435. * where a discriminator column is used.</b>
  436. *
  437. * @see discriminatorColumn
  438. *
  439. * @var mixed
  440. */
  441. public $discriminatorMap = [];
  442. /**
  443. * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
  444. * inheritance mappings.
  445. *
  446. * @psalm-var array<string, mixed>
  447. */
  448. public $discriminatorColumn;
  449. /**
  450. * READ-ONLY: The primary table definition. The definition is an array with the
  451. * following entries:
  452. *
  453. * name => <tableName>
  454. * schema => <schemaName>
  455. * indexes => array
  456. * uniqueConstraints => array
  457. *
  458. * @var mixed[]
  459. * @psalm-var array{
  460. * name: string,
  461. * schema: string,
  462. * indexes: array,
  463. * uniqueConstraints: array,
  464. * options: array<string, mixed>,
  465. * quoted?: bool
  466. * }
  467. */
  468. public $table;
  469. /**
  470. * READ-ONLY: The registered lifecycle callbacks for entities of this class.
  471. *
  472. * @psalm-var array<string, list<string>>
  473. */
  474. public $lifecycleCallbacks = [];
  475. /**
  476. * READ-ONLY: The registered entity listeners.
  477. *
  478. * @psalm-var array<string, list<array{class: class-string, method: string}>>
  479. */
  480. public $entityListeners = [];
  481. /**
  482. * READ-ONLY: The association mappings of this class.
  483. *
  484. * The mapping definition array supports the following keys:
  485. *
  486. * - <b>fieldName</b> (string)
  487. * The name of the field in the entity the association is mapped to.
  488. *
  489. * - <b>targetEntity</b> (string)
  490. * The class name of the target entity. If it is fully-qualified it is used as is.
  491. * If it is a simple, unqualified class name the namespace is assumed to be the same
  492. * as the namespace of the source entity.
  493. *
  494. * - <b>mappedBy</b> (string, required for bidirectional associations)
  495. * The name of the field that completes the bidirectional association on the owning side.
  496. * This key must be specified on the inverse side of a bidirectional association.
  497. *
  498. * - <b>inversedBy</b> (string, required for bidirectional associations)
  499. * The name of the field that completes the bidirectional association on the inverse side.
  500. * This key must be specified on the owning side of a bidirectional association.
  501. *
  502. * - <b>cascade</b> (array, optional)
  503. * The names of persistence operations to cascade on the association. The set of possible
  504. * values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others).
  505. *
  506. * - <b>orderBy</b> (array, one-to-many/many-to-many only)
  507. * A map of field names (of the target entity) to sorting directions (ASC/DESC).
  508. * Example: array('priority' => 'desc')
  509. *
  510. * - <b>fetch</b> (integer, optional)
  511. * The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
  512. * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY.
  513. *
  514. * - <b>joinTable</b> (array, optional, many-to-many only)
  515. * Specification of the join table and its join columns (foreign keys).
  516. * Only valid for many-to-many mappings. Note that one-to-many associations can be mapped
  517. * through a join table by simply mapping the association as many-to-many with a unique
  518. * constraint on the join table.
  519. *
  520. * - <b>indexBy</b> (string, optional, to-many only)
  521. * Specification of a field on target-entity that is used to index the collection by.
  522. * This field HAS to be either the primary key or a unique column. Otherwise the collection
  523. * does not contain all the entities that are actually related.
  524. *
  525. * A join table definition has the following structure:
  526. * <pre>
  527. * array(
  528. * 'name' => <join table name>,
  529. * 'joinColumns' => array(<join column mapping from join table to source table>),
  530. * 'inverseJoinColumns' => array(<join column mapping from join table to target table>)
  531. * )
  532. * </pre>
  533. *
  534. * @psalm-var array<string, array<string, mixed>>
  535. */
  536. public $associationMappings = [];
  537. /**
  538. * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite.
  539. *
  540. * @var bool
  541. */
  542. public $isIdentifierComposite = false;
  543. /**
  544. * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association.
  545. *
  546. * This flag is necessary because some code blocks require special treatment of this cases.
  547. *
  548. * @var bool
  549. */
  550. public $containsForeignIdentifier = false;
  551. /**
  552. * READ-ONLY: The ID generator used for generating IDs for this class.
  553. *
  554. * @var AbstractIdGenerator
  555. * @todo Remove!
  556. */
  557. public $idGenerator;
  558. /**
  559. * READ-ONLY: The definition of the sequence generator of this class. Only used for the
  560. * SEQUENCE generation strategy.
  561. *
  562. * The definition has the following structure:
  563. * <code>
  564. * array(
  565. * 'sequenceName' => 'name',
  566. * 'allocationSize' => '20',
  567. * 'initialValue' => '1'
  568. * )
  569. * </code>
  570. *
  571. * @var array<string, mixed>
  572. * @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}
  573. * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
  574. */
  575. public $sequenceGeneratorDefinition;
  576. /**
  577. * READ-ONLY: The definition of the table generator of this class. Only used for the
  578. * TABLE generation strategy.
  579. *
  580. * @deprecated
  581. *
  582. * @var array<string, mixed>
  583. */
  584. public $tableGeneratorDefinition;
  585. /**
  586. * READ-ONLY: The policy used for change-tracking on entities of this class.
  587. *
  588. * @var int
  589. */
  590. public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
  591. /**
  592. * READ-ONLY: A flag for whether or not instances of this class are to be versioned
  593. * with optimistic locking.
  594. *
  595. * @var bool
  596. */
  597. public $isVersioned;
  598. /**
  599. * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
  600. *
  601. * @var mixed
  602. */
  603. public $versionField;
  604. /** @var mixed[] */
  605. public $cache = null;
  606. /**
  607. * The ReflectionClass instance of the mapped class.
  608. *
  609. * @var ReflectionClass|null
  610. */
  611. public $reflClass;
  612. /**
  613. * Is this entity marked as "read-only"?
  614. *
  615. * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance
  616. * optimization for entities that are immutable, either in your domain or through the relation database
  617. * (coming from a view, or a history table for example).
  618. *
  619. * @var bool
  620. */
  621. public $isReadOnly = false;
  622. /**
  623. * NamingStrategy determining the default column and table names.
  624. *
  625. * @var NamingStrategy
  626. */
  627. protected $namingStrategy;
  628. /**
  629. * The ReflectionProperty instances of the mapped class.
  630. *
  631. * @var ReflectionProperty[]|null[]
  632. */
  633. public $reflFields = [];
  634. /** @var InstantiatorInterface|null */
  635. private $instantiator;
  636. /**
  637. * Initializes a new ClassMetadata instance that will hold the object-relational mapping
  638. * metadata of the class with the given name.
  639. *
  640. * @param string $entityName The name of the entity class the new instance is used for.
  641. * @psalm-param class-string<T> $entityName
  642. */
  643. public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
  644. {
  645. $this->name = $entityName;
  646. $this->rootEntityName = $entityName;
  647. $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
  648. $this->instantiator = new Instantiator();
  649. }
  650. /**
  651. * Gets the ReflectionProperties of the mapped class.
  652. *
  653. * @return ReflectionProperty[]|null[] An array of ReflectionProperty instances.
  654. * @psalm-return array<ReflectionProperty|null>
  655. */
  656. public function getReflectionProperties()
  657. {
  658. return $this->reflFields;
  659. }
  660. /**
  661. * Gets a ReflectionProperty for a specific field of the mapped class.
  662. *
  663. * @param string $name
  664. *
  665. * @return ReflectionProperty
  666. */
  667. public function getReflectionProperty($name)
  668. {
  669. return $this->reflFields[$name];
  670. }
  671. /**
  672. * Gets the ReflectionProperty for the single identifier field.
  673. *
  674. * @return ReflectionProperty
  675. *
  676. * @throws BadMethodCallException If the class has a composite identifier.
  677. */
  678. public function getSingleIdReflectionProperty()
  679. {
  680. if ($this->isIdentifierComposite) {
  681. throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.');
  682. }
  683. return $this->reflFields[$this->identifier[0]];
  684. }
  685. /**
  686. * Extracts the identifier values of an entity of this class.
  687. *
  688. * For composite identifiers, the identifier values are returned as an array
  689. * with the same order as the field order in {@link identifier}.
  690. *
  691. * @param object $entity
  692. *
  693. * @return array<string, mixed>
  694. */
  695. public function getIdentifierValues($entity)
  696. {
  697. if ($this->isIdentifierComposite) {
  698. $id = [];
  699. foreach ($this->identifier as $idField) {
  700. $value = $this->reflFields[$idField]->getValue($entity);
  701. if ($value !== null) {
  702. $id[$idField] = $value;
  703. }
  704. }
  705. return $id;
  706. }
  707. $id = $this->identifier[0];
  708. $value = $this->reflFields[$id]->getValue($entity);
  709. if ($value === null) {
  710. return [];
  711. }
  712. return [$id => $value];
  713. }
  714. /**
  715. * Populates the entity identifier of an entity.
  716. *
  717. * @param object $entity
  718. * @psalm-param array<string, mixed> $id
  719. *
  720. * @return void
  721. *
  722. * @todo Rename to assignIdentifier()
  723. */
  724. public function setIdentifierValues($entity, array $id)
  725. {
  726. foreach ($id as $idField => $idValue) {
  727. $this->reflFields[$idField]->setValue($entity, $idValue);
  728. }
  729. }
  730. /**
  731. * Sets the specified field to the specified value on the given entity.
  732. *
  733. * @param object $entity
  734. * @param string $field
  735. * @param mixed $value
  736. *
  737. * @return void
  738. */
  739. public function setFieldValue($entity, $field, $value)
  740. {
  741. $this->reflFields[$field]->setValue($entity, $value);
  742. }
  743. /**
  744. * Gets the specified field's value off the given entity.
  745. *
  746. * @param object $entity
  747. * @param string $field
  748. *
  749. * @return mixed
  750. */
  751. public function getFieldValue($entity, $field)
  752. {
  753. return $this->reflFields[$field]->getValue($entity);
  754. }
  755. /**
  756. * Creates a string representation of this instance.
  757. *
  758. * @return string The string representation of this instance.
  759. *
  760. * @todo Construct meaningful string representation.
  761. */
  762. public function __toString()
  763. {
  764. return self::class . '@' . spl_object_id($this);
  765. }
  766. /**
  767. * Determines which fields get serialized.
  768. *
  769. * It is only serialized what is necessary for best unserialization performance.
  770. * That means any metadata properties that are not set or empty or simply have
  771. * their default value are NOT serialized.
  772. *
  773. * Parts that are also NOT serialized because they can not be properly unserialized:
  774. * - reflClass (ReflectionClass)
  775. * - reflFields (ReflectionProperty array)
  776. *
  777. * @return string[] The names of all the fields that should be serialized.
  778. */
  779. public function __sleep()
  780. {
  781. // This metadata is always serialized/cached.
  782. $serialized = [
  783. 'associationMappings',
  784. 'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName']
  785. 'fieldMappings',
  786. 'fieldNames',
  787. 'embeddedClasses',
  788. 'identifier',
  789. 'isIdentifierComposite', // TODO: REMOVE
  790. 'name',
  791. 'namespace', // TODO: REMOVE
  792. 'table',
  793. 'rootEntityName',
  794. 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime.
  795. ];
  796. // The rest of the metadata is only serialized if necessary.
  797. if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
  798. $serialized[] = 'changeTrackingPolicy';
  799. }
  800. if ($this->customRepositoryClassName) {
  801. $serialized[] = 'customRepositoryClassName';
  802. }
  803. if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE) {
  804. $serialized[] = 'inheritanceType';
  805. $serialized[] = 'discriminatorColumn';
  806. $serialized[] = 'discriminatorValue';
  807. $serialized[] = 'discriminatorMap';
  808. $serialized[] = 'parentClasses';
  809. $serialized[] = 'subClasses';
  810. }
  811. if ($this->generatorType !== self::GENERATOR_TYPE_NONE) {
  812. $serialized[] = 'generatorType';
  813. if ($this->generatorType === self::GENERATOR_TYPE_SEQUENCE) {
  814. $serialized[] = 'sequenceGeneratorDefinition';
  815. }
  816. }
  817. if ($this->isMappedSuperclass) {
  818. $serialized[] = 'isMappedSuperclass';
  819. }
  820. if ($this->isEmbeddedClass) {
  821. $serialized[] = 'isEmbeddedClass';
  822. }
  823. if ($this->containsForeignIdentifier) {
  824. $serialized[] = 'containsForeignIdentifier';
  825. }
  826. if ($this->isVersioned) {
  827. $serialized[] = 'isVersioned';
  828. $serialized[] = 'versionField';
  829. }
  830. if ($this->lifecycleCallbacks) {
  831. $serialized[] = 'lifecycleCallbacks';
  832. }
  833. if ($this->entityListeners) {
  834. $serialized[] = 'entityListeners';
  835. }
  836. if ($this->namedQueries) {
  837. $serialized[] = 'namedQueries';
  838. }
  839. if ($this->namedNativeQueries) {
  840. $serialized[] = 'namedNativeQueries';
  841. }
  842. if ($this->sqlResultSetMappings) {
  843. $serialized[] = 'sqlResultSetMappings';
  844. }
  845. if ($this->isReadOnly) {
  846. $serialized[] = 'isReadOnly';
  847. }
  848. if ($this->customGeneratorDefinition) {
  849. $serialized[] = 'customGeneratorDefinition';
  850. }
  851. if ($this->cache) {
  852. $serialized[] = 'cache';
  853. }
  854. return $serialized;
  855. }
  856. /**
  857. * Creates a new instance of the mapped class, without invoking the constructor.
  858. *
  859. * @return object
  860. */
  861. public function newInstance()
  862. {
  863. return $this->instantiator->instantiate($this->name);
  864. }
  865. /**
  866. * Restores some state that can not be serialized/unserialized.
  867. *
  868. * @param ReflectionService $reflService
  869. *
  870. * @return void
  871. */
  872. public function wakeupReflection($reflService)
  873. {
  874. // Restore ReflectionClass and properties
  875. $this->reflClass = $reflService->getClass($this->name);
  876. $this->instantiator = $this->instantiator ?: new Instantiator();
  877. $parentReflFields = [];
  878. foreach ($this->embeddedClasses as $property => $embeddedClass) {
  879. if (isset($embeddedClass['declaredField'])) {
  880. $childProperty = $reflService->getAccessibleProperty(
  881. $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
  882. $embeddedClass['originalField']
  883. );
  884. assert($childProperty !== null);
  885. $parentReflFields[$property] = new ReflectionEmbeddedProperty(
  886. $parentReflFields[$embeddedClass['declaredField']],
  887. $childProperty,
  888. $this->embeddedClasses[$embeddedClass['declaredField']]['class']
  889. );
  890. continue;
  891. }
  892. $fieldRefl = $reflService->getAccessibleProperty(
  893. $embeddedClass['declared'] ?? $this->name,
  894. $property
  895. );
  896. $parentReflFields[$property] = $fieldRefl;
  897. $this->reflFields[$property] = $fieldRefl;
  898. }
  899. foreach ($this->fieldMappings as $field => $mapping) {
  900. if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
  901. $this->reflFields[$field] = new ReflectionEmbeddedProperty(
  902. $parentReflFields[$mapping['declaredField']],
  903. $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
  904. $mapping['originalClass']
  905. );
  906. continue;
  907. }
  908. $this->reflFields[$field] = isset($mapping['declared'])
  909. ? $reflService->getAccessibleProperty($mapping['declared'], $field)
  910. : $reflService->getAccessibleProperty($this->name, $field);
  911. }
  912. foreach ($this->associationMappings as $field => $mapping) {
  913. $this->reflFields[$field] = isset($mapping['declared'])
  914. ? $reflService->getAccessibleProperty($mapping['declared'], $field)
  915. : $reflService->getAccessibleProperty($this->name, $field);
  916. }
  917. }
  918. /**
  919. * Initializes a new ClassMetadata instance that will hold the object-relational mapping
  920. * metadata of the class with the given name.
  921. *
  922. * @param ReflectionService $reflService The reflection service.
  923. *
  924. * @return void
  925. */
  926. public function initializeReflection($reflService)
  927. {
  928. $this->reflClass = $reflService->getClass($this->name);
  929. $this->namespace = $reflService->getClassNamespace($this->name);
  930. if ($this->reflClass) {
  931. $this->name = $this->rootEntityName = $this->reflClass->getName();
  932. }
  933. $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
  934. }
  935. /**
  936. * Validates Identifier.
  937. *
  938. * @return void
  939. *
  940. * @throws MappingException
  941. */
  942. public function validateIdentifier()
  943. {
  944. if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
  945. return;
  946. }
  947. // Verify & complete identifier mapping
  948. if (! $this->identifier) {
  949. throw MappingException::identifierRequired($this->name);
  950. }
  951. if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
  952. throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
  953. }
  954. }
  955. /**
  956. * Validates association targets actually exist.
  957. *
  958. * @return void
  959. *
  960. * @throws MappingException
  961. */
  962. public function validateAssociations()
  963. {
  964. foreach ($this->associationMappings as $mapping) {
  965. if (
  966. ! class_exists($mapping['targetEntity'])
  967. && ! interface_exists($mapping['targetEntity'])
  968. && ! trait_exists($mapping['targetEntity'])
  969. ) {
  970. throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
  971. }
  972. }
  973. }
  974. /**
  975. * Validates lifecycle callbacks.
  976. *
  977. * @param ReflectionService $reflService
  978. *
  979. * @return void
  980. *
  981. * @throws MappingException
  982. */
  983. public function validateLifecycleCallbacks($reflService)
  984. {
  985. foreach ($this->lifecycleCallbacks as $callbacks) {
  986. foreach ($callbacks as $callbackFuncName) {
  987. if (! $reflService->hasPublicMethod($this->name, $callbackFuncName)) {
  988. throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName);
  989. }
  990. }
  991. }
  992. }
  993. /**
  994. * {@inheritDoc}
  995. */
  996. public function getReflectionClass()
  997. {
  998. return $this->reflClass;
  999. }
  1000. /**
  1001. * @psalm-param array{usage?: mixed, region?: mixed} $cache
  1002. *
  1003. * @return void
  1004. */
  1005. public function enableCache(array $cache)
  1006. {
  1007. if (! isset($cache['usage'])) {
  1008. $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
  1009. }
  1010. if (! isset($cache['region'])) {
  1011. $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
  1012. }
  1013. $this->cache = $cache;
  1014. }
  1015. /**
  1016. * @param string $fieldName
  1017. * @psalm-param array{usage?: int, region?: string} $cache
  1018. *
  1019. * @return void
  1020. */
  1021. public function enableAssociationCache($fieldName, array $cache)
  1022. {
  1023. $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache);
  1024. }
  1025. /**
  1026. * @param string $fieldName
  1027. * @param array $cache
  1028. * @psalm-param array{usage?: int, region?: string|null} $cache
  1029. *
  1030. * @return int[]|string[]
  1031. * @psalm-return array{usage: int, region: string|null}
  1032. */
  1033. public function getAssociationCacheDefaults($fieldName, array $cache)
  1034. {
  1035. if (! isset($cache['usage'])) {
  1036. $cache['usage'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY;
  1037. }
  1038. if (! isset($cache['region'])) {
  1039. $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
  1040. }
  1041. return $cache;
  1042. }
  1043. /**
  1044. * Sets the change tracking policy used by this class.
  1045. *
  1046. * @param int $policy
  1047. *
  1048. * @return void
  1049. */
  1050. public function setChangeTrackingPolicy($policy)
  1051. {
  1052. $this->changeTrackingPolicy = $policy;
  1053. }
  1054. /**
  1055. * Whether the change tracking policy of this class is "deferred explicit".
  1056. *
  1057. * @return bool
  1058. */
  1059. public function isChangeTrackingDeferredExplicit()
  1060. {
  1061. return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
  1062. }
  1063. /**
  1064. * Whether the change tracking policy of this class is "deferred implicit".
  1065. *
  1066. * @return bool
  1067. */
  1068. public function isChangeTrackingDeferredImplicit()
  1069. {
  1070. return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
  1071. }
  1072. /**
  1073. * Whether the change tracking policy of this class is "notify".
  1074. *
  1075. * @return bool
  1076. */
  1077. public function isChangeTrackingNotify()
  1078. {
  1079. return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
  1080. }
  1081. /**
  1082. * Checks whether a field is part of the identifier/primary key field(s).
  1083. *
  1084. * @param string $fieldName The field name.
  1085. *
  1086. * @return bool TRUE if the field is part of the table identifier/primary key field(s),
  1087. * FALSE otherwise.
  1088. */
  1089. public function isIdentifier($fieldName)
  1090. {
  1091. if (! $this->identifier) {
  1092. return false;
  1093. }
  1094. if (! $this->isIdentifierComposite) {
  1095. return $fieldName === $this->identifier[0];
  1096. }
  1097. return in_array($fieldName, $this->identifier, true);
  1098. }
  1099. /**
  1100. * Checks if the field is unique.
  1101. *
  1102. * @param string $fieldName The field name.
  1103. *
  1104. * @return bool TRUE if the field is unique, FALSE otherwise.
  1105. */
  1106. public function isUniqueField($fieldName)
  1107. {
  1108. $mapping = $this->getFieldMapping($fieldName);
  1109. return $mapping !== false && isset($mapping['unique']) && $mapping['unique'];
  1110. }
  1111. /**
  1112. * Checks if the field is not null.
  1113. *
  1114. * @param string $fieldName The field name.
  1115. *
  1116. * @return bool TRUE if the field is not null, FALSE otherwise.
  1117. */
  1118. public function isNullable($fieldName)
  1119. {
  1120. $mapping = $this->getFieldMapping($fieldName);
  1121. return $mapping !== false && isset($mapping['nullable']) && $mapping['nullable'];
  1122. }
  1123. /**
  1124. * Gets a column name for a field name.
  1125. * If the column name for the field cannot be found, the given field name
  1126. * is returned.
  1127. *
  1128. * @param string $fieldName The field name.
  1129. *
  1130. * @return string The column name.
  1131. */
  1132. public function getColumnName($fieldName)
  1133. {
  1134. return $this->columnNames[$fieldName] ?? $fieldName;
  1135. }
  1136. /**
  1137. * Gets the mapping of a (regular) field that holds some data but not a
  1138. * reference to another object.
  1139. *
  1140. * @param string $fieldName The field name.
  1141. *
  1142. * @return mixed[] The field mapping.
  1143. * @psalm-return array{
  1144. * type: string,
  1145. * fieldName: string,
  1146. * columnName?: string,
  1147. * inherited?: class-string,
  1148. * nullable?: bool,
  1149. * originalClass?: class-string,
  1150. * originalField?: string,
  1151. * scale?: int,
  1152. * precision?: int,
  1153. * length?: int
  1154. * }
  1155. *
  1156. * @throws MappingException
  1157. */
  1158. public function getFieldMapping($fieldName)
  1159. {
  1160. if (! isset($this->fieldMappings[$fieldName])) {
  1161. throw MappingException::mappingNotFound($this->name, $fieldName);
  1162. }
  1163. return $this->fieldMappings[$fieldName];
  1164. }
  1165. /**
  1166. * Gets the mapping of an association.
  1167. *
  1168. * @see ClassMetadataInfo::$associationMappings
  1169. *
  1170. * @param string $fieldName The field name that represents the association in
  1171. * the object model.
  1172. *
  1173. * @return mixed[] The mapping.
  1174. * @psalm-return array<string, mixed>
  1175. *
  1176. * @throws MappingException
  1177. */
  1178. public function getAssociationMapping($fieldName)
  1179. {
  1180. if (! isset($this->associationMappings[$fieldName])) {
  1181. throw MappingException::mappingNotFound($this->name, $fieldName);
  1182. }
  1183. return $this->associationMappings[$fieldName];
  1184. }
  1185. /**
  1186. * Gets all association mappings of the class.
  1187. *
  1188. * @psalm-return array<string, array<string, mixed>>
  1189. */
  1190. public function getAssociationMappings()
  1191. {
  1192. return $this->associationMappings;
  1193. }
  1194. /**
  1195. * Gets the field name for a column name.
  1196. * If no field name can be found the column name is returned.
  1197. *
  1198. * @param string $columnName The column name.
  1199. *
  1200. * @return string The column alias.
  1201. */
  1202. public function getFieldName($columnName)
  1203. {
  1204. return $this->fieldNames[$columnName] ?? $columnName;
  1205. }
  1206. /**
  1207. * Gets the named query.
  1208. *
  1209. * @see ClassMetadataInfo::$namedQueries
  1210. *
  1211. * @param string $queryName The query name.
  1212. *
  1213. * @return string
  1214. *
  1215. * @throws MappingException
  1216. */
  1217. public function getNamedQuery($queryName)
  1218. {
  1219. if (! isset($this->namedQueries[$queryName])) {
  1220. throw MappingException::queryNotFound($this->name, $queryName);
  1221. }
  1222. return $this->namedQueries[$queryName]['dql'];
  1223. }
  1224. /**
  1225. * Gets all named queries of the class.
  1226. *
  1227. * @return mixed[][]
  1228. * @psalm-return array<string, array<string, mixed>>
  1229. */
  1230. public function getNamedQueries()
  1231. {
  1232. return $this->namedQueries;
  1233. }
  1234. /**
  1235. * Gets the named native query.
  1236. *
  1237. * @see ClassMetadataInfo::$namedNativeQueries
  1238. *
  1239. * @param string $queryName The query name.
  1240. *
  1241. * @return mixed[]
  1242. * @psalm-return array<string, mixed>
  1243. *
  1244. * @throws MappingException
  1245. */
  1246. public function getNamedNativeQuery($queryName)
  1247. {
  1248. if (! isset($this->namedNativeQueries[$queryName])) {
  1249. throw MappingException::queryNotFound($this->name, $queryName);
  1250. }
  1251. return $this->namedNativeQueries[$queryName];
  1252. }
  1253. /**
  1254. * Gets all named native queries of the class.
  1255. *
  1256. * @psalm-return array<string, array<string, mixed>>
  1257. */
  1258. public function getNamedNativeQueries()
  1259. {
  1260. return $this->namedNativeQueries;
  1261. }
  1262. /**
  1263. * Gets the result set mapping.
  1264. *
  1265. * @see ClassMetadataInfo::$sqlResultSetMappings
  1266. *
  1267. * @param string $name The result set mapping name.
  1268. *
  1269. * @return mixed[]
  1270. * @psalm-return array{name: string, entities: array, columns: array}
  1271. *
  1272. * @throws MappingException
  1273. */
  1274. public function getSqlResultSetMapping($name)
  1275. {
  1276. if (! isset($this->sqlResultSetMappings[$name])) {
  1277. throw MappingException::resultMappingNotFound($this->name, $name);
  1278. }
  1279. return $this->sqlResultSetMappings[$name];
  1280. }
  1281. /**
  1282. * Gets all sql result set mappings of the class.
  1283. *
  1284. * @return mixed[]
  1285. * @psalm-return array<string, array{name: string, entities: array, columns: array}>
  1286. */
  1287. public function getSqlResultSetMappings()
  1288. {
  1289. return $this->sqlResultSetMappings;
  1290. }
  1291. /**
  1292. * Checks whether given property has type
  1293. *
  1294. * @param string $name Property name
  1295. */
  1296. private function isTypedProperty(string $name): bool
  1297. {
  1298. return PHP_VERSION_ID >= 70400
  1299. && isset($this->reflClass)
  1300. && $this->reflClass->hasProperty($name)
  1301. && $this->reflClass->getProperty($name)->hasType();
  1302. }
  1303. /**
  1304. * Validates & completes the given field mapping based on typed property.
  1305. *
  1306. * @param mixed[] $mapping The field mapping to validate & complete.
  1307. *
  1308. * @return mixed[] The updated mapping.
  1309. */
  1310. private function validateAndCompleteTypedFieldMapping(array $mapping): array
  1311. {
  1312. $type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
  1313. if ($type) {
  1314. if (
  1315. ! isset($mapping['type'])
  1316. && ($type instanceof ReflectionNamedType)
  1317. ) {
  1318. switch ($type->getName()) {
  1319. case DateInterval::class:
  1320. $mapping['type'] = Types::DATEINTERVAL;
  1321. break;
  1322. case DateTime::class:
  1323. $mapping['type'] = Types::DATETIME_MUTABLE;
  1324. break;
  1325. case DateTimeImmutable::class:
  1326. $mapping['type'] = Types::DATETIME_IMMUTABLE;
  1327. break;
  1328. case 'array':
  1329. $mapping['type'] = Types::JSON;
  1330. break;
  1331. case 'bool':
  1332. $mapping['type'] = Types::BOOLEAN;
  1333. break;
  1334. case 'float':
  1335. $mapping['type'] = Types::FLOAT;
  1336. break;
  1337. case 'int':
  1338. $mapping['type'] = Types::INTEGER;
  1339. break;
  1340. case 'string':
  1341. $mapping['type'] = Types::STRING;
  1342. break;
  1343. }
  1344. }
  1345. }
  1346. return $mapping;
  1347. }
  1348. /**
  1349. * Validates & completes the basic mapping information based on typed property.
  1350. *
  1351. * @param mixed[] $mapping The mapping.
  1352. *
  1353. * @return mixed[] The updated mapping.
  1354. */
  1355. private function validateAndCompleteTypedAssociationMapping(array $mapping): array
  1356. {
  1357. $type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
  1358. if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) {
  1359. return $mapping;
  1360. }
  1361. if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) {
  1362. $mapping['targetEntity'] = $type->getName();
  1363. }
  1364. return $mapping;
  1365. }
  1366. /**
  1367. * Validates & completes the given field mapping.
  1368. *
  1369. * @psalm-param array<string, mixed> $mapping The field mapping to validate & complete.
  1370. *
  1371. * @return mixed[] The updated mapping.
  1372. *
  1373. * @throws MappingException
  1374. */
  1375. protected function validateAndCompleteFieldMapping(array $mapping): array
  1376. {
  1377. // Check mandatory fields
  1378. if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) {
  1379. throw MappingException::missingFieldName($this->name);
  1380. }
  1381. if ($this->isTypedProperty($mapping['fieldName'])) {
  1382. $mapping = $this->validateAndCompleteTypedFieldMapping($mapping);
  1383. }
  1384. if (! isset($mapping['type'])) {
  1385. // Default to string
  1386. $mapping['type'] = 'string';
  1387. }
  1388. // Complete fieldName and columnName mapping
  1389. if (! isset($mapping['columnName'])) {
  1390. $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
  1391. }
  1392. if ($mapping['columnName'][0] === '`') {
  1393. $mapping['columnName'] = trim($mapping['columnName'], '`');
  1394. $mapping['quoted'] = true;
  1395. }
  1396. $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];
  1397. if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn && $this->discriminatorColumn['name'] === $mapping['columnName'])) {
  1398. throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
  1399. }
  1400. $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
  1401. // Complete id mapping
  1402. if (isset($mapping['id']) && $mapping['id'] === true) {
  1403. if ($this->versionField === $mapping['fieldName']) {
  1404. throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
  1405. }
  1406. if (! in_array($mapping['fieldName'], $this->identifier, true)) {
  1407. $this->identifier[] = $mapping['fieldName'];
  1408. }
  1409. // Check for composite key
  1410. if (! $this->isIdentifierComposite && count($this->identifier) > 1) {
  1411. $this->isIdentifierComposite = true;
  1412. }
  1413. }
  1414. if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
  1415. if (isset($mapping['id']) && $mapping['id'] === true) {
  1416. throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
  1417. }
  1418. $mapping['requireSQLConversion'] = true;
  1419. }
  1420. return $mapping;
  1421. }
  1422. /**
  1423. * Validates & completes the basic mapping information that is common to all
  1424. * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
  1425. *
  1426. * @psalm-param array<string, mixed> $mapping The mapping.
  1427. *
  1428. * @return mixed[] The updated mapping.
  1429. * @psalm-return array{
  1430. * mappedBy: mixed|null,
  1431. * inversedBy: mixed|null,
  1432. * isOwningSide: bool,
  1433. * sourceEntity: class-string,
  1434. * targetEntity: string,
  1435. * fieldName: mixed,
  1436. * fetch: mixed,
  1437. * cascade: array<array-key,string>,
  1438. * isCascadeRemove: bool,
  1439. * isCascadePersist: bool,
  1440. * isCascadeRefresh: bool,
  1441. * isCascadeMerge: bool,
  1442. * isCascadeDetach: bool,
  1443. * type: int,
  1444. * originalField: string,
  1445. * originalClass: class-string,
  1446. * ?orphanRemoval: bool
  1447. * }
  1448. *
  1449. * @throws MappingException If something is wrong with the mapping.
  1450. */
  1451. protected function _validateAndCompleteAssociationMapping(array $mapping)
  1452. {
  1453. if (! isset($mapping['mappedBy'])) {
  1454. $mapping['mappedBy'] = null;
  1455. }
  1456. if (! isset($mapping['inversedBy'])) {
  1457. $mapping['inversedBy'] = null;
  1458. }
  1459. $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
  1460. if (empty($mapping['indexBy'])) {
  1461. unset($mapping['indexBy']);
  1462. }
  1463. // If targetEntity is unqualified, assume it is in the same namespace as
  1464. // the sourceEntity.
  1465. $mapping['sourceEntity'] = $this->name;
  1466. if ($this->isTypedProperty($mapping['fieldName'])) {
  1467. $mapping = $this->validateAndCompleteTypedAssociationMapping($mapping);
  1468. }
  1469. if (isset($mapping['targetEntity'])) {
  1470. $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
  1471. $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
  1472. }
  1473. if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
  1474. throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
  1475. }
  1476. // Complete id mapping
  1477. if (isset($mapping['id']) && $mapping['id'] === true) {
  1478. if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
  1479. throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
  1480. }
  1481. if (! in_array($mapping['fieldName'], $this->identifier, true)) {
  1482. if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
  1483. throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
  1484. $mapping['targetEntity'],
  1485. $this->name,
  1486. $mapping['fieldName']
  1487. );
  1488. }
  1489. $this->identifier[] = $mapping['fieldName'];
  1490. $this->containsForeignIdentifier = true;
  1491. }
  1492. // Check for composite key
  1493. if (! $this->isIdentifierComposite && count($this->identifier) > 1) {
  1494. $this->isIdentifierComposite = true;
  1495. }
  1496. if ($this->cache && ! isset($mapping['cache'])) {
  1497. throw NonCacheableEntityAssociation::fromEntityAndField(
  1498. $this->name,
  1499. $mapping['fieldName']
  1500. );
  1501. }
  1502. }
  1503. // Mandatory attributes for both sides
  1504. // Mandatory: fieldName, targetEntity
  1505. if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) {
  1506. throw MappingException::missingFieldName($this->name);
  1507. }
  1508. if (! isset($mapping['targetEntity'])) {
  1509. throw MappingException::missingTargetEntity($mapping['fieldName']);
  1510. }
  1511. // Mandatory and optional attributes for either side
  1512. if (! $mapping['mappedBy']) {
  1513. if (isset($mapping['joinTable']) && $mapping['joinTable']) {
  1514. if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
  1515. $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`');
  1516. $mapping['joinTable']['quoted'] = true;
  1517. }
  1518. }
  1519. } else {
  1520. $mapping['isOwningSide'] = false;
  1521. }
  1522. if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
  1523. throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
  1524. }
  1525. // Fetch mode. Default fetch mode to LAZY, if not set.
  1526. if (! isset($mapping['fetch'])) {
  1527. $mapping['fetch'] = self::FETCH_LAZY;
  1528. }
  1529. // Cascades
  1530. $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
  1531. $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
  1532. if (in_array('all', $cascades, true)) {
  1533. $cascades = $allCascades;
  1534. } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
  1535. throw MappingException::invalidCascadeOption(
  1536. array_diff($cascades, $allCascades),
  1537. $this->name,
  1538. $mapping['fieldName']
  1539. );
  1540. }
  1541. $mapping['cascade'] = $cascades;
  1542. $mapping['isCascadeRemove'] = in_array('remove', $cascades, true);
  1543. $mapping['isCascadePersist'] = in_array('persist', $cascades, true);
  1544. $mapping['isCascadeRefresh'] = in_array('refresh', $cascades, true);
  1545. $mapping['isCascadeMerge'] = in_array('merge', $cascades, true);
  1546. $mapping['isCascadeDetach'] = in_array('detach', $cascades, true);
  1547. return $mapping;
  1548. }
  1549. /**
  1550. * Validates & completes a one-to-one association mapping.
  1551. *
  1552. * @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
  1553. * @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
  1554. *
  1555. * @return mixed[] The validated & completed mapping.
  1556. * @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
  1557. * @psalm-return array{
  1558. * mappedBy: mixed|null,
  1559. * inversedBy: mixed|null,
  1560. * isOwningSide: bool,
  1561. * sourceEntity: class-string,
  1562. * targetEntity: string,
  1563. * fieldName: mixed,
  1564. * fetch: mixed,
  1565. * cascade: array<string>,
  1566. * isCascadeRemove: bool,
  1567. * isCascadePersist: bool,
  1568. * isCascadeRefresh: bool,
  1569. * isCascadeMerge: bool,
  1570. * isCascadeDetach: bool,
  1571. * type: int,
  1572. * originalField: string,
  1573. * originalClass: class-string,
  1574. * joinColumns?: array{0: array{name: string, referencedColumnName: string}}|mixed,
  1575. * id?: mixed,
  1576. * sourceToTargetKeyColumns?: array,
  1577. * joinColumnFieldNames?: array,
  1578. * targetToSourceKeyColumns?: array<array-key>,
  1579. * orphanRemoval: bool
  1580. * }
  1581. *
  1582. * @throws RuntimeException
  1583. * @throws MappingException
  1584. */
  1585. protected function _validateAndCompleteOneToOneMapping(array $mapping)
  1586. {
  1587. $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
  1588. if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
  1589. $mapping['isOwningSide'] = true;
  1590. }
  1591. if ($mapping['isOwningSide']) {
  1592. if (empty($mapping['joinColumns'])) {
  1593. // Apply default join column
  1594. $mapping['joinColumns'] = [
  1595. [
  1596. 'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
  1597. 'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
  1598. ],
  1599. ];
  1600. }
  1601. $uniqueConstraintColumns = [];
  1602. foreach ($mapping['joinColumns'] as &$joinColumn) {
  1603. if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
  1604. if (count($mapping['joinColumns']) === 1) {
  1605. if (empty($mapping['id'])) {
  1606. $joinColumn['unique'] = true;
  1607. }
  1608. } else {
  1609. $uniqueConstraintColumns[] = $joinColumn['name'];
  1610. }
  1611. }
  1612. if (empty($joinColumn['name'])) {
  1613. $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
  1614. }
  1615. if (empty($joinColumn['referencedColumnName'])) {
  1616. $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
  1617. }
  1618. if ($joinColumn['name'][0] === '`') {
  1619. $joinColumn['name'] = trim($joinColumn['name'], '`');
  1620. $joinColumn['quoted'] = true;
  1621. }
  1622. if ($joinColumn['referencedColumnName'][0] === '`') {
  1623. $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
  1624. $joinColumn['quoted'] = true;
  1625. }
  1626. $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
  1627. $mapping['joinColumnFieldNames'][$joinColumn['name']] = $joinColumn['fieldName'] ?? $joinColumn['name'];
  1628. }
  1629. if ($uniqueConstraintColumns) {
  1630. if (! $this->table) {
  1631. throw new RuntimeException('ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.');
  1632. }
  1633. $this->table['uniqueConstraints'][$mapping['fieldName'] . '_uniq'] = ['columns' => $uniqueConstraintColumns];
  1634. }
  1635. $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
  1636. }
  1637. $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
  1638. $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
  1639. if ($mapping['orphanRemoval']) {
  1640. unset($mapping['unique']);
  1641. }
  1642. if (isset($mapping['id']) && $mapping['id'] === true && ! $mapping['isOwningSide']) {
  1643. throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
  1644. }
  1645. return $mapping;
  1646. }
  1647. /**
  1648. * Validates & completes a one-to-many association mapping.
  1649. *
  1650. * @psalm-param array<string, mixed> $mapping The mapping to validate and complete.
  1651. *
  1652. * @return mixed[] The validated and completed mapping.
  1653. * @psalm-return array{
  1654. * mappedBy: mixed,
  1655. * inversedBy: mixed,
  1656. * isOwningSide: bool,
  1657. * sourceEntity: string,
  1658. * targetEntity: string,
  1659. * fieldName: mixed,
  1660. * fetch: int|mixed,
  1661. * cascade: array<array-key,string>,
  1662. * isCascadeRemove: bool,
  1663. * isCascadePersist: bool,
  1664. * isCascadeRefresh: bool,
  1665. * isCascadeMerge: bool,
  1666. * isCascadeDetach: bool,
  1667. * orphanRemoval: bool
  1668. * }
  1669. *
  1670. * @throws MappingException
  1671. * @throws InvalidArgumentException
  1672. */
  1673. protected function _validateAndCompleteOneToManyMapping(array $mapping)
  1674. {
  1675. $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
  1676. // OneToMany-side MUST be inverse (must have mappedBy)
  1677. if (! isset($mapping['mappedBy'])) {
  1678. throw MappingException::oneToManyRequiresMappedBy($this->name, $mapping['fieldName']);
  1679. }
  1680. $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
  1681. $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
  1682. $this->assertMappingOrderBy($mapping);
  1683. return $mapping;
  1684. }
  1685. /**
  1686. * Validates & completes a many-to-many association mapping.
  1687. *
  1688. * @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
  1689. * @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
  1690. *
  1691. * @return mixed[] The validated & completed mapping.
  1692. * @psalm-return array{
  1693. * mappedBy: mixed,
  1694. * inversedBy: mixed,
  1695. * isOwningSide: bool,
  1696. * sourceEntity: class-string,
  1697. * targetEntity: string,
  1698. * fieldName: mixed,
  1699. * fetch: mixed,
  1700. * cascade: array<string>,
  1701. * isCascadeRemove: bool,
  1702. * isCascadePersist: bool,
  1703. * isCascadeRefresh: bool,
  1704. * isCascadeMerge: bool,
  1705. * isCascadeDetach: bool,
  1706. * type: int,
  1707. * originalField: string,
  1708. * originalClass: class-string,
  1709. * joinTable?: array{inverseJoinColumns: mixed}|mixed,
  1710. * joinTableColumns?: list<mixed>,
  1711. * isOnDeleteCascade?: true,
  1712. * relationToSourceKeyColumns?: array,
  1713. * relationToTargetKeyColumns?: array,
  1714. * orphanRemoval: bool
  1715. * }
  1716. *
  1717. * @throws InvalidArgumentException
  1718. */
  1719. protected function _validateAndCompleteManyToManyMapping(array $mapping)
  1720. {
  1721. $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
  1722. if ($mapping['isOwningSide']) {
  1723. // owning side MUST have a join table
  1724. if (! isset($mapping['joinTable']['name'])) {
  1725. $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
  1726. }
  1727. $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] === $mapping['targetEntity']
  1728. && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
  1729. if (! isset($mapping['joinTable']['joinColumns'])) {
  1730. $mapping['joinTable']['joinColumns'] = [
  1731. [
  1732. 'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
  1733. 'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
  1734. 'onDelete' => 'CASCADE',
  1735. ],
  1736. ];
  1737. }
  1738. if (! isset($mapping['joinTable']['inverseJoinColumns'])) {
  1739. $mapping['joinTable']['inverseJoinColumns'] = [
  1740. [
  1741. 'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
  1742. 'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
  1743. 'onDelete' => 'CASCADE',
  1744. ],
  1745. ];
  1746. }
  1747. $mapping['joinTableColumns'] = [];
  1748. foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
  1749. if (empty($joinColumn['name'])) {
  1750. $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
  1751. }
  1752. if (empty($joinColumn['referencedColumnName'])) {
  1753. $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
  1754. }
  1755. if ($joinColumn['name'][0] === '`') {
  1756. $joinColumn['name'] = trim($joinColumn['name'], '`');
  1757. $joinColumn['quoted'] = true;
  1758. }
  1759. if ($joinColumn['referencedColumnName'][0] === '`') {
  1760. $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
  1761. $joinColumn['quoted'] = true;
  1762. }
  1763. if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) === 'cascade') {
  1764. $mapping['isOnDeleteCascade'] = true;
  1765. }
  1766. $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
  1767. $mapping['joinTableColumns'][] = $joinColumn['name'];
  1768. }
  1769. foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
  1770. if (empty($inverseJoinColumn['name'])) {
  1771. $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
  1772. }
  1773. if (empty($inverseJoinColumn['referencedColumnName'])) {
  1774. $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
  1775. }
  1776. if ($inverseJoinColumn['name'][0] === '`') {
  1777. $inverseJoinColumn['name'] = trim($inverseJoinColumn['name'], '`');
  1778. $inverseJoinColumn['quoted'] = true;
  1779. }
  1780. if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
  1781. $inverseJoinColumn['referencedColumnName'] = trim($inverseJoinColumn['referencedColumnName'], '`');
  1782. $inverseJoinColumn['quoted'] = true;
  1783. }
  1784. if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) === 'cascade') {
  1785. $mapping['isOnDeleteCascade'] = true;
  1786. }
  1787. $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
  1788. $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
  1789. }
  1790. }
  1791. $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
  1792. $this->assertMappingOrderBy($mapping);
  1793. return $mapping;
  1794. }
  1795. /**
  1796. * {@inheritDoc}
  1797. */
  1798. public function getIdentifierFieldNames()
  1799. {
  1800. return $this->identifier;
  1801. }
  1802. /**
  1803. * Gets the name of the single id field. Note that this only works on
  1804. * entity classes that have a single-field pk.
  1805. *
  1806. * @return string
  1807. *
  1808. * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
  1809. */
  1810. public function getSingleIdentifierFieldName()
  1811. {
  1812. if ($this->isIdentifierComposite) {
  1813. throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
  1814. }
  1815. if (! isset($this->identifier[0])) {
  1816. throw MappingException::noIdDefined($this->name);
  1817. }
  1818. return $this->identifier[0];
  1819. }
  1820. /**
  1821. * Gets the column name of the single id column. Note that this only works on
  1822. * entity classes that have a single-field pk.
  1823. *
  1824. * @return string
  1825. *
  1826. * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
  1827. */
  1828. public function getSingleIdentifierColumnName()
  1829. {
  1830. return $this->getColumnName($this->getSingleIdentifierFieldName());
  1831. }
  1832. /**
  1833. * INTERNAL:
  1834. * Sets the mapped identifier/primary key fields of this class.
  1835. * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
  1836. *
  1837. * @psalm-param list<mixed> $identifier
  1838. *
  1839. * @return void
  1840. */
  1841. public function setIdentifier(array $identifier)
  1842. {
  1843. $this->identifier = $identifier;
  1844. $this->isIdentifierComposite = (count($this->identifier) > 1);
  1845. }
  1846. /**
  1847. * {@inheritDoc}
  1848. */
  1849. public function getIdentifier()
  1850. {
  1851. return $this->identifier;
  1852. }
  1853. /**
  1854. * {@inheritDoc}
  1855. */
  1856. public function hasField($fieldName)
  1857. {
  1858. return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
  1859. }
  1860. /**
  1861. * Gets an array containing all the column names.
  1862. *
  1863. * @psalm-param list<string>|null $fieldNames
  1864. *
  1865. * @return mixed[]
  1866. * @psalm-return list<string>
  1867. */
  1868. public function getColumnNames(?array $fieldNames = null)
  1869. {
  1870. if ($fieldNames === null) {
  1871. return array_keys($this->fieldNames);
  1872. }
  1873. return array_values(array_map([$this, 'getColumnName'], $fieldNames));
  1874. }
  1875. /**
  1876. * Returns an array with all the identifier column names.
  1877. *
  1878. * @psalm-return list<string>
  1879. */
  1880. public function getIdentifierColumnNames()
  1881. {
  1882. $columnNames = [];
  1883. foreach ($this->identifier as $idProperty) {
  1884. if (isset($this->fieldMappings[$idProperty])) {
  1885. $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
  1886. continue;
  1887. }
  1888. // Association defined as Id field
  1889. $joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
  1890. $assocColumnNames = array_map(static function ($joinColumn) {
  1891. return $joinColumn['name'];
  1892. }, $joinColumns);
  1893. $columnNames = array_merge($columnNames, $assocColumnNames);
  1894. }
  1895. return $columnNames;
  1896. }
  1897. /**
  1898. * Sets the type of Id generator to use for the mapped class.
  1899. *
  1900. * @param int $generatorType
  1901. *
  1902. * @return void
  1903. */
  1904. public function setIdGeneratorType($generatorType)
  1905. {
  1906. $this->generatorType = $generatorType;
  1907. }
  1908. /**
  1909. * Checks whether the mapped class uses an Id generator.
  1910. *
  1911. * @return bool TRUE if the mapped class uses an Id generator, FALSE otherwise.
  1912. */
  1913. public function usesIdGenerator()
  1914. {
  1915. return $this->generatorType !== self::GENERATOR_TYPE_NONE;
  1916. }
  1917. /**
  1918. * @return bool
  1919. */
  1920. public function isInheritanceTypeNone()
  1921. {
  1922. return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
  1923. }
  1924. /**
  1925. * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
  1926. *
  1927. * @return bool TRUE if the class participates in a JOINED inheritance mapping,
  1928. * FALSE otherwise.
  1929. */
  1930. public function isInheritanceTypeJoined()
  1931. {
  1932. return $this->inheritanceType === self::INHERITANCE_TYPE_JOINED;
  1933. }
  1934. /**
  1935. * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
  1936. *
  1937. * @return bool TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
  1938. * FALSE otherwise.
  1939. */
  1940. public function isInheritanceTypeSingleTable()
  1941. {
  1942. return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_TABLE;
  1943. }
  1944. /**
  1945. * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
  1946. *
  1947. * @return bool TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
  1948. * FALSE otherwise.
  1949. */
  1950. public function isInheritanceTypeTablePerClass()
  1951. {
  1952. return $this->inheritanceType === self::INHERITANCE_TYPE_TABLE_PER_CLASS;
  1953. }
  1954. /**
  1955. * Checks whether the class uses an identity column for the Id generation.
  1956. *
  1957. * @return bool TRUE if the class uses the IDENTITY generator, FALSE otherwise.
  1958. */
  1959. public function isIdGeneratorIdentity()
  1960. {
  1961. return $this->generatorType === self::GENERATOR_TYPE_IDENTITY;
  1962. }
  1963. /**
  1964. * Checks whether the class uses a sequence for id generation.
  1965. *
  1966. * @return bool TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
  1967. */
  1968. public function isIdGeneratorSequence()
  1969. {
  1970. return $this->generatorType === self::GENERATOR_TYPE_SEQUENCE;
  1971. }
  1972. /**
  1973. * Checks whether the class uses a table for id generation.
  1974. *
  1975. * @deprecated
  1976. *
  1977. * @return false
  1978. */
  1979. public function isIdGeneratorTable()
  1980. {
  1981. Deprecation::trigger(
  1982. 'doctrine/orm',
  1983. 'https://github.com/doctrine/orm/pull/9046',
  1984. '%s is deprecated',
  1985. __METHOD__
  1986. );
  1987. return false;
  1988. }
  1989. /**
  1990. * Checks whether the class has a natural identifier/pk (which means it does
  1991. * not use any Id generator.
  1992. *
  1993. * @return bool
  1994. */
  1995. public function isIdentifierNatural()
  1996. {
  1997. return $this->generatorType === self::GENERATOR_TYPE_NONE;
  1998. }
  1999. /**
  2000. * Checks whether the class use a UUID for id generation.
  2001. *
  2002. * @deprecated
  2003. *
  2004. * @return bool
  2005. */
  2006. public function isIdentifierUuid()
  2007. {
  2008. Deprecation::trigger(
  2009. 'doctrine/orm',
  2010. 'https://github.com/doctrine/orm/pull/9046',
  2011. '%s is deprecated',
  2012. __METHOD__
  2013. );
  2014. return $this->generatorType === self::GENERATOR_TYPE_UUID;
  2015. }
  2016. /**
  2017. * Gets the type of a field.
  2018. *
  2019. * @param string $fieldName
  2020. *
  2021. * @return string|null
  2022. *
  2023. * @todo 3.0 Remove this. PersisterHelper should fix it somehow
  2024. */
  2025. public function getTypeOfField($fieldName)
  2026. {
  2027. return isset($this->fieldMappings[$fieldName])
  2028. ? $this->fieldMappings[$fieldName]['type']
  2029. : null;
  2030. }
  2031. /**
  2032. * Gets the type of a column.
  2033. *
  2034. * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
  2035. * that is derived by a referenced field on a different entity.
  2036. *
  2037. * @param string $columnName
  2038. *
  2039. * @return string|null
  2040. */
  2041. public function getTypeOfColumn($columnName)
  2042. {
  2043. return $this->getTypeOfField($this->getFieldName($columnName));
  2044. }
  2045. /**
  2046. * Gets the name of the primary table.
  2047. *
  2048. * @return string
  2049. */
  2050. public function getTableName()
  2051. {
  2052. return $this->table['name'];
  2053. }
  2054. /**
  2055. * Gets primary table's schema name.
  2056. *
  2057. * @return string|null
  2058. */
  2059. public function getSchemaName()
  2060. {
  2061. return $this->table['schema'] ?? null;
  2062. }
  2063. /**
  2064. * Gets the table name to use for temporary identifier tables of this class.
  2065. *
  2066. * @return string
  2067. */
  2068. public function getTemporaryIdTableName()
  2069. {
  2070. // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
  2071. return str_replace('.', '_', $this->getTableName() . '_id_tmp');
  2072. }
  2073. /**
  2074. * Sets the mapped subclasses of this class.
  2075. *
  2076. * @psalm-param list<string> $subclasses The names of all mapped subclasses.
  2077. *
  2078. * @return void
  2079. */
  2080. public function setSubclasses(array $subclasses)
  2081. {
  2082. foreach ($subclasses as $subclass) {
  2083. $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
  2084. }
  2085. }
  2086. /**
  2087. * Sets the parent class names.
  2088. * Assumes that the class names in the passed array are in the order:
  2089. * directParent -> directParentParent -> directParentParentParent ... -> root.
  2090. *
  2091. * @psalm-param list<class-string> $classNames
  2092. *
  2093. * @return void
  2094. */
  2095. public function setParentClasses(array $classNames)
  2096. {
  2097. $this->parentClasses = $classNames;
  2098. if (count($classNames) > 0) {
  2099. $this->rootEntityName = array_pop($classNames);
  2100. }
  2101. }
  2102. /**
  2103. * Sets the inheritance type used by the class and its subclasses.
  2104. *
  2105. * @param int $type
  2106. *
  2107. * @return void
  2108. *
  2109. * @throws MappingException
  2110. */
  2111. public function setInheritanceType($type)
  2112. {
  2113. if (! $this->isInheritanceType($type)) {
  2114. throw MappingException::invalidInheritanceType($this->name, $type);
  2115. }
  2116. $this->inheritanceType = $type;
  2117. }
  2118. /**
  2119. * Sets the association to override association mapping of property for an entity relationship.
  2120. *
  2121. * @param string $fieldName
  2122. * @psalm-param array<string, mixed> $overrideMapping
  2123. *
  2124. * @return void
  2125. *
  2126. * @throws MappingException
  2127. */
  2128. public function setAssociationOverride($fieldName, array $overrideMapping)
  2129. {
  2130. if (! isset($this->associationMappings[$fieldName])) {
  2131. throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
  2132. }
  2133. $mapping = $this->associationMappings[$fieldName];
  2134. //if (isset($mapping['inherited']) && (count($overrideMapping) !== 1 || ! isset($overrideMapping['fetch']))) {
  2135. // TODO: Deprecate overriding the fetch mode via association override for 3.0,
  2136. // users should do this with a listener and a custom attribute/annotation
  2137. // TODO: Enable this exception in 2.8
  2138. //throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName);
  2139. //}
  2140. if (isset($overrideMapping['joinColumns'])) {
  2141. $mapping['joinColumns'] = $overrideMapping['joinColumns'];
  2142. }
  2143. if (isset($overrideMapping['inversedBy'])) {
  2144. $mapping['inversedBy'] = $overrideMapping['inversedBy'];
  2145. }
  2146. if (isset($overrideMapping['joinTable'])) {
  2147. $mapping['joinTable'] = $overrideMapping['joinTable'];
  2148. }
  2149. if (isset($overrideMapping['fetch'])) {
  2150. $mapping['fetch'] = $overrideMapping['fetch'];
  2151. }
  2152. $mapping['joinColumnFieldNames'] = null;
  2153. $mapping['joinTableColumns'] = null;
  2154. $mapping['sourceToTargetKeyColumns'] = null;
  2155. $mapping['relationToSourceKeyColumns'] = null;
  2156. $mapping['relationToTargetKeyColumns'] = null;
  2157. switch ($mapping['type']) {
  2158. case self::ONE_TO_ONE:
  2159. $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
  2160. break;
  2161. case self::ONE_TO_MANY:
  2162. $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
  2163. break;
  2164. case self::MANY_TO_ONE:
  2165. $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
  2166. break;
  2167. case self::MANY_TO_MANY:
  2168. $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
  2169. break;
  2170. }
  2171. $this->associationMappings[$fieldName] = $mapping;
  2172. }
  2173. /**
  2174. * Sets the override for a mapped field.
  2175. *
  2176. * @param string $fieldName
  2177. * @psalm-param array<string, mixed> $overrideMapping
  2178. *
  2179. * @return void
  2180. *
  2181. * @throws MappingException
  2182. */
  2183. public function setAttributeOverride($fieldName, array $overrideMapping)
  2184. {
  2185. if (! isset($this->fieldMappings[$fieldName])) {
  2186. throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
  2187. }
  2188. $mapping = $this->fieldMappings[$fieldName];
  2189. //if (isset($mapping['inherited'])) {
  2190. // TODO: Enable this exception in 2.8
  2191. //throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName);
  2192. //}
  2193. if (isset($mapping['id'])) {
  2194. $overrideMapping['id'] = $mapping['id'];
  2195. }
  2196. if (! isset($overrideMapping['type'])) {
  2197. $overrideMapping['type'] = $mapping['type'];
  2198. }
  2199. if (! isset($overrideMapping['fieldName'])) {
  2200. $overrideMapping['fieldName'] = $mapping['fieldName'];
  2201. }
  2202. if ($overrideMapping['type'] !== $mapping['type']) {
  2203. throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
  2204. }
  2205. unset($this->fieldMappings[$fieldName]);
  2206. unset($this->fieldNames[$mapping['columnName']]);
  2207. unset($this->columnNames[$mapping['fieldName']]);
  2208. $overrideMapping = $this->validateAndCompleteFieldMapping($overrideMapping);
  2209. $this->fieldMappings[$fieldName] = $overrideMapping;
  2210. }
  2211. /**
  2212. * Checks whether a mapped field is inherited from an entity superclass.
  2213. *
  2214. * @param string $fieldName
  2215. *
  2216. * @return bool TRUE if the field is inherited, FALSE otherwise.
  2217. */
  2218. public function isInheritedField($fieldName)
  2219. {
  2220. return isset($this->fieldMappings[$fieldName]['inherited']);
  2221. }
  2222. /**
  2223. * Checks if this entity is the root in any entity-inheritance-hierarchy.
  2224. *
  2225. * @return bool
  2226. */
  2227. public function isRootEntity()
  2228. {
  2229. return $this->name === $this->rootEntityName;
  2230. }
  2231. /**
  2232. * Checks whether a mapped association field is inherited from a superclass.
  2233. *
  2234. * @param string $fieldName
  2235. *
  2236. * @return bool TRUE if the field is inherited, FALSE otherwise.
  2237. */
  2238. public function isInheritedAssociation($fieldName)
  2239. {
  2240. return isset($this->associationMappings[$fieldName]['inherited']);
  2241. }
  2242. /**
  2243. * @param string $fieldName
  2244. *
  2245. * @return bool
  2246. */
  2247. public function isInheritedEmbeddedClass($fieldName)
  2248. {
  2249. return isset($this->embeddedClasses[$fieldName]['inherited']);
  2250. }
  2251. /**
  2252. * Sets the name of the primary table the class is mapped to.
  2253. *
  2254. * @deprecated Use {@link setPrimaryTable}.
  2255. *
  2256. * @param string $tableName The table name.
  2257. *
  2258. * @return void
  2259. */
  2260. public function setTableName($tableName)
  2261. {
  2262. $this->table['name'] = $tableName;
  2263. }
  2264. /**
  2265. * Sets the primary table definition. The provided array supports the
  2266. * following structure:
  2267. *
  2268. * name => <tableName> (optional, defaults to class name)
  2269. * indexes => array of indexes (optional)
  2270. * uniqueConstraints => array of constraints (optional)
  2271. *
  2272. * If a key is omitted, the current value is kept.
  2273. *
  2274. * @psalm-param array<string, mixed> $table The table description.
  2275. *
  2276. * @return void
  2277. */
  2278. public function setPrimaryTable(array $table)
  2279. {
  2280. if (isset($table['name'])) {
  2281. // Split schema and table name from a table name like "myschema.mytable"
  2282. if (strpos($table['name'], '.') !== false) {
  2283. [$this->table['schema'], $table['name']] = explode('.', $table['name'], 2);
  2284. }
  2285. if ($table['name'][0] === '`') {
  2286. $table['name'] = trim($table['name'], '`');
  2287. $this->table['quoted'] = true;
  2288. }
  2289. $this->table['name'] = $table['name'];
  2290. }
  2291. if (isset($table['quoted'])) {
  2292. $this->table['quoted'] = $table['quoted'];
  2293. }
  2294. if (isset($table['schema'])) {
  2295. $this->table['schema'] = $table['schema'];
  2296. }
  2297. if (isset($table['indexes'])) {
  2298. $this->table['indexes'] = $table['indexes'];
  2299. }
  2300. if (isset($table['uniqueConstraints'])) {
  2301. $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
  2302. }
  2303. if (isset($table['options'])) {
  2304. $this->table['options'] = $table['options'];
  2305. }
  2306. }
  2307. /**
  2308. * Checks whether the given type identifies an inheritance type.
  2309. *
  2310. * @return bool TRUE if the given type identifies an inheritance type, FALSE otherwise.
  2311. */
  2312. private function isInheritanceType(int $type): bool
  2313. {
  2314. return $type === self::INHERITANCE_TYPE_NONE ||
  2315. $type === self::INHERITANCE_TYPE_SINGLE_TABLE ||
  2316. $type === self::INHERITANCE_TYPE_JOINED ||
  2317. $type === self::INHERITANCE_TYPE_TABLE_PER_CLASS;
  2318. }
  2319. /**
  2320. * Adds a mapped field to the class.
  2321. *
  2322. * @psalm-param array<string, mixed> $mapping The field mapping.
  2323. *
  2324. * @return void
  2325. *
  2326. * @throws MappingException
  2327. */
  2328. public function mapField(array $mapping)
  2329. {
  2330. $mapping = $this->validateAndCompleteFieldMapping($mapping);
  2331. $this->assertFieldNotMapped($mapping['fieldName']);
  2332. $this->fieldMappings[$mapping['fieldName']] = $mapping;
  2333. }
  2334. /**
  2335. * INTERNAL:
  2336. * Adds an association mapping without completing/validating it.
  2337. * This is mainly used to add inherited association mappings to derived classes.
  2338. *
  2339. * @psalm-param array<string, mixed> $mapping
  2340. *
  2341. * @return void
  2342. *
  2343. * @throws MappingException
  2344. */
  2345. public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
  2346. {
  2347. if (isset($this->associationMappings[$mapping['fieldName']])) {
  2348. throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
  2349. }
  2350. $this->associationMappings[$mapping['fieldName']] = $mapping;
  2351. }
  2352. /**
  2353. * INTERNAL:
  2354. * Adds a field mapping without completing/validating it.
  2355. * This is mainly used to add inherited field mappings to derived classes.
  2356. *
  2357. * @psalm-param array<string, mixed> $fieldMapping
  2358. *
  2359. * @return void
  2360. */
  2361. public function addInheritedFieldMapping(array $fieldMapping)
  2362. {
  2363. $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
  2364. $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
  2365. $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
  2366. }
  2367. /**
  2368. * INTERNAL:
  2369. * Adds a named query to this class.
  2370. *
  2371. * @deprecated
  2372. *
  2373. * @psalm-param array<string, mixed> $queryMapping
  2374. *
  2375. * @return void
  2376. *
  2377. * @throws MappingException
  2378. */
  2379. public function addNamedQuery(array $queryMapping)
  2380. {
  2381. if (! isset($queryMapping['name'])) {
  2382. throw MappingException::nameIsMandatoryForQueryMapping($this->name);
  2383. }
  2384. Deprecation::trigger(
  2385. 'doctrine/orm',
  2386. 'https://github.com/doctrine/orm/issues/8592',
  2387. 'Named Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
  2388. $queryMapping['name'],
  2389. $this->name
  2390. );
  2391. if (isset($this->namedQueries[$queryMapping['name']])) {
  2392. throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
  2393. }
  2394. if (! isset($queryMapping['query'])) {
  2395. throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
  2396. }
  2397. $name = $queryMapping['name'];
  2398. $query = $queryMapping['query'];
  2399. $dql = str_replace('__CLASS__', $this->name, $query);
  2400. $this->namedQueries[$name] = [
  2401. 'name' => $name,
  2402. 'query' => $query,
  2403. 'dql' => $dql,
  2404. ];
  2405. }
  2406. /**
  2407. * INTERNAL:
  2408. * Adds a named native query to this class.
  2409. *
  2410. * @deprecated
  2411. *
  2412. * @psalm-param array<string, mixed> $queryMapping
  2413. *
  2414. * @return void
  2415. *
  2416. * @throws MappingException
  2417. */
  2418. public function addNamedNativeQuery(array $queryMapping)
  2419. {
  2420. if (! isset($queryMapping['name'])) {
  2421. throw MappingException::nameIsMandatoryForQueryMapping($this->name);
  2422. }
  2423. Deprecation::trigger(
  2424. 'doctrine/orm',
  2425. 'https://github.com/doctrine/orm/issues/8592',
  2426. 'Named Native Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
  2427. $queryMapping['name'],
  2428. $this->name
  2429. );
  2430. if (isset($this->namedNativeQueries[$queryMapping['name']])) {
  2431. throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
  2432. }
  2433. if (! isset($queryMapping['query'])) {
  2434. throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
  2435. }
  2436. if (! isset($queryMapping['resultClass']) && ! isset($queryMapping['resultSetMapping'])) {
  2437. throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
  2438. }
  2439. $queryMapping['isSelfClass'] = false;
  2440. if (isset($queryMapping['resultClass'])) {
  2441. if ($queryMapping['resultClass'] === '__CLASS__') {
  2442. $queryMapping['isSelfClass'] = true;
  2443. $queryMapping['resultClass'] = $this->name;
  2444. }
  2445. $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
  2446. $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
  2447. }
  2448. $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
  2449. }
  2450. /**
  2451. * INTERNAL:
  2452. * Adds a sql result set mapping to this class.
  2453. *
  2454. * @psalm-param array<string, mixed> $resultMapping
  2455. *
  2456. * @return void
  2457. *
  2458. * @throws MappingException
  2459. */
  2460. public function addSqlResultSetMapping(array $resultMapping)
  2461. {
  2462. if (! isset($resultMapping['name'])) {
  2463. throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
  2464. }
  2465. if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
  2466. throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
  2467. }
  2468. if (isset($resultMapping['entities'])) {
  2469. foreach ($resultMapping['entities'] as $key => $entityResult) {
  2470. if (! isset($entityResult['entityClass'])) {
  2471. throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
  2472. }
  2473. $entityResult['isSelfClass'] = false;
  2474. if ($entityResult['entityClass'] === '__CLASS__') {
  2475. $entityResult['isSelfClass'] = true;
  2476. $entityResult['entityClass'] = $this->name;
  2477. }
  2478. $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
  2479. $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
  2480. $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
  2481. if (isset($entityResult['fields'])) {
  2482. foreach ($entityResult['fields'] as $k => $field) {
  2483. if (! isset($field['name'])) {
  2484. throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']);
  2485. }
  2486. if (! isset($field['column'])) {
  2487. $fieldName = $field['name'];
  2488. if (strpos($fieldName, '.')) {
  2489. [, $fieldName] = explode('.', $fieldName);
  2490. }
  2491. $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
  2492. }
  2493. }
  2494. }
  2495. }
  2496. }
  2497. $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
  2498. }
  2499. /**
  2500. * Adds a one-to-one mapping.
  2501. *
  2502. * @param array<string, mixed> $mapping The mapping.
  2503. *
  2504. * @return void
  2505. */
  2506. public function mapOneToOne(array $mapping)
  2507. {
  2508. $mapping['type'] = self::ONE_TO_ONE;
  2509. $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
  2510. $this->_storeAssociationMapping($mapping);
  2511. }
  2512. /**
  2513. * Adds a one-to-many mapping.
  2514. *
  2515. * @psalm-param array<string, mixed> $mapping The mapping.
  2516. *
  2517. * @return void
  2518. */
  2519. public function mapOneToMany(array $mapping)
  2520. {
  2521. $mapping['type'] = self::ONE_TO_MANY;
  2522. $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
  2523. $this->_storeAssociationMapping($mapping);
  2524. }
  2525. /**
  2526. * Adds a many-to-one mapping.
  2527. *
  2528. * @psalm-param array<string, mixed> $mapping The mapping.
  2529. *
  2530. * @return void
  2531. */
  2532. public function mapManyToOne(array $mapping)
  2533. {
  2534. $mapping['type'] = self::MANY_TO_ONE;
  2535. // A many-to-one mapping is essentially a one-one backreference
  2536. $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
  2537. $this->_storeAssociationMapping($mapping);
  2538. }
  2539. /**
  2540. * Adds a many-to-many mapping.
  2541. *
  2542. * @psalm-param array<string, mixed> $mapping The mapping.
  2543. *
  2544. * @return void
  2545. */
  2546. public function mapManyToMany(array $mapping)
  2547. {
  2548. $mapping['type'] = self::MANY_TO_MANY;
  2549. $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
  2550. $this->_storeAssociationMapping($mapping);
  2551. }
  2552. /**
  2553. * Stores the association mapping.
  2554. *
  2555. * @psalm-param array<string, mixed> $assocMapping
  2556. *
  2557. * @return void
  2558. *
  2559. * @throws MappingException
  2560. */
  2561. protected function _storeAssociationMapping(array $assocMapping)
  2562. {
  2563. $sourceFieldName = $assocMapping['fieldName'];
  2564. $this->assertFieldNotMapped($sourceFieldName);
  2565. $this->associationMappings[$sourceFieldName] = $assocMapping;
  2566. }
  2567. /**
  2568. * Registers a custom repository class for the entity class.
  2569. *
  2570. * @param string $repositoryClassName The class name of the custom mapper.
  2571. * @psalm-param class-string $repositoryClassName
  2572. *
  2573. * @return void
  2574. */
  2575. public function setCustomRepositoryClass($repositoryClassName)
  2576. {
  2577. $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
  2578. }
  2579. /**
  2580. * Dispatches the lifecycle event of the given entity to the registered
  2581. * lifecycle callbacks and lifecycle listeners.
  2582. *
  2583. * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
  2584. *
  2585. * @param string $lifecycleEvent The lifecycle event.
  2586. * @param object $entity The Entity on which the event occurred.
  2587. *
  2588. * @return void
  2589. */
  2590. public function invokeLifecycleCallbacks($lifecycleEvent, $entity)
  2591. {
  2592. foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
  2593. $entity->$callback();
  2594. }
  2595. }
  2596. /**
  2597. * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
  2598. *
  2599. * @param string $lifecycleEvent
  2600. *
  2601. * @return bool
  2602. */
  2603. public function hasLifecycleCallbacks($lifecycleEvent)
  2604. {
  2605. return isset($this->lifecycleCallbacks[$lifecycleEvent]);
  2606. }
  2607. /**
  2608. * Gets the registered lifecycle callbacks for an event.
  2609. *
  2610. * @param string $event
  2611. *
  2612. * @return string[]
  2613. * @psalm-return list<string>
  2614. */
  2615. public function getLifecycleCallbacks($event)
  2616. {
  2617. return $this->lifecycleCallbacks[$event] ?? [];
  2618. }
  2619. /**
  2620. * Adds a lifecycle callback for entities of this class.
  2621. *
  2622. * @param string $callback
  2623. * @param string $event
  2624. *
  2625. * @return void
  2626. */
  2627. public function addLifecycleCallback($callback, $event)
  2628. {
  2629. if ($this->isEmbeddedClass) {
  2630. Deprecation::trigger(
  2631. 'doctrine/orm',
  2632. 'https://github.com/doctrine/orm/pull/8381',
  2633. 'Registering lifecycle callback %s on Embedded class %s is not doing anything and will throw exception in 3.0',
  2634. $event,
  2635. $this->name
  2636. );
  2637. }
  2638. if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) {
  2639. return;
  2640. }
  2641. $this->lifecycleCallbacks[$event][] = $callback;
  2642. }
  2643. /**
  2644. * Sets the lifecycle callbacks for entities of this class.
  2645. * Any previously registered callbacks are overwritten.
  2646. *
  2647. * @psalm-param array<string, list<string>> $callbacks
  2648. *
  2649. * @return void
  2650. */
  2651. public function setLifecycleCallbacks(array $callbacks)
  2652. {
  2653. $this->lifecycleCallbacks = $callbacks;
  2654. }
  2655. /**
  2656. * Adds a entity listener for entities of this class.
  2657. *
  2658. * @param string $eventName The entity lifecycle event.
  2659. * @param string $class The listener class.
  2660. * @param string $method The listener callback method.
  2661. *
  2662. * @return void
  2663. *
  2664. * @throws MappingException
  2665. */
  2666. public function addEntityListener($eventName, $class, $method)
  2667. {
  2668. $class = $this->fullyQualifiedClassName($class);
  2669. $listener = [
  2670. 'class' => $class,
  2671. 'method' => $method,
  2672. ];
  2673. if (! class_exists($class)) {
  2674. throw MappingException::entityListenerClassNotFound($class, $this->name);
  2675. }
  2676. if (! method_exists($class, $method)) {
  2677. throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
  2678. }
  2679. if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
  2680. throw MappingException::duplicateEntityListener($class, $method, $this->name);
  2681. }
  2682. $this->entityListeners[$eventName][] = $listener;
  2683. }
  2684. /**
  2685. * Sets the discriminator column definition.
  2686. *
  2687. * @see getDiscriminatorColumn()
  2688. *
  2689. * @param mixed[]|null $columnDef
  2690. * @psalm-param array<string, mixed>|null $columnDef
  2691. *
  2692. * @return void
  2693. *
  2694. * @throws MappingException
  2695. */
  2696. public function setDiscriminatorColumn($columnDef)
  2697. {
  2698. if ($columnDef !== null) {
  2699. if (! isset($columnDef['name'])) {
  2700. throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
  2701. }
  2702. if (isset($this->fieldNames[$columnDef['name']])) {
  2703. throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
  2704. }
  2705. if (! isset($columnDef['fieldName'])) {
  2706. $columnDef['fieldName'] = $columnDef['name'];
  2707. }
  2708. if (! isset($columnDef['type'])) {
  2709. $columnDef['type'] = 'string';
  2710. }
  2711. if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) {
  2712. throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
  2713. }
  2714. $this->discriminatorColumn = $columnDef;
  2715. }
  2716. }
  2717. /**
  2718. * Sets the discriminator values used by this class.
  2719. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
  2720. *
  2721. * @psalm-param array<string, class-string> $map
  2722. *
  2723. * @return void
  2724. */
  2725. public function setDiscriminatorMap(array $map)
  2726. {
  2727. foreach ($map as $value => $className) {
  2728. $this->addDiscriminatorMapClass($value, $className);
  2729. }
  2730. }
  2731. /**
  2732. * Adds one entry of the discriminator map with a new class and corresponding name.
  2733. *
  2734. * @param string $name
  2735. * @param string $className
  2736. * @psalm-param class-string $className
  2737. *
  2738. * @return void
  2739. *
  2740. * @throws MappingException
  2741. */
  2742. public function addDiscriminatorMapClass($name, $className)
  2743. {
  2744. $className = $this->fullyQualifiedClassName($className);
  2745. $className = ltrim($className, '\\');
  2746. $this->discriminatorMap[$name] = $className;
  2747. if ($this->name === $className) {
  2748. $this->discriminatorValue = $name;
  2749. return;
  2750. }
  2751. if (! (class_exists($className) || interface_exists($className))) {
  2752. throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
  2753. }
  2754. if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) {
  2755. $this->subClasses[] = $className;
  2756. }
  2757. }
  2758. /**
  2759. * Checks whether the class has a named query with the given query name.
  2760. *
  2761. * @param string $queryName
  2762. *
  2763. * @return bool
  2764. */
  2765. public function hasNamedQuery($queryName)
  2766. {
  2767. return isset($this->namedQueries[$queryName]);
  2768. }
  2769. /**
  2770. * Checks whether the class has a named native query with the given query name.
  2771. *
  2772. * @param string $queryName
  2773. *
  2774. * @return bool
  2775. */
  2776. public function hasNamedNativeQuery($queryName)
  2777. {
  2778. return isset($this->namedNativeQueries[$queryName]);
  2779. }
  2780. /**
  2781. * Checks whether the class has a named native query with the given query name.
  2782. *
  2783. * @param string $name
  2784. *
  2785. * @return bool
  2786. */
  2787. public function hasSqlResultSetMapping($name)
  2788. {
  2789. return isset($this->sqlResultSetMappings[$name]);
  2790. }
  2791. /**
  2792. * {@inheritDoc}
  2793. */
  2794. public function hasAssociation($fieldName)
  2795. {
  2796. return isset($this->associationMappings[$fieldName]);
  2797. }
  2798. /**
  2799. * {@inheritDoc}
  2800. */
  2801. public function isSingleValuedAssociation($fieldName)
  2802. {
  2803. return isset($this->associationMappings[$fieldName])
  2804. && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
  2805. }
  2806. /**
  2807. * {@inheritDoc}
  2808. */
  2809. public function isCollectionValuedAssociation($fieldName)
  2810. {
  2811. return isset($this->associationMappings[$fieldName])
  2812. && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
  2813. }
  2814. /**
  2815. * Is this an association that only has a single join column?
  2816. *
  2817. * @param string $fieldName
  2818. *
  2819. * @return bool
  2820. */
  2821. public function isAssociationWithSingleJoinColumn($fieldName)
  2822. {
  2823. return isset($this->associationMappings[$fieldName])
  2824. && isset($this->associationMappings[$fieldName]['joinColumns'][0])
  2825. && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]);
  2826. }
  2827. /**
  2828. * Returns the single association join column (if any).
  2829. *
  2830. * @param string $fieldName
  2831. *
  2832. * @return string
  2833. *
  2834. * @throws MappingException
  2835. */
  2836. public function getSingleAssociationJoinColumnName($fieldName)
  2837. {
  2838. if (! $this->isAssociationWithSingleJoinColumn($fieldName)) {
  2839. throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
  2840. }
  2841. return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
  2842. }
  2843. /**
  2844. * Returns the single association referenced join column name (if any).
  2845. *
  2846. * @param string $fieldName
  2847. *
  2848. * @return string
  2849. *
  2850. * @throws MappingException
  2851. */
  2852. public function getSingleAssociationReferencedJoinColumnName($fieldName)
  2853. {
  2854. if (! $this->isAssociationWithSingleJoinColumn($fieldName)) {
  2855. throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
  2856. }
  2857. return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
  2858. }
  2859. /**
  2860. * Used to retrieve a fieldname for either field or association from a given column.
  2861. *
  2862. * This method is used in foreign-key as primary-key contexts.
  2863. *
  2864. * @param string $columnName
  2865. *
  2866. * @return string
  2867. *
  2868. * @throws MappingException
  2869. */
  2870. public function getFieldForColumn($columnName)
  2871. {
  2872. if (isset($this->fieldNames[$columnName])) {
  2873. return $this->fieldNames[$columnName];
  2874. }
  2875. foreach ($this->associationMappings as $assocName => $mapping) {
  2876. if (
  2877. $this->isAssociationWithSingleJoinColumn($assocName) &&
  2878. $this->associationMappings[$assocName]['joinColumns'][0]['name'] === $columnName
  2879. ) {
  2880. return $assocName;
  2881. }
  2882. }
  2883. throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
  2884. }
  2885. /**
  2886. * Sets the ID generator used to generate IDs for instances of this class.
  2887. *
  2888. * @param AbstractIdGenerator $generator
  2889. *
  2890. * @return void
  2891. */
  2892. public function setIdGenerator($generator)
  2893. {
  2894. $this->idGenerator = $generator;
  2895. }
  2896. /**
  2897. * Sets definition.
  2898. *
  2899. * @psalm-param array<string, string|null> $definition
  2900. *
  2901. * @return void
  2902. */
  2903. public function setCustomGeneratorDefinition(array $definition)
  2904. {
  2905. $this->customGeneratorDefinition = $definition;
  2906. }
  2907. /**
  2908. * Sets the definition of the sequence ID generator for this class.
  2909. *
  2910. * The definition must have the following structure:
  2911. * <code>
  2912. * array(
  2913. * 'sequenceName' => 'name',
  2914. * 'allocationSize' => 20,
  2915. * 'initialValue' => 1
  2916. * 'quoted' => 1
  2917. * )
  2918. * </code>
  2919. *
  2920. * @psalm-param array{sequenceName?: string, allocationSize?: int|string, initialValue?: int|string, quoted?: mixed} $definition
  2921. *
  2922. * @return void
  2923. *
  2924. * @throws MappingException
  2925. */
  2926. public function setSequenceGeneratorDefinition(array $definition)
  2927. {
  2928. if (! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') {
  2929. throw MappingException::missingSequenceName($this->name);
  2930. }
  2931. if ($definition['sequenceName'][0] === '`') {
  2932. $definition['sequenceName'] = trim($definition['sequenceName'], '`');
  2933. $definition['quoted'] = true;
  2934. }
  2935. if (! isset($definition['allocationSize']) || trim((string) $definition['allocationSize']) === '') {
  2936. $definition['allocationSize'] = '1';
  2937. }
  2938. if (! isset($definition['initialValue']) || trim((string) $definition['initialValue']) === '') {
  2939. $definition['initialValue'] = '1';
  2940. }
  2941. $definition['allocationSize'] = (string) $definition['allocationSize'];
  2942. $definition['initialValue'] = (string) $definition['initialValue'];
  2943. $this->sequenceGeneratorDefinition = $definition;
  2944. }
  2945. /**
  2946. * Sets the version field mapping used for versioning. Sets the default
  2947. * value to use depending on the column type.
  2948. *
  2949. * @psalm-param array<string, mixed> $mapping The version field mapping array.
  2950. *
  2951. * @return void
  2952. *
  2953. * @throws MappingException
  2954. */
  2955. public function setVersionMapping(array &$mapping)
  2956. {
  2957. $this->isVersioned = true;
  2958. $this->versionField = $mapping['fieldName'];
  2959. if (! isset($mapping['default'])) {
  2960. if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) {
  2961. $mapping['default'] = 1;
  2962. } elseif ($mapping['type'] === 'datetime') {
  2963. $mapping['default'] = 'CURRENT_TIMESTAMP';
  2964. } else {
  2965. throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
  2966. }
  2967. }
  2968. }
  2969. /**
  2970. * Sets whether this class is to be versioned for optimistic locking.
  2971. *
  2972. * @param bool $bool
  2973. *
  2974. * @return void
  2975. */
  2976. public function setVersioned($bool)
  2977. {
  2978. $this->isVersioned = $bool;
  2979. }
  2980. /**
  2981. * Sets the name of the field that is to be used for versioning if this class is
  2982. * versioned for optimistic locking.
  2983. *
  2984. * @param string $versionField
  2985. *
  2986. * @return void
  2987. */
  2988. public function setVersionField($versionField)
  2989. {
  2990. $this->versionField = $versionField;
  2991. }
  2992. /**
  2993. * Marks this class as read only, no change tracking is applied to it.
  2994. *
  2995. * @return void
  2996. */
  2997. public function markReadOnly()
  2998. {
  2999. $this->isReadOnly = true;
  3000. }
  3001. /**
  3002. * {@inheritDoc}
  3003. */
  3004. public function getFieldNames()
  3005. {
  3006. return array_keys($this->fieldMappings);
  3007. }
  3008. /**
  3009. * {@inheritDoc}
  3010. */
  3011. public function getAssociationNames()
  3012. {
  3013. return array_keys($this->associationMappings);
  3014. }
  3015. /**
  3016. * {@inheritDoc}
  3017. *
  3018. * @param string $assocName
  3019. *
  3020. * @return string
  3021. * @psalm-return class-string
  3022. *
  3023. * @throws InvalidArgumentException
  3024. */
  3025. public function getAssociationTargetClass($assocName)
  3026. {
  3027. if (! isset($this->associationMappings[$assocName])) {
  3028. throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
  3029. }
  3030. return $this->associationMappings[$assocName]['targetEntity'];
  3031. }
  3032. /**
  3033. * {@inheritDoc}
  3034. */
  3035. public function getName()
  3036. {
  3037. return $this->name;
  3038. }
  3039. /**
  3040. * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
  3041. *
  3042. * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
  3043. *
  3044. * @param AbstractPlatform $platform
  3045. *
  3046. * @return string[]
  3047. * @psalm-return list<string>
  3048. */
  3049. public function getQuotedIdentifierColumnNames($platform)
  3050. {
  3051. $quotedColumnNames = [];
  3052. foreach ($this->identifier as $idProperty) {
  3053. if (isset($this->fieldMappings[$idProperty])) {
  3054. $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
  3055. ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
  3056. : $this->fieldMappings[$idProperty]['columnName'];
  3057. continue;
  3058. }
  3059. // Association defined as Id field
  3060. $joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
  3061. $assocQuotedColumnNames = array_map(
  3062. static function ($joinColumn) use ($platform) {
  3063. return isset($joinColumn['quoted'])
  3064. ? $platform->quoteIdentifier($joinColumn['name'])
  3065. : $joinColumn['name'];
  3066. },
  3067. $joinColumns
  3068. );
  3069. $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
  3070. }
  3071. return $quotedColumnNames;
  3072. }
  3073. /**
  3074. * Gets the (possibly quoted) column name of a mapped field for safe use in an SQL statement.
  3075. *
  3076. * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
  3077. *
  3078. * @param string $field
  3079. * @param AbstractPlatform $platform
  3080. *
  3081. * @return string
  3082. */
  3083. public function getQuotedColumnName($field, $platform)
  3084. {
  3085. return isset($this->fieldMappings[$field]['quoted'])
  3086. ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
  3087. : $this->fieldMappings[$field]['columnName'];
  3088. }
  3089. /**
  3090. * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement.
  3091. *
  3092. * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
  3093. *
  3094. * @param AbstractPlatform $platform
  3095. *
  3096. * @return string
  3097. */
  3098. public function getQuotedTableName($platform)
  3099. {
  3100. return isset($this->table['quoted'])
  3101. ? $platform->quoteIdentifier($this->table['name'])
  3102. : $this->table['name'];
  3103. }
  3104. /**
  3105. * Gets the (possibly quoted) name of the join table.
  3106. *
  3107. * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
  3108. *
  3109. * @param mixed[] $assoc
  3110. * @param AbstractPlatform $platform
  3111. *
  3112. * @return string
  3113. */
  3114. public function getQuotedJoinTableName(array $assoc, $platform)
  3115. {
  3116. return isset($assoc['joinTable']['quoted'])
  3117. ? $platform->quoteIdentifier($assoc['joinTable']['name'])
  3118. : $assoc['joinTable']['name'];
  3119. }
  3120. /**
  3121. * {@inheritDoc}
  3122. */
  3123. public function isAssociationInverseSide($fieldName)
  3124. {
  3125. return isset($this->associationMappings[$fieldName])
  3126. && ! $this->associationMappings[$fieldName]['isOwningSide'];
  3127. }
  3128. /**
  3129. * {@inheritDoc}
  3130. */
  3131. public function getAssociationMappedByTargetField($fieldName)
  3132. {
  3133. return $this->associationMappings[$fieldName]['mappedBy'];
  3134. }
  3135. /**
  3136. * @param string $targetClass
  3137. *
  3138. * @return mixed[][]
  3139. * @psalm-return array<string, array<string, mixed>>
  3140. */
  3141. public function getAssociationsByTargetClass($targetClass)
  3142. {
  3143. $relations = [];
  3144. foreach ($this->associationMappings as $mapping) {
  3145. if ($mapping['targetEntity'] === $targetClass) {
  3146. $relations[$mapping['fieldName']] = $mapping;
  3147. }
  3148. }
  3149. return $relations;
  3150. }
  3151. /**
  3152. * @param string|null $className
  3153. * @psalm-param ?class-string $className
  3154. *
  3155. * @return string|null null if the input value is null
  3156. */
  3157. public function fullyQualifiedClassName($className)
  3158. {
  3159. if (empty($className)) {
  3160. return $className;
  3161. }
  3162. if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
  3163. return $this->namespace . '\\' . $className;
  3164. }
  3165. return $className;
  3166. }
  3167. /**
  3168. * @param string $name
  3169. *
  3170. * @return mixed
  3171. */
  3172. public function getMetadataValue($name)
  3173. {
  3174. if (isset($this->$name)) {
  3175. return $this->$name;
  3176. }
  3177. return null;
  3178. }
  3179. /**
  3180. * Map Embedded Class
  3181. *
  3182. * @psalm-param array<string, mixed> $mapping
  3183. *
  3184. * @return void
  3185. *
  3186. * @throws MappingException
  3187. */
  3188. public function mapEmbedded(array $mapping)
  3189. {
  3190. $this->assertFieldNotMapped($mapping['fieldName']);
  3191. if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) {
  3192. $type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
  3193. if ($type instanceof ReflectionNamedType) {
  3194. $mapping['class'] = $type->getName();
  3195. }
  3196. }
  3197. $this->embeddedClasses[$mapping['fieldName']] = [
  3198. 'class' => $this->fullyQualifiedClassName($mapping['class']),
  3199. 'columnPrefix' => $mapping['columnPrefix'] ?? null,
  3200. 'declaredField' => $mapping['declaredField'] ?? null,
  3201. 'originalField' => $mapping['originalField'] ?? null,
  3202. ];
  3203. }
  3204. /**
  3205. * Inline the embeddable class
  3206. *
  3207. * @param string $property
  3208. *
  3209. * @return void
  3210. */
  3211. public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
  3212. {
  3213. foreach ($embeddable->fieldMappings as $fieldMapping) {
  3214. $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->name;
  3215. $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
  3216. ? $property . '.' . $fieldMapping['declaredField']
  3217. : $property;
  3218. $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldMapping['fieldName'];
  3219. $fieldMapping['fieldName'] = $property . '.' . $fieldMapping['fieldName'];
  3220. if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
  3221. $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
  3222. } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
  3223. $fieldMapping['columnName'] = $this->namingStrategy
  3224. ->embeddedFieldToColumnName(
  3225. $property,
  3226. $fieldMapping['columnName'],
  3227. $this->reflClass->name,
  3228. $embeddable->reflClass->name
  3229. );
  3230. }
  3231. $this->mapField($fieldMapping);
  3232. }
  3233. }
  3234. /**
  3235. * @throws MappingException
  3236. */
  3237. private function assertFieldNotMapped(string $fieldName): void
  3238. {
  3239. if (
  3240. isset($this->fieldMappings[$fieldName]) ||
  3241. isset($this->associationMappings[$fieldName]) ||
  3242. isset($this->embeddedClasses[$fieldName])
  3243. ) {
  3244. throw MappingException::duplicateFieldMapping($this->name, $fieldName);
  3245. }
  3246. }
  3247. /**
  3248. * Gets the sequence name based on class metadata.
  3249. *
  3250. * @return string
  3251. *
  3252. * @todo Sequence names should be computed in DBAL depending on the platform
  3253. */
  3254. public function getSequenceName(AbstractPlatform $platform)
  3255. {
  3256. $sequencePrefix = $this->getSequencePrefix($platform);
  3257. $columnName = $this->getSingleIdentifierColumnName();
  3258. return $sequencePrefix . '_' . $columnName . '_seq';
  3259. }
  3260. /**
  3261. * Gets the sequence name prefix based on class metadata.
  3262. *
  3263. * @return string
  3264. *
  3265. * @todo Sequence names should be computed in DBAL depending on the platform
  3266. */
  3267. public function getSequencePrefix(AbstractPlatform $platform)
  3268. {
  3269. $tableName = $this->getTableName();
  3270. $sequencePrefix = $tableName;
  3271. // Prepend the schema name to the table name if there is one
  3272. $schemaName = $this->getSchemaName();
  3273. if ($schemaName) {
  3274. $sequencePrefix = $schemaName . '.' . $tableName;
  3275. if (! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
  3276. $sequencePrefix = $schemaName . '__' . $tableName;
  3277. }
  3278. }
  3279. return $sequencePrefix;
  3280. }
  3281. /**
  3282. * @psalm-param array<string, mixed> $mapping
  3283. */
  3284. private function assertMappingOrderBy(array $mapping): void
  3285. {
  3286. if (isset($mapping['orderBy']) && ! is_array($mapping['orderBy'])) {
  3287. throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
  3288. }
  3289. }
  3290. }