ClassMetadataInfo.php 108 KB

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