vendor/doctrine/orm/src/Mapping/ClassMetadataInfo.php line 1213

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