BelongsToMany.php 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646
  1. <?php
  2. namespace Illuminate\Database\Eloquent\Relations;
  3. use Closure;
  4. use Illuminate\Contracts\Support\Arrayable;
  5. use Illuminate\Database\Eloquent\Builder;
  6. use Illuminate\Database\Eloquent\Collection;
  7. use Illuminate\Database\Eloquent\Model;
  8. use Illuminate\Database\Eloquent\ModelNotFoundException;
  9. use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
  10. use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
  11. use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable;
  12. use Illuminate\Database\Query\Grammars\MySqlGrammar;
  13. use Illuminate\Database\UniqueConstraintViolationException;
  14. use Illuminate\Support\Str;
  15. use InvalidArgumentException;
  16. class BelongsToMany extends Relation
  17. {
  18. use InteractsWithDictionary, InteractsWithPivotTable;
  19. /**
  20. * The intermediate table for the relation.
  21. *
  22. * @var string
  23. */
  24. protected $table;
  25. /**
  26. * The foreign key of the parent model.
  27. *
  28. * @var string
  29. */
  30. protected $foreignPivotKey;
  31. /**
  32. * The associated key of the relation.
  33. *
  34. * @var string
  35. */
  36. protected $relatedPivotKey;
  37. /**
  38. * The key name of the parent model.
  39. *
  40. * @var string
  41. */
  42. protected $parentKey;
  43. /**
  44. * The key name of the related model.
  45. *
  46. * @var string
  47. */
  48. protected $relatedKey;
  49. /**
  50. * The "name" of the relationship.
  51. *
  52. * @var string
  53. */
  54. protected $relationName;
  55. /**
  56. * The pivot table columns to retrieve.
  57. *
  58. * @var array<string|\Illuminate\Contracts\Database\Query\Expression>
  59. */
  60. protected $pivotColumns = [];
  61. /**
  62. * Any pivot table restrictions for where clauses.
  63. *
  64. * @var array
  65. */
  66. protected $pivotWheres = [];
  67. /**
  68. * Any pivot table restrictions for whereIn clauses.
  69. *
  70. * @var array
  71. */
  72. protected $pivotWhereIns = [];
  73. /**
  74. * Any pivot table restrictions for whereNull clauses.
  75. *
  76. * @var array
  77. */
  78. protected $pivotWhereNulls = [];
  79. /**
  80. * The default values for the pivot columns.
  81. *
  82. * @var array
  83. */
  84. protected $pivotValues = [];
  85. /**
  86. * Indicates if timestamps are available on the pivot table.
  87. *
  88. * @var bool
  89. */
  90. public $withTimestamps = false;
  91. /**
  92. * The custom pivot table column for the created_at timestamp.
  93. *
  94. * @var string
  95. */
  96. protected $pivotCreatedAt;
  97. /**
  98. * The custom pivot table column for the updated_at timestamp.
  99. *
  100. * @var string
  101. */
  102. protected $pivotUpdatedAt;
  103. /**
  104. * The class name of the custom pivot model to use for the relationship.
  105. *
  106. * @var string
  107. */
  108. protected $using;
  109. /**
  110. * The name of the accessor to use for the "pivot" relationship.
  111. *
  112. * @var string
  113. */
  114. protected $accessor = 'pivot';
  115. /**
  116. * Create a new belongs to many relationship instance.
  117. *
  118. * @param \Illuminate\Database\Eloquent\Builder $query
  119. * @param \Illuminate\Database\Eloquent\Model $parent
  120. * @param string|class-string<\Illuminate\Database\Eloquent\Model> $table
  121. * @param string $foreignPivotKey
  122. * @param string $relatedPivotKey
  123. * @param string $parentKey
  124. * @param string $relatedKey
  125. * @param string|null $relationName
  126. * @return void
  127. */
  128. public function __construct(Builder $query, Model $parent, $table, $foreignPivotKey,
  129. $relatedPivotKey, $parentKey, $relatedKey, $relationName = null)
  130. {
  131. $this->parentKey = $parentKey;
  132. $this->relatedKey = $relatedKey;
  133. $this->relationName = $relationName;
  134. $this->relatedPivotKey = $relatedPivotKey;
  135. $this->foreignPivotKey = $foreignPivotKey;
  136. $this->table = $this->resolveTableName($table);
  137. parent::__construct($query, $parent);
  138. }
  139. /**
  140. * Attempt to resolve the intermediate table name from the given string.
  141. *
  142. * @param string $table
  143. * @return string
  144. */
  145. protected function resolveTableName($table)
  146. {
  147. if (! str_contains($table, '\\') || ! class_exists($table)) {
  148. return $table;
  149. }
  150. $model = new $table;
  151. if (! $model instanceof Model) {
  152. return $table;
  153. }
  154. if (in_array(AsPivot::class, class_uses_recursive($model))) {
  155. $this->using($table);
  156. }
  157. return $model->getTable();
  158. }
  159. /**
  160. * Set the base constraints on the relation query.
  161. *
  162. * @return void
  163. */
  164. public function addConstraints()
  165. {
  166. $this->performJoin();
  167. if (static::$constraints) {
  168. $this->addWhereConstraints();
  169. }
  170. }
  171. /**
  172. * Set the join clause for the relation query.
  173. *
  174. * @param \Illuminate\Database\Eloquent\Builder|null $query
  175. * @return $this
  176. */
  177. protected function performJoin($query = null)
  178. {
  179. $query = $query ?: $this->query;
  180. // We need to join to the intermediate table on the related model's primary
  181. // key column with the intermediate table's foreign key for the related
  182. // model instance. Then we can set the "where" for the parent models.
  183. $query->join(
  184. $this->table,
  185. $this->getQualifiedRelatedKeyName(),
  186. '=',
  187. $this->getQualifiedRelatedPivotKeyName()
  188. );
  189. return $this;
  190. }
  191. /**
  192. * Set the where clause for the relation query.
  193. *
  194. * @return $this
  195. */
  196. protected function addWhereConstraints()
  197. {
  198. $this->query->where(
  199. $this->getQualifiedForeignPivotKeyName(), '=', $this->parent->{$this->parentKey}
  200. );
  201. return $this;
  202. }
  203. /**
  204. * Set the constraints for an eager load of the relation.
  205. *
  206. * @param array $models
  207. * @return void
  208. */
  209. public function addEagerConstraints(array $models)
  210. {
  211. $whereIn = $this->whereInMethod($this->parent, $this->parentKey);
  212. $this->whereInEager(
  213. $whereIn,
  214. $this->getQualifiedForeignPivotKeyName(),
  215. $this->getKeys($models, $this->parentKey)
  216. );
  217. }
  218. /**
  219. * Initialize the relation on a set of models.
  220. *
  221. * @param array $models
  222. * @param string $relation
  223. * @return array
  224. */
  225. public function initRelation(array $models, $relation)
  226. {
  227. foreach ($models as $model) {
  228. $model->setRelation($relation, $this->related->newCollection());
  229. }
  230. return $models;
  231. }
  232. /**
  233. * Match the eagerly loaded results to their parents.
  234. *
  235. * @param array $models
  236. * @param \Illuminate\Database\Eloquent\Collection $results
  237. * @param string $relation
  238. * @return array
  239. */
  240. public function match(array $models, Collection $results, $relation)
  241. {
  242. $dictionary = $this->buildDictionary($results);
  243. // Once we have an array dictionary of child objects we can easily match the
  244. // children back to their parent using the dictionary and the keys on the
  245. // parent models. Then we should return these hydrated models back out.
  246. foreach ($models as $model) {
  247. $key = $this->getDictionaryKey($model->{$this->parentKey});
  248. if (isset($dictionary[$key])) {
  249. $model->setRelation(
  250. $relation, $this->related->newCollection($dictionary[$key])
  251. );
  252. }
  253. }
  254. return $models;
  255. }
  256. /**
  257. * Build model dictionary keyed by the relation's foreign key.
  258. *
  259. * @param \Illuminate\Database\Eloquent\Collection $results
  260. * @return array
  261. */
  262. protected function buildDictionary(Collection $results)
  263. {
  264. // First we'll build a dictionary of child models keyed by the foreign key
  265. // of the relation so that we will easily and quickly match them to the
  266. // parents without having a possibly slow inner loop for every model.
  267. $dictionary = [];
  268. foreach ($results as $result) {
  269. $value = $this->getDictionaryKey($result->{$this->accessor}->{$this->foreignPivotKey});
  270. $dictionary[$value][] = $result;
  271. }
  272. return $dictionary;
  273. }
  274. /**
  275. * Get the class being used for pivot models.
  276. *
  277. * @return string
  278. */
  279. public function getPivotClass()
  280. {
  281. return $this->using ?? Pivot::class;
  282. }
  283. /**
  284. * Specify the custom pivot model to use for the relationship.
  285. *
  286. * @param string $class
  287. * @return $this
  288. */
  289. public function using($class)
  290. {
  291. $this->using = $class;
  292. return $this;
  293. }
  294. /**
  295. * Specify the custom pivot accessor to use for the relationship.
  296. *
  297. * @param string $accessor
  298. * @return $this
  299. */
  300. public function as($accessor)
  301. {
  302. $this->accessor = $accessor;
  303. return $this;
  304. }
  305. /**
  306. * Set a where clause for a pivot table column.
  307. *
  308. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  309. * @param mixed $operator
  310. * @param mixed $value
  311. * @param string $boolean
  312. * @return $this
  313. */
  314. public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
  315. {
  316. $this->pivotWheres[] = func_get_args();
  317. return $this->where($this->qualifyPivotColumn($column), $operator, $value, $boolean);
  318. }
  319. /**
  320. * Set a "where between" clause for a pivot table column.
  321. *
  322. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  323. * @param array $values
  324. * @param string $boolean
  325. * @param bool $not
  326. * @return $this
  327. */
  328. public function wherePivotBetween($column, array $values, $boolean = 'and', $not = false)
  329. {
  330. return $this->whereBetween($this->qualifyPivotColumn($column), $values, $boolean, $not);
  331. }
  332. /**
  333. * Set a "or where between" clause for a pivot table column.
  334. *
  335. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  336. * @param array $values
  337. * @return $this
  338. */
  339. public function orWherePivotBetween($column, array $values)
  340. {
  341. return $this->wherePivotBetween($column, $values, 'or');
  342. }
  343. /**
  344. * Set a "where pivot not between" clause for a pivot table column.
  345. *
  346. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  347. * @param array $values
  348. * @param string $boolean
  349. * @return $this
  350. */
  351. public function wherePivotNotBetween($column, array $values, $boolean = 'and')
  352. {
  353. return $this->wherePivotBetween($column, $values, $boolean, true);
  354. }
  355. /**
  356. * Set a "or where not between" clause for a pivot table column.
  357. *
  358. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  359. * @param array $values
  360. * @return $this
  361. */
  362. public function orWherePivotNotBetween($column, array $values)
  363. {
  364. return $this->wherePivotBetween($column, $values, 'or', true);
  365. }
  366. /**
  367. * Set a "where in" clause for a pivot table column.
  368. *
  369. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  370. * @param mixed $values
  371. * @param string $boolean
  372. * @param bool $not
  373. * @return $this
  374. */
  375. public function wherePivotIn($column, $values, $boolean = 'and', $not = false)
  376. {
  377. $this->pivotWhereIns[] = func_get_args();
  378. return $this->whereIn($this->qualifyPivotColumn($column), $values, $boolean, $not);
  379. }
  380. /**
  381. * Set an "or where" clause for a pivot table column.
  382. *
  383. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  384. * @param mixed $operator
  385. * @param mixed $value
  386. * @return $this
  387. */
  388. public function orWherePivot($column, $operator = null, $value = null)
  389. {
  390. return $this->wherePivot($column, $operator, $value, 'or');
  391. }
  392. /**
  393. * Set a where clause for a pivot table column.
  394. *
  395. * In addition, new pivot records will receive this value.
  396. *
  397. * @param string|\Illuminate\Contracts\Database\Query\Expression|array<string, string> $column
  398. * @param mixed $value
  399. * @return $this
  400. *
  401. * @throws \InvalidArgumentException
  402. */
  403. public function withPivotValue($column, $value = null)
  404. {
  405. if (is_array($column)) {
  406. foreach ($column as $name => $value) {
  407. $this->withPivotValue($name, $value);
  408. }
  409. return $this;
  410. }
  411. if (is_null($value)) {
  412. throw new InvalidArgumentException('The provided value may not be null.');
  413. }
  414. $this->pivotValues[] = compact('column', 'value');
  415. return $this->wherePivot($column, '=', $value);
  416. }
  417. /**
  418. * Set an "or where in" clause for a pivot table column.
  419. *
  420. * @param string $column
  421. * @param mixed $values
  422. * @return $this
  423. */
  424. public function orWherePivotIn($column, $values)
  425. {
  426. return $this->wherePivotIn($column, $values, 'or');
  427. }
  428. /**
  429. * Set a "where not in" clause for a pivot table column.
  430. *
  431. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  432. * @param mixed $values
  433. * @param string $boolean
  434. * @return $this
  435. */
  436. public function wherePivotNotIn($column, $values, $boolean = 'and')
  437. {
  438. return $this->wherePivotIn($column, $values, $boolean, true);
  439. }
  440. /**
  441. * Set an "or where not in" clause for a pivot table column.
  442. *
  443. * @param string $column
  444. * @param mixed $values
  445. * @return $this
  446. */
  447. public function orWherePivotNotIn($column, $values)
  448. {
  449. return $this->wherePivotNotIn($column, $values, 'or');
  450. }
  451. /**
  452. * Set a "where null" clause for a pivot table column.
  453. *
  454. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  455. * @param string $boolean
  456. * @param bool $not
  457. * @return $this
  458. */
  459. public function wherePivotNull($column, $boolean = 'and', $not = false)
  460. {
  461. $this->pivotWhereNulls[] = func_get_args();
  462. return $this->whereNull($this->qualifyPivotColumn($column), $boolean, $not);
  463. }
  464. /**
  465. * Set a "where not null" clause for a pivot table column.
  466. *
  467. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  468. * @param string $boolean
  469. * @return $this
  470. */
  471. public function wherePivotNotNull($column, $boolean = 'and')
  472. {
  473. return $this->wherePivotNull($column, $boolean, true);
  474. }
  475. /**
  476. * Set a "or where null" clause for a pivot table column.
  477. *
  478. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  479. * @param bool $not
  480. * @return $this
  481. */
  482. public function orWherePivotNull($column, $not = false)
  483. {
  484. return $this->wherePivotNull($column, 'or', $not);
  485. }
  486. /**
  487. * Set a "or where not null" clause for a pivot table column.
  488. *
  489. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  490. * @return $this
  491. */
  492. public function orWherePivotNotNull($column)
  493. {
  494. return $this->orWherePivotNull($column, true);
  495. }
  496. /**
  497. * Add an "order by" clause for a pivot table column.
  498. *
  499. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  500. * @param string $direction
  501. * @return $this
  502. */
  503. public function orderByPivot($column, $direction = 'asc')
  504. {
  505. return $this->orderBy($this->qualifyPivotColumn($column), $direction);
  506. }
  507. /**
  508. * Find a related model by its primary key or return a new instance of the related model.
  509. *
  510. * @param mixed $id
  511. * @param array $columns
  512. * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
  513. */
  514. public function findOrNew($id, $columns = ['*'])
  515. {
  516. if (is_null($instance = $this->find($id, $columns))) {
  517. $instance = $this->related->newInstance();
  518. }
  519. return $instance;
  520. }
  521. /**
  522. * Get the first related model record matching the attributes or instantiate it.
  523. *
  524. * @param array $attributes
  525. * @param array $values
  526. * @return \Illuminate\Database\Eloquent\Model
  527. */
  528. public function firstOrNew(array $attributes = [], array $values = [])
  529. {
  530. if (is_null($instance = $this->related->where($attributes)->first())) {
  531. $instance = $this->related->newInstance(array_merge($attributes, $values));
  532. }
  533. return $instance;
  534. }
  535. /**
  536. * Get the first record matching the attributes. If the record is not found, create it.
  537. *
  538. * @param array $attributes
  539. * @param array $values
  540. * @param array $joining
  541. * @param bool $touch
  542. * @return \Illuminate\Database\Eloquent\Model
  543. */
  544. public function firstOrCreate(array $attributes = [], array $values = [], array $joining = [], $touch = true)
  545. {
  546. if (is_null($instance = (clone $this)->where($attributes)->first())) {
  547. if (is_null($instance = $this->related->where($attributes)->first())) {
  548. $instance = $this->createOrFirst($attributes, $values, $joining, $touch);
  549. } else {
  550. try {
  551. $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch));
  552. } catch (UniqueConstraintViolationException) {
  553. // Nothing to do, the model was already attached...
  554. }
  555. }
  556. }
  557. return $instance;
  558. }
  559. /**
  560. * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record.
  561. *
  562. * @param array $attributes
  563. * @param array $values
  564. * @param array $joining
  565. * @param bool $touch
  566. * @return \Illuminate\Database\Eloquent\Model
  567. */
  568. public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true)
  569. {
  570. try {
  571. return $this->getQuery()->withSavePointIfNeeded(fn () => $this->create(array_merge($attributes, $values), $joining, $touch));
  572. } catch (UniqueConstraintViolationException $e) {
  573. // ...
  574. }
  575. try {
  576. return tap($this->related->where($attributes)->first() ?? throw $e, function ($instance) use ($joining, $touch) {
  577. $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch));
  578. });
  579. } catch (UniqueConstraintViolationException $e) {
  580. return (clone $this)->useWritePdo()->where($attributes)->first() ?? throw $e;
  581. }
  582. }
  583. /**
  584. * Create or update a related record matching the attributes, and fill it with values.
  585. *
  586. * @param array $attributes
  587. * @param array $values
  588. * @param array $joining
  589. * @param bool $touch
  590. * @return \Illuminate\Database\Eloquent\Model
  591. */
  592. public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
  593. {
  594. return tap($this->firstOrCreate($attributes, $values, $joining, $touch), function ($instance) use ($values) {
  595. if (! $instance->wasRecentlyCreated) {
  596. $instance->fill($values);
  597. $instance->save(['touch' => false]);
  598. }
  599. });
  600. }
  601. /**
  602. * Find a related model by its primary key.
  603. *
  604. * @param mixed $id
  605. * @param array $columns
  606. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
  607. */
  608. public function find($id, $columns = ['*'])
  609. {
  610. if (! $id instanceof Model && (is_array($id) || $id instanceof Arrayable)) {
  611. return $this->findMany($id, $columns);
  612. }
  613. return $this->where(
  614. $this->getRelated()->getQualifiedKeyName(), '=', $this->parseId($id)
  615. )->first($columns);
  616. }
  617. /**
  618. * Find multiple related models by their primary keys.
  619. *
  620. * @param \Illuminate\Contracts\Support\Arrayable|array $ids
  621. * @param array $columns
  622. * @return \Illuminate\Database\Eloquent\Collection
  623. */
  624. public function findMany($ids, $columns = ['*'])
  625. {
  626. $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
  627. if (empty($ids)) {
  628. return $this->getRelated()->newCollection();
  629. }
  630. return $this->whereKey(
  631. $this->parseIds($ids)
  632. )->get($columns);
  633. }
  634. /**
  635. * Find a related model by its primary key or throw an exception.
  636. *
  637. * @param mixed $id
  638. * @param array $columns
  639. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
  640. *
  641. * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
  642. */
  643. public function findOrFail($id, $columns = ['*'])
  644. {
  645. $result = $this->find($id, $columns);
  646. $id = $id instanceof Arrayable ? $id->toArray() : $id;
  647. if (is_array($id)) {
  648. if (count($result) === count(array_unique($id))) {
  649. return $result;
  650. }
  651. } elseif (! is_null($result)) {
  652. return $result;
  653. }
  654. throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
  655. }
  656. /**
  657. * Find a related model by its primary key or call a callback.
  658. *
  659. * @param mixed $id
  660. * @param \Closure|array $columns
  661. * @param \Closure|null $callback
  662. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed
  663. */
  664. public function findOr($id, $columns = ['*'], ?Closure $callback = null)
  665. {
  666. if ($columns instanceof Closure) {
  667. $callback = $columns;
  668. $columns = ['*'];
  669. }
  670. $result = $this->find($id, $columns);
  671. $id = $id instanceof Arrayable ? $id->toArray() : $id;
  672. if (is_array($id)) {
  673. if (count($result) === count(array_unique($id))) {
  674. return $result;
  675. }
  676. } elseif (! is_null($result)) {
  677. return $result;
  678. }
  679. return $callback();
  680. }
  681. /**
  682. * Add a basic where clause to the query, and return the first result.
  683. *
  684. * @param \Closure|string|array $column
  685. * @param mixed $operator
  686. * @param mixed $value
  687. * @param string $boolean
  688. * @return \Illuminate\Database\Eloquent\Model|static|null
  689. */
  690. public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
  691. {
  692. return $this->where($column, $operator, $value, $boolean)->first();
  693. }
  694. /**
  695. * Execute the query and get the first result.
  696. *
  697. * @param array $columns
  698. * @return \Illuminate\Database\Eloquent\Model|static|null
  699. */
  700. public function first($columns = ['*'])
  701. {
  702. $results = $this->take(1)->get($columns);
  703. return count($results) > 0 ? $results->first() : null;
  704. }
  705. /**
  706. * Execute the query and get the first result or throw an exception.
  707. *
  708. * @param array $columns
  709. * @return \Illuminate\Database\Eloquent\Model|static
  710. *
  711. * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
  712. */
  713. public function firstOrFail($columns = ['*'])
  714. {
  715. if (! is_null($model = $this->first($columns))) {
  716. return $model;
  717. }
  718. throw (new ModelNotFoundException)->setModel(get_class($this->related));
  719. }
  720. /**
  721. * Execute the query and get the first result or call a callback.
  722. *
  723. * @param \Closure|array $columns
  724. * @param \Closure|null $callback
  725. * @return \Illuminate\Database\Eloquent\Model|static|mixed
  726. */
  727. public function firstOr($columns = ['*'], ?Closure $callback = null)
  728. {
  729. if ($columns instanceof Closure) {
  730. $callback = $columns;
  731. $columns = ['*'];
  732. }
  733. if (! is_null($model = $this->first($columns))) {
  734. return $model;
  735. }
  736. return $callback();
  737. }
  738. /**
  739. * Get the results of the relationship.
  740. *
  741. * @return mixed
  742. */
  743. public function getResults()
  744. {
  745. return ! is_null($this->parent->{$this->parentKey})
  746. ? $this->get()
  747. : $this->related->newCollection();
  748. }
  749. /**
  750. * Execute the query as a "select" statement.
  751. *
  752. * @param array $columns
  753. * @return \Illuminate\Database\Eloquent\Collection
  754. */
  755. public function get($columns = ['*'])
  756. {
  757. // First we'll add the proper select columns onto the query so it is run with
  758. // the proper columns. Then, we will get the results and hydrate our pivot
  759. // models with the result of those columns as a separate model relation.
  760. $builder = $this->query->applyScopes();
  761. $columns = $builder->getQuery()->columns ? [] : $columns;
  762. $models = $builder->addSelect(
  763. $this->shouldSelect($columns)
  764. )->getModels();
  765. $this->hydratePivotRelation($models);
  766. // If we actually found models we will also eager load any relationships that
  767. // have been specified as needing to be eager loaded. This will solve the
  768. // n + 1 query problem for the developer and also increase performance.
  769. if (count($models) > 0) {
  770. $models = $builder->eagerLoadRelations($models);
  771. }
  772. return $this->query->applyAfterQueryCallbacks(
  773. $this->related->newCollection($models)
  774. );
  775. }
  776. /**
  777. * Get the select columns for the relation query.
  778. *
  779. * @param array $columns
  780. * @return array
  781. */
  782. protected function shouldSelect(array $columns = ['*'])
  783. {
  784. if ($columns == ['*']) {
  785. $columns = [$this->related->getTable().'.*'];
  786. }
  787. return array_merge($columns, $this->aliasedPivotColumns());
  788. }
  789. /**
  790. * Get the pivot columns for the relation.
  791. *
  792. * "pivot_" is prefixed at each column for easy removal later.
  793. *
  794. * @return array
  795. */
  796. protected function aliasedPivotColumns()
  797. {
  798. $defaults = [$this->foreignPivotKey, $this->relatedPivotKey];
  799. return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
  800. return $this->qualifyPivotColumn($column).' as pivot_'.$column;
  801. })->unique()->all();
  802. }
  803. /**
  804. * Get a paginator for the "select" statement.
  805. *
  806. * @param int|null $perPage
  807. * @param array $columns
  808. * @param string $pageName
  809. * @param int|null $page
  810. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
  811. */
  812. public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  813. {
  814. $this->query->addSelect($this->shouldSelect($columns));
  815. return tap($this->query->paginate($perPage, $columns, $pageName, $page), function ($paginator) {
  816. $this->hydratePivotRelation($paginator->items());
  817. });
  818. }
  819. /**
  820. * Paginate the given query into a simple paginator.
  821. *
  822. * @param int|null $perPage
  823. * @param array $columns
  824. * @param string $pageName
  825. * @param int|null $page
  826. * @return \Illuminate\Contracts\Pagination\Paginator
  827. */
  828. public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  829. {
  830. $this->query->addSelect($this->shouldSelect($columns));
  831. return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function ($paginator) {
  832. $this->hydratePivotRelation($paginator->items());
  833. });
  834. }
  835. /**
  836. * Paginate the given query into a cursor paginator.
  837. *
  838. * @param int|null $perPage
  839. * @param array $columns
  840. * @param string $cursorName
  841. * @param string|null $cursor
  842. * @return \Illuminate\Contracts\Pagination\CursorPaginator
  843. */
  844. public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
  845. {
  846. $this->query->addSelect($this->shouldSelect($columns));
  847. return tap($this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor), function ($paginator) {
  848. $this->hydratePivotRelation($paginator->items());
  849. });
  850. }
  851. /**
  852. * Chunk the results of the query.
  853. *
  854. * @param int $count
  855. * @param callable $callback
  856. * @return bool
  857. */
  858. public function chunk($count, callable $callback)
  859. {
  860. return $this->prepareQueryBuilder()->chunk($count, function ($results, $page) use ($callback) {
  861. $this->hydratePivotRelation($results->all());
  862. return $callback($results, $page);
  863. });
  864. }
  865. /**
  866. * Chunk the results of a query by comparing numeric IDs.
  867. *
  868. * @param int $count
  869. * @param callable $callback
  870. * @param string|null $column
  871. * @param string|null $alias
  872. * @return bool
  873. */
  874. public function chunkById($count, callable $callback, $column = null, $alias = null)
  875. {
  876. return $this->orderedChunkById($count, $callback, $column, $alias);
  877. }
  878. /**
  879. * Chunk the results of a query by comparing IDs in descending order.
  880. *
  881. * @param int $count
  882. * @param callable $callback
  883. * @param string|null $column
  884. * @param string|null $alias
  885. * @return bool
  886. */
  887. public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null)
  888. {
  889. return $this->orderedChunkById($count, $callback, $column, $alias, descending: true);
  890. }
  891. /**
  892. * Execute a callback over each item while chunking by ID.
  893. *
  894. * @param callable $callback
  895. * @param int $count
  896. * @param string|null $column
  897. * @param string|null $alias
  898. * @return bool
  899. */
  900. public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
  901. {
  902. return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
  903. foreach ($results as $key => $value) {
  904. if ($callback($value, (($page - 1) * $count) + $key) === false) {
  905. return false;
  906. }
  907. }
  908. }, $column, $alias);
  909. }
  910. /**
  911. * Chunk the results of a query by comparing IDs in a given order.
  912. *
  913. * @param int $count
  914. * @param callable $callback
  915. * @param string|null $column
  916. * @param string|null $alias
  917. * @param bool $descending
  918. * @return bool
  919. */
  920. public function orderedChunkById($count, callable $callback, $column = null, $alias = null, $descending = false)
  921. {
  922. $column ??= $this->getRelated()->qualifyColumn(
  923. $this->getRelatedKeyName()
  924. );
  925. $alias ??= $this->getRelatedKeyName();
  926. return $this->prepareQueryBuilder()->orderedChunkById($count, function ($results, $page) use ($callback) {
  927. $this->hydratePivotRelation($results->all());
  928. return $callback($results, $page);
  929. }, $column, $alias, $descending);
  930. }
  931. /**
  932. * Execute a callback over each item while chunking.
  933. *
  934. * @param callable $callback
  935. * @param int $count
  936. * @return bool
  937. */
  938. public function each(callable $callback, $count = 1000)
  939. {
  940. return $this->chunk($count, function ($results) use ($callback) {
  941. foreach ($results as $key => $value) {
  942. if ($callback($value, $key) === false) {
  943. return false;
  944. }
  945. }
  946. });
  947. }
  948. /**
  949. * Query lazily, by chunks of the given size.
  950. *
  951. * @param int $chunkSize
  952. * @return \Illuminate\Support\LazyCollection
  953. */
  954. public function lazy($chunkSize = 1000)
  955. {
  956. return $this->prepareQueryBuilder()->lazy($chunkSize)->map(function ($model) {
  957. $this->hydratePivotRelation([$model]);
  958. return $model;
  959. });
  960. }
  961. /**
  962. * Query lazily, by chunking the results of a query by comparing IDs.
  963. *
  964. * @param int $chunkSize
  965. * @param string|null $column
  966. * @param string|null $alias
  967. * @return \Illuminate\Support\LazyCollection
  968. */
  969. public function lazyById($chunkSize = 1000, $column = null, $alias = null)
  970. {
  971. $column ??= $this->getRelated()->qualifyColumn(
  972. $this->getRelatedKeyName()
  973. );
  974. $alias ??= $this->getRelatedKeyName();
  975. return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias)->map(function ($model) {
  976. $this->hydratePivotRelation([$model]);
  977. return $model;
  978. });
  979. }
  980. /**
  981. * Query lazily, by chunking the results of a query by comparing IDs in descending order.
  982. *
  983. * @param int $chunkSize
  984. * @param string|null $column
  985. * @param string|null $alias
  986. * @return \Illuminate\Support\LazyCollection
  987. */
  988. public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
  989. {
  990. $column ??= $this->getRelated()->qualifyColumn(
  991. $this->getRelatedKeyName()
  992. );
  993. $alias ??= $this->getRelatedKeyName();
  994. return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias)->map(function ($model) {
  995. $this->hydratePivotRelation([$model]);
  996. return $model;
  997. });
  998. }
  999. /**
  1000. * Get a lazy collection for the given query.
  1001. *
  1002. * @return \Illuminate\Support\LazyCollection
  1003. */
  1004. public function cursor()
  1005. {
  1006. return $this->prepareQueryBuilder()->cursor()->map(function ($model) {
  1007. $this->hydratePivotRelation([$model]);
  1008. return $model;
  1009. });
  1010. }
  1011. /**
  1012. * Prepare the query builder for query execution.
  1013. *
  1014. * @return \Illuminate\Database\Eloquent\Builder
  1015. */
  1016. protected function prepareQueryBuilder()
  1017. {
  1018. return $this->query->addSelect($this->shouldSelect());
  1019. }
  1020. /**
  1021. * Hydrate the pivot table relationship on the models.
  1022. *
  1023. * @param array $models
  1024. * @return void
  1025. */
  1026. protected function hydratePivotRelation(array $models)
  1027. {
  1028. // To hydrate the pivot relationship, we will just gather the pivot attributes
  1029. // and create a new Pivot model, which is basically a dynamic model that we
  1030. // will set the attributes, table, and connections on it so it will work.
  1031. foreach ($models as $model) {
  1032. $model->setRelation($this->accessor, $this->newExistingPivot(
  1033. $this->migratePivotAttributes($model)
  1034. ));
  1035. }
  1036. }
  1037. /**
  1038. * Get the pivot attributes from a model.
  1039. *
  1040. * @param \Illuminate\Database\Eloquent\Model $model
  1041. * @return array
  1042. */
  1043. protected function migratePivotAttributes(Model $model)
  1044. {
  1045. $values = [];
  1046. foreach ($model->getAttributes() as $key => $value) {
  1047. // To get the pivots attributes we will just take any of the attributes which
  1048. // begin with "pivot_" and add those to this arrays, as well as unsetting
  1049. // them from the parent's models since they exist in a different table.
  1050. if (str_starts_with($key, 'pivot_')) {
  1051. $values[substr($key, 6)] = $value;
  1052. unset($model->$key);
  1053. }
  1054. }
  1055. return $values;
  1056. }
  1057. /**
  1058. * If we're touching the parent model, touch.
  1059. *
  1060. * @return void
  1061. */
  1062. public function touchIfTouching()
  1063. {
  1064. if ($this->touchingParent()) {
  1065. $this->getParent()->touch();
  1066. }
  1067. if ($this->getParent()->touches($this->relationName)) {
  1068. $this->touch();
  1069. }
  1070. }
  1071. /**
  1072. * Determine if we should touch the parent on sync.
  1073. *
  1074. * @return bool
  1075. */
  1076. protected function touchingParent()
  1077. {
  1078. return $this->getRelated()->touches($this->guessInverseRelation());
  1079. }
  1080. /**
  1081. * Attempt to guess the name of the inverse of the relation.
  1082. *
  1083. * @return string
  1084. */
  1085. protected function guessInverseRelation()
  1086. {
  1087. return Str::camel(Str::pluralStudly(class_basename($this->getParent())));
  1088. }
  1089. /**
  1090. * Touch all of the related models for the relationship.
  1091. *
  1092. * E.g.: Touch all roles associated with this user.
  1093. *
  1094. * @return void
  1095. */
  1096. public function touch()
  1097. {
  1098. if ($this->related->isIgnoringTouch()) {
  1099. return;
  1100. }
  1101. $columns = [
  1102. $this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(),
  1103. ];
  1104. // If we actually have IDs for the relation, we will run the query to update all
  1105. // the related model's timestamps, to make sure these all reflect the changes
  1106. // to the parent models. This will help us keep any caching synced up here.
  1107. if (count($ids = $this->allRelatedIds()) > 0) {
  1108. $this->getRelated()->newQueryWithoutRelationships()->whereKey($ids)->update($columns);
  1109. }
  1110. }
  1111. /**
  1112. * Get all of the IDs for the related models.
  1113. *
  1114. * @return \Illuminate\Support\Collection
  1115. */
  1116. public function allRelatedIds()
  1117. {
  1118. return $this->newPivotQuery()->pluck($this->relatedPivotKey);
  1119. }
  1120. /**
  1121. * Save a new model and attach it to the parent model.
  1122. *
  1123. * @param \Illuminate\Database\Eloquent\Model $model
  1124. * @param array $pivotAttributes
  1125. * @param bool $touch
  1126. * @return \Illuminate\Database\Eloquent\Model
  1127. */
  1128. public function save(Model $model, array $pivotAttributes = [], $touch = true)
  1129. {
  1130. $model->save(['touch' => false]);
  1131. $this->attach($model, $pivotAttributes, $touch);
  1132. return $model;
  1133. }
  1134. /**
  1135. * Save a new model without raising any events and attach it to the parent model.
  1136. *
  1137. * @param \Illuminate\Database\Eloquent\Model $model
  1138. * @param array $pivotAttributes
  1139. * @param bool $touch
  1140. * @return \Illuminate\Database\Eloquent\Model
  1141. */
  1142. public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = true)
  1143. {
  1144. return Model::withoutEvents(function () use ($model, $pivotAttributes, $touch) {
  1145. return $this->save($model, $pivotAttributes, $touch);
  1146. });
  1147. }
  1148. /**
  1149. * Save an array of new models and attach them to the parent model.
  1150. *
  1151. * @param \Illuminate\Support\Collection|array $models
  1152. * @param array $pivotAttributes
  1153. * @return array
  1154. */
  1155. public function saveMany($models, array $pivotAttributes = [])
  1156. {
  1157. foreach ($models as $key => $model) {
  1158. $this->save($model, (array) ($pivotAttributes[$key] ?? []), false);
  1159. }
  1160. $this->touchIfTouching();
  1161. return $models;
  1162. }
  1163. /**
  1164. * Save an array of new models without raising any events and attach them to the parent model.
  1165. *
  1166. * @param \Illuminate\Support\Collection|array $models
  1167. * @param array $pivotAttributes
  1168. * @return array
  1169. */
  1170. public function saveManyQuietly($models, array $pivotAttributes = [])
  1171. {
  1172. return Model::withoutEvents(function () use ($models, $pivotAttributes) {
  1173. return $this->saveMany($models, $pivotAttributes);
  1174. });
  1175. }
  1176. /**
  1177. * Create a new instance of the related model.
  1178. *
  1179. * @param array $attributes
  1180. * @param array $joining
  1181. * @param bool $touch
  1182. * @return \Illuminate\Database\Eloquent\Model
  1183. */
  1184. public function create(array $attributes = [], array $joining = [], $touch = true)
  1185. {
  1186. $instance = $this->related->newInstance($attributes);
  1187. // Once we save the related model, we need to attach it to the base model via
  1188. // through intermediate table so we'll use the existing "attach" method to
  1189. // accomplish this which will insert the record and any more attributes.
  1190. $instance->save(['touch' => false]);
  1191. $this->attach($instance, $joining, $touch);
  1192. return $instance;
  1193. }
  1194. /**
  1195. * Create an array of new instances of the related models.
  1196. *
  1197. * @param iterable $records
  1198. * @param array $joinings
  1199. * @return array
  1200. */
  1201. public function createMany(iterable $records, array $joinings = [])
  1202. {
  1203. $instances = [];
  1204. foreach ($records as $key => $record) {
  1205. $instances[] = $this->create($record, (array) ($joinings[$key] ?? []), false);
  1206. }
  1207. $this->touchIfTouching();
  1208. return $instances;
  1209. }
  1210. /**
  1211. * Add the constraints for a relationship query.
  1212. *
  1213. * @param \Illuminate\Database\Eloquent\Builder $query
  1214. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  1215. * @param array|mixed $columns
  1216. * @return \Illuminate\Database\Eloquent\Builder
  1217. */
  1218. public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
  1219. {
  1220. if ($parentQuery->getQuery()->from == $query->getQuery()->from) {
  1221. return $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns);
  1222. }
  1223. $this->performJoin($query);
  1224. return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
  1225. }
  1226. /**
  1227. * Add the constraints for a relationship query on the same table.
  1228. *
  1229. * @param \Illuminate\Database\Eloquent\Builder $query
  1230. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  1231. * @param array|mixed $columns
  1232. * @return \Illuminate\Database\Eloquent\Builder
  1233. */
  1234. public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*'])
  1235. {
  1236. $query->select($columns);
  1237. $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash());
  1238. $this->related->setTable($hash);
  1239. $this->performJoin($query);
  1240. return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
  1241. }
  1242. /**
  1243. * Alias to set the "limit" value of the query.
  1244. *
  1245. * @param int $value
  1246. * @return $this
  1247. */
  1248. public function take($value)
  1249. {
  1250. return $this->limit($value);
  1251. }
  1252. /**
  1253. * Set the "limit" value of the query.
  1254. *
  1255. * @param int $value
  1256. * @return $this
  1257. */
  1258. public function limit($value)
  1259. {
  1260. if ($this->parent->exists) {
  1261. $this->query->limit($value);
  1262. } else {
  1263. $column = $this->getExistenceCompareKey();
  1264. $grammar = $this->query->getQuery()->getGrammar();
  1265. if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) {
  1266. $column = 'pivot_'.last(explode('.', $column));
  1267. }
  1268. $this->query->groupLimit($value, $column);
  1269. }
  1270. return $this;
  1271. }
  1272. /**
  1273. * Get the key for comparing against the parent key in "has" query.
  1274. *
  1275. * @return string
  1276. */
  1277. public function getExistenceCompareKey()
  1278. {
  1279. return $this->getQualifiedForeignPivotKeyName();
  1280. }
  1281. /**
  1282. * Specify that the pivot table has creation and update timestamps.
  1283. *
  1284. * @param mixed $createdAt
  1285. * @param mixed $updatedAt
  1286. * @return $this
  1287. */
  1288. public function withTimestamps($createdAt = null, $updatedAt = null)
  1289. {
  1290. $this->withTimestamps = true;
  1291. $this->pivotCreatedAt = $createdAt;
  1292. $this->pivotUpdatedAt = $updatedAt;
  1293. return $this->withPivot($this->createdAt(), $this->updatedAt());
  1294. }
  1295. /**
  1296. * Get the name of the "created at" column.
  1297. *
  1298. * @return string
  1299. */
  1300. public function createdAt()
  1301. {
  1302. return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn();
  1303. }
  1304. /**
  1305. * Get the name of the "updated at" column.
  1306. *
  1307. * @return string
  1308. */
  1309. public function updatedAt()
  1310. {
  1311. return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn();
  1312. }
  1313. /**
  1314. * Get the foreign key for the relation.
  1315. *
  1316. * @return string
  1317. */
  1318. public function getForeignPivotKeyName()
  1319. {
  1320. return $this->foreignPivotKey;
  1321. }
  1322. /**
  1323. * Get the fully qualified foreign key for the relation.
  1324. *
  1325. * @return string
  1326. */
  1327. public function getQualifiedForeignPivotKeyName()
  1328. {
  1329. return $this->qualifyPivotColumn($this->foreignPivotKey);
  1330. }
  1331. /**
  1332. * Get the "related key" for the relation.
  1333. *
  1334. * @return string
  1335. */
  1336. public function getRelatedPivotKeyName()
  1337. {
  1338. return $this->relatedPivotKey;
  1339. }
  1340. /**
  1341. * Get the fully qualified "related key" for the relation.
  1342. *
  1343. * @return string
  1344. */
  1345. public function getQualifiedRelatedPivotKeyName()
  1346. {
  1347. return $this->qualifyPivotColumn($this->relatedPivotKey);
  1348. }
  1349. /**
  1350. * Get the parent key for the relationship.
  1351. *
  1352. * @return string
  1353. */
  1354. public function getParentKeyName()
  1355. {
  1356. return $this->parentKey;
  1357. }
  1358. /**
  1359. * Get the fully qualified parent key name for the relation.
  1360. *
  1361. * @return string
  1362. */
  1363. public function getQualifiedParentKeyName()
  1364. {
  1365. return $this->parent->qualifyColumn($this->parentKey);
  1366. }
  1367. /**
  1368. * Get the related key for the relationship.
  1369. *
  1370. * @return string
  1371. */
  1372. public function getRelatedKeyName()
  1373. {
  1374. return $this->relatedKey;
  1375. }
  1376. /**
  1377. * Get the fully qualified related key name for the relation.
  1378. *
  1379. * @return string
  1380. */
  1381. public function getQualifiedRelatedKeyName()
  1382. {
  1383. return $this->related->qualifyColumn($this->relatedKey);
  1384. }
  1385. /**
  1386. * Get the intermediate table for the relationship.
  1387. *
  1388. * @return string
  1389. */
  1390. public function getTable()
  1391. {
  1392. return $this->table;
  1393. }
  1394. /**
  1395. * Get the relationship name for the relationship.
  1396. *
  1397. * @return string
  1398. */
  1399. public function getRelationName()
  1400. {
  1401. return $this->relationName;
  1402. }
  1403. /**
  1404. * Get the name of the pivot accessor for this relationship.
  1405. *
  1406. * @return string
  1407. */
  1408. public function getPivotAccessor()
  1409. {
  1410. return $this->accessor;
  1411. }
  1412. /**
  1413. * Get the pivot columns for this relationship.
  1414. *
  1415. * @return array
  1416. */
  1417. public function getPivotColumns()
  1418. {
  1419. return $this->pivotColumns;
  1420. }
  1421. /**
  1422. * Qualify the given column name by the pivot table.
  1423. *
  1424. * @param string|\Illuminate\Contracts\Database\Query\Expression $column
  1425. * @return string|\Illuminate\Contracts\Database\Query\Expression
  1426. */
  1427. public function qualifyPivotColumn($column)
  1428. {
  1429. if ($this->query->getQuery()->getGrammar()->isExpression($column)) {
  1430. return $column;
  1431. }
  1432. return str_contains($column, '.')
  1433. ? $column
  1434. : $this->table.'.'.$column;
  1435. }
  1436. }