HasManyThrough.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  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\InteractsWithDictionary;
  10. use Illuminate\Database\Eloquent\SoftDeletes;
  11. use Illuminate\Database\Query\Grammars\MySqlGrammar;
  12. use Illuminate\Database\UniqueConstraintViolationException;
  13. class HasManyThrough extends Relation
  14. {
  15. use InteractsWithDictionary;
  16. /**
  17. * The "through" parent model instance.
  18. *
  19. * @var \Illuminate\Database\Eloquent\Model
  20. */
  21. protected $throughParent;
  22. /**
  23. * The far parent model instance.
  24. *
  25. * @var \Illuminate\Database\Eloquent\Model
  26. */
  27. protected $farParent;
  28. /**
  29. * The near key on the relationship.
  30. *
  31. * @var string
  32. */
  33. protected $firstKey;
  34. /**
  35. * The far key on the relationship.
  36. *
  37. * @var string
  38. */
  39. protected $secondKey;
  40. /**
  41. * The local key on the relationship.
  42. *
  43. * @var string
  44. */
  45. protected $localKey;
  46. /**
  47. * The local key on the intermediary model.
  48. *
  49. * @var string
  50. */
  51. protected $secondLocalKey;
  52. /**
  53. * Create a new has many through relationship instance.
  54. *
  55. * @param \Illuminate\Database\Eloquent\Builder $query
  56. * @param \Illuminate\Database\Eloquent\Model $farParent
  57. * @param \Illuminate\Database\Eloquent\Model $throughParent
  58. * @param string $firstKey
  59. * @param string $secondKey
  60. * @param string $localKey
  61. * @param string $secondLocalKey
  62. * @return void
  63. */
  64. public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
  65. {
  66. $this->localKey = $localKey;
  67. $this->firstKey = $firstKey;
  68. $this->secondKey = $secondKey;
  69. $this->farParent = $farParent;
  70. $this->throughParent = $throughParent;
  71. $this->secondLocalKey = $secondLocalKey;
  72. parent::__construct($query, $throughParent);
  73. }
  74. /**
  75. * Convert the relationship to a "has one through" relationship.
  76. *
  77. * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
  78. */
  79. public function one()
  80. {
  81. return HasOneThrough::noConstraints(fn () => new HasOneThrough(
  82. $this->getQuery(),
  83. $this->farParent,
  84. $this->throughParent,
  85. $this->getFirstKeyName(),
  86. $this->secondKey,
  87. $this->getLocalKeyName(),
  88. $this->getSecondLocalKeyName(),
  89. ));
  90. }
  91. /**
  92. * Set the base constraints on the relation query.
  93. *
  94. * @return void
  95. */
  96. public function addConstraints()
  97. {
  98. $localValue = $this->farParent[$this->localKey];
  99. $this->performJoin();
  100. if (static::$constraints) {
  101. $this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue);
  102. }
  103. }
  104. /**
  105. * Set the join clause on the query.
  106. *
  107. * @param \Illuminate\Database\Eloquent\Builder|null $query
  108. * @return void
  109. */
  110. protected function performJoin(?Builder $query = null)
  111. {
  112. $query = $query ?: $this->query;
  113. $farKey = $this->getQualifiedFarKeyName();
  114. $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey);
  115. if ($this->throughParentSoftDeletes()) {
  116. $query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) {
  117. $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
  118. });
  119. }
  120. }
  121. /**
  122. * Get the fully qualified parent key name.
  123. *
  124. * @return string
  125. */
  126. public function getQualifiedParentKeyName()
  127. {
  128. return $this->parent->qualifyColumn($this->secondLocalKey);
  129. }
  130. /**
  131. * Determine whether "through" parent of the relation uses Soft Deletes.
  132. *
  133. * @return bool
  134. */
  135. public function throughParentSoftDeletes()
  136. {
  137. return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
  138. }
  139. /**
  140. * Indicate that trashed "through" parents should be included in the query.
  141. *
  142. * @return $this
  143. */
  144. public function withTrashedParents()
  145. {
  146. $this->query->withoutGlobalScope('SoftDeletableHasManyThrough');
  147. return $this;
  148. }
  149. /**
  150. * Set the constraints for an eager load of the relation.
  151. *
  152. * @param array $models
  153. * @return void
  154. */
  155. public function addEagerConstraints(array $models)
  156. {
  157. $whereIn = $this->whereInMethod($this->farParent, $this->localKey);
  158. $this->whereInEager(
  159. $whereIn,
  160. $this->getQualifiedFirstKeyName(),
  161. $this->getKeys($models, $this->localKey)
  162. );
  163. }
  164. /**
  165. * Initialize the relation on a set of models.
  166. *
  167. * @param array $models
  168. * @param string $relation
  169. * @return array
  170. */
  171. public function initRelation(array $models, $relation)
  172. {
  173. foreach ($models as $model) {
  174. $model->setRelation($relation, $this->related->newCollection());
  175. }
  176. return $models;
  177. }
  178. /**
  179. * Match the eagerly loaded results to their parents.
  180. *
  181. * @param array $models
  182. * @param \Illuminate\Database\Eloquent\Collection $results
  183. * @param string $relation
  184. * @return array
  185. */
  186. public function match(array $models, Collection $results, $relation)
  187. {
  188. $dictionary = $this->buildDictionary($results);
  189. // Once we have the dictionary we can simply spin through the parent models to
  190. // link them up with their children using the keyed dictionary to make the
  191. // matching very convenient and easy work. Then we'll just return them.
  192. foreach ($models as $model) {
  193. if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
  194. $model->setRelation(
  195. $relation, $this->related->newCollection($dictionary[$key])
  196. );
  197. }
  198. }
  199. return $models;
  200. }
  201. /**
  202. * Build model dictionary keyed by the relation's foreign key.
  203. *
  204. * @param \Illuminate\Database\Eloquent\Collection $results
  205. * @return array
  206. */
  207. protected function buildDictionary(Collection $results)
  208. {
  209. $dictionary = [];
  210. // First we will create a dictionary of models keyed by the foreign key of the
  211. // relationship as this will allow us to quickly access all of the related
  212. // models without having to do nested looping which will be quite slow.
  213. foreach ($results as $result) {
  214. $dictionary[$result->laravel_through_key][] = $result;
  215. }
  216. return $dictionary;
  217. }
  218. /**
  219. * Get the first related model record matching the attributes or instantiate it.
  220. *
  221. * @param array $attributes
  222. * @param array $values
  223. * @return \Illuminate\Database\Eloquent\Model
  224. */
  225. public function firstOrNew(array $attributes = [], array $values = [])
  226. {
  227. if (! is_null($instance = $this->where($attributes)->first())) {
  228. return $instance;
  229. }
  230. return $this->related->newInstance(array_merge($attributes, $values));
  231. }
  232. /**
  233. * Get the first record matching the attributes. If the record is not found, create it.
  234. *
  235. * @param array $attributes
  236. * @param array $values
  237. * @return \Illuminate\Database\Eloquent\Model
  238. */
  239. public function firstOrCreate(array $attributes = [], array $values = [])
  240. {
  241. if (! is_null($instance = (clone $this)->where($attributes)->first())) {
  242. return $instance;
  243. }
  244. return $this->createOrFirst(array_merge($attributes, $values));
  245. }
  246. /**
  247. * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record.
  248. *
  249. * @param array $attributes
  250. * @param array $values
  251. * @return \Illuminate\Database\Eloquent\Model
  252. */
  253. public function createOrFirst(array $attributes = [], array $values = [])
  254. {
  255. try {
  256. return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)));
  257. } catch (UniqueConstraintViolationException $exception) {
  258. return $this->where($attributes)->first() ?? throw $exception;
  259. }
  260. }
  261. /**
  262. * Create or update a related record matching the attributes, and fill it with values.
  263. *
  264. * @param array $attributes
  265. * @param array $values
  266. * @return \Illuminate\Database\Eloquent\Model
  267. */
  268. public function updateOrCreate(array $attributes, array $values = [])
  269. {
  270. return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) {
  271. if (! $instance->wasRecentlyCreated) {
  272. $instance->fill($values)->save();
  273. }
  274. });
  275. }
  276. /**
  277. * Add a basic where clause to the query, and return the first result.
  278. *
  279. * @param \Closure|string|array $column
  280. * @param mixed $operator
  281. * @param mixed $value
  282. * @param string $boolean
  283. * @return \Illuminate\Database\Eloquent\Model|static|null
  284. */
  285. public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
  286. {
  287. return $this->where($column, $operator, $value, $boolean)->first();
  288. }
  289. /**
  290. * Execute the query and get the first related model.
  291. *
  292. * @param array $columns
  293. * @return \Illuminate\Database\Eloquent\Model|static|null
  294. */
  295. public function first($columns = ['*'])
  296. {
  297. $results = $this->take(1)->get($columns);
  298. return count($results) > 0 ? $results->first() : null;
  299. }
  300. /**
  301. * Execute the query and get the first result or throw an exception.
  302. *
  303. * @param array $columns
  304. * @return \Illuminate\Database\Eloquent\Model|static
  305. *
  306. * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
  307. */
  308. public function firstOrFail($columns = ['*'])
  309. {
  310. if (! is_null($model = $this->first($columns))) {
  311. return $model;
  312. }
  313. throw (new ModelNotFoundException)->setModel(get_class($this->related));
  314. }
  315. /**
  316. * Execute the query and get the first result or call a callback.
  317. *
  318. * @param \Closure|array $columns
  319. * @param \Closure|null $callback
  320. * @return \Illuminate\Database\Eloquent\Model|static|mixed
  321. */
  322. public function firstOr($columns = ['*'], ?Closure $callback = null)
  323. {
  324. if ($columns instanceof Closure) {
  325. $callback = $columns;
  326. $columns = ['*'];
  327. }
  328. if (! is_null($model = $this->first($columns))) {
  329. return $model;
  330. }
  331. return $callback();
  332. }
  333. /**
  334. * Find a related model by its primary key.
  335. *
  336. * @param mixed $id
  337. * @param array $columns
  338. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
  339. */
  340. public function find($id, $columns = ['*'])
  341. {
  342. if (is_array($id) || $id instanceof Arrayable) {
  343. return $this->findMany($id, $columns);
  344. }
  345. return $this->where(
  346. $this->getRelated()->getQualifiedKeyName(), '=', $id
  347. )->first($columns);
  348. }
  349. /**
  350. * Find multiple related models by their primary keys.
  351. *
  352. * @param \Illuminate\Contracts\Support\Arrayable|array $ids
  353. * @param array $columns
  354. * @return \Illuminate\Database\Eloquent\Collection
  355. */
  356. public function findMany($ids, $columns = ['*'])
  357. {
  358. $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
  359. if (empty($ids)) {
  360. return $this->getRelated()->newCollection();
  361. }
  362. return $this->whereIn(
  363. $this->getRelated()->getQualifiedKeyName(), $ids
  364. )->get($columns);
  365. }
  366. /**
  367. * Find a related model by its primary key or throw an exception.
  368. *
  369. * @param mixed $id
  370. * @param array $columns
  371. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
  372. *
  373. * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
  374. */
  375. public function findOrFail($id, $columns = ['*'])
  376. {
  377. $result = $this->find($id, $columns);
  378. $id = $id instanceof Arrayable ? $id->toArray() : $id;
  379. if (is_array($id)) {
  380. if (count($result) === count(array_unique($id))) {
  381. return $result;
  382. }
  383. } elseif (! is_null($result)) {
  384. return $result;
  385. }
  386. throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
  387. }
  388. /**
  389. * Find a related model by its primary key or call a callback.
  390. *
  391. * @param mixed $id
  392. * @param \Closure|array $columns
  393. * @param \Closure|null $callback
  394. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed
  395. */
  396. public function findOr($id, $columns = ['*'], ?Closure $callback = null)
  397. {
  398. if ($columns instanceof Closure) {
  399. $callback = $columns;
  400. $columns = ['*'];
  401. }
  402. $result = $this->find($id, $columns);
  403. $id = $id instanceof Arrayable ? $id->toArray() : $id;
  404. if (is_array($id)) {
  405. if (count($result) === count(array_unique($id))) {
  406. return $result;
  407. }
  408. } elseif (! is_null($result)) {
  409. return $result;
  410. }
  411. return $callback();
  412. }
  413. /**
  414. * Get the results of the relationship.
  415. *
  416. * @return mixed
  417. */
  418. public function getResults()
  419. {
  420. return ! is_null($this->farParent->{$this->localKey})
  421. ? $this->get()
  422. : $this->related->newCollection();
  423. }
  424. /**
  425. * Execute the query as a "select" statement.
  426. *
  427. * @param array $columns
  428. * @return \Illuminate\Database\Eloquent\Collection
  429. */
  430. public function get($columns = ['*'])
  431. {
  432. $builder = $this->prepareQueryBuilder($columns);
  433. $models = $builder->getModels();
  434. // If we actually found models we will also eager load any relationships that
  435. // have been specified as needing to be eager loaded. This will solve the
  436. // n + 1 query problem for the developer and also increase performance.
  437. if (count($models) > 0) {
  438. $models = $builder->eagerLoadRelations($models);
  439. }
  440. return $this->query->applyAfterQueryCallbacks(
  441. $this->related->newCollection($models)
  442. );
  443. }
  444. /**
  445. * Get a paginator for the "select" statement.
  446. *
  447. * @param int|null $perPage
  448. * @param array $columns
  449. * @param string $pageName
  450. * @param int $page
  451. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
  452. */
  453. public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  454. {
  455. $this->query->addSelect($this->shouldSelect($columns));
  456. return $this->query->paginate($perPage, $columns, $pageName, $page);
  457. }
  458. /**
  459. * Paginate the given query into a simple paginator.
  460. *
  461. * @param int|null $perPage
  462. * @param array $columns
  463. * @param string $pageName
  464. * @param int|null $page
  465. * @return \Illuminate\Contracts\Pagination\Paginator
  466. */
  467. public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  468. {
  469. $this->query->addSelect($this->shouldSelect($columns));
  470. return $this->query->simplePaginate($perPage, $columns, $pageName, $page);
  471. }
  472. /**
  473. * Paginate the given query into a cursor paginator.
  474. *
  475. * @param int|null $perPage
  476. * @param array $columns
  477. * @param string $cursorName
  478. * @param string|null $cursor
  479. * @return \Illuminate\Contracts\Pagination\CursorPaginator
  480. */
  481. public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
  482. {
  483. $this->query->addSelect($this->shouldSelect($columns));
  484. return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor);
  485. }
  486. /**
  487. * Set the select clause for the relation query.
  488. *
  489. * @param array $columns
  490. * @return array
  491. */
  492. protected function shouldSelect(array $columns = ['*'])
  493. {
  494. if ($columns == ['*']) {
  495. $columns = [$this->related->getTable().'.*'];
  496. }
  497. return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
  498. }
  499. /**
  500. * Chunk the results of the query.
  501. *
  502. * @param int $count
  503. * @param callable $callback
  504. * @return bool
  505. */
  506. public function chunk($count, callable $callback)
  507. {
  508. return $this->prepareQueryBuilder()->chunk($count, $callback);
  509. }
  510. /**
  511. * Chunk the results of a query by comparing numeric IDs.
  512. *
  513. * @param int $count
  514. * @param callable $callback
  515. * @param string|null $column
  516. * @param string|null $alias
  517. * @return bool
  518. */
  519. public function chunkById($count, callable $callback, $column = null, $alias = null)
  520. {
  521. $column ??= $this->getRelated()->getQualifiedKeyName();
  522. $alias ??= $this->getRelated()->getKeyName();
  523. return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias);
  524. }
  525. /**
  526. * Chunk the results of a query by comparing IDs in descending order.
  527. *
  528. * @param int $count
  529. * @param callable $callback
  530. * @param string|null $column
  531. * @param string|null $alias
  532. * @return bool
  533. */
  534. public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null)
  535. {
  536. $column ??= $this->getRelated()->getQualifiedKeyName();
  537. $alias ??= $this->getRelated()->getKeyName();
  538. return $this->prepareQueryBuilder()->chunkByIdDesc($count, $callback, $column, $alias);
  539. }
  540. /**
  541. * Execute a callback over each item while chunking by ID.
  542. *
  543. * @param callable $callback
  544. * @param int $count
  545. * @param string|null $column
  546. * @param string|null $alias
  547. * @return bool
  548. */
  549. public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
  550. {
  551. $column = $column ?? $this->getRelated()->getQualifiedKeyName();
  552. $alias = $alias ?? $this->getRelated()->getKeyName();
  553. return $this->prepareQueryBuilder()->eachById($callback, $count, $column, $alias);
  554. }
  555. /**
  556. * Get a generator for the given query.
  557. *
  558. * @return \Illuminate\Support\LazyCollection
  559. */
  560. public function cursor()
  561. {
  562. return $this->prepareQueryBuilder()->cursor();
  563. }
  564. /**
  565. * Execute a callback over each item while chunking.
  566. *
  567. * @param callable $callback
  568. * @param int $count
  569. * @return bool
  570. */
  571. public function each(callable $callback, $count = 1000)
  572. {
  573. return $this->chunk($count, function ($results) use ($callback) {
  574. foreach ($results as $key => $value) {
  575. if ($callback($value, $key) === false) {
  576. return false;
  577. }
  578. }
  579. });
  580. }
  581. /**
  582. * Query lazily, by chunks of the given size.
  583. *
  584. * @param int $chunkSize
  585. * @return \Illuminate\Support\LazyCollection
  586. */
  587. public function lazy($chunkSize = 1000)
  588. {
  589. return $this->prepareQueryBuilder()->lazy($chunkSize);
  590. }
  591. /**
  592. * Query lazily, by chunking the results of a query by comparing IDs.
  593. *
  594. * @param int $chunkSize
  595. * @param string|null $column
  596. * @param string|null $alias
  597. * @return \Illuminate\Support\LazyCollection
  598. */
  599. public function lazyById($chunkSize = 1000, $column = null, $alias = null)
  600. {
  601. $column ??= $this->getRelated()->getQualifiedKeyName();
  602. $alias ??= $this->getRelated()->getKeyName();
  603. return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias);
  604. }
  605. /**
  606. * Query lazily, by chunking the results of a query by comparing IDs in descending order.
  607. *
  608. * @param int $chunkSize
  609. * @param string|null $column
  610. * @param string|null $alias
  611. * @return \Illuminate\Support\LazyCollection
  612. */
  613. public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
  614. {
  615. $column ??= $this->getRelated()->getQualifiedKeyName();
  616. $alias ??= $this->getRelated()->getKeyName();
  617. return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias);
  618. }
  619. /**
  620. * Prepare the query builder for query execution.
  621. *
  622. * @param array $columns
  623. * @return \Illuminate\Database\Eloquent\Builder
  624. */
  625. protected function prepareQueryBuilder($columns = ['*'])
  626. {
  627. $builder = $this->query->applyScopes();
  628. return $builder->addSelect(
  629. $this->shouldSelect($builder->getQuery()->columns ? [] : $columns)
  630. );
  631. }
  632. /**
  633. * Add the constraints for a relationship query.
  634. *
  635. * @param \Illuminate\Database\Eloquent\Builder $query
  636. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  637. * @param array|mixed $columns
  638. * @return \Illuminate\Database\Eloquent\Builder
  639. */
  640. public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
  641. {
  642. if ($parentQuery->getQuery()->from === $query->getQuery()->from) {
  643. return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
  644. }
  645. if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) {
  646. return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns);
  647. }
  648. $this->performJoin($query);
  649. return $query->select($columns)->whereColumn(
  650. $this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName()
  651. );
  652. }
  653. /**
  654. * Add the constraints for a relationship query on the same table.
  655. *
  656. * @param \Illuminate\Database\Eloquent\Builder $query
  657. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  658. * @param array|mixed $columns
  659. * @return \Illuminate\Database\Eloquent\Builder
  660. */
  661. public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
  662. {
  663. $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
  664. $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey);
  665. if ($this->throughParentSoftDeletes()) {
  666. $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
  667. }
  668. $query->getModel()->setTable($hash);
  669. return $query->select($columns)->whereColumn(
  670. $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName()
  671. );
  672. }
  673. /**
  674. * Add the constraints for a relationship query on the same table as the through parent.
  675. *
  676. * @param \Illuminate\Database\Eloquent\Builder $query
  677. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  678. * @param array|mixed $columns
  679. * @return \Illuminate\Database\Eloquent\Builder
  680. */
  681. public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
  682. {
  683. $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash();
  684. $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName());
  685. if ($this->throughParentSoftDeletes()) {
  686. $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn());
  687. }
  688. return $query->select($columns)->whereColumn(
  689. $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey
  690. );
  691. }
  692. /**
  693. * Alias to set the "limit" value of the query.
  694. *
  695. * @param int $value
  696. * @return $this
  697. */
  698. public function take($value)
  699. {
  700. return $this->limit($value);
  701. }
  702. /**
  703. * Set the "limit" value of the query.
  704. *
  705. * @param int $value
  706. * @return $this
  707. */
  708. public function limit($value)
  709. {
  710. if ($this->farParent->exists) {
  711. $this->query->limit($value);
  712. } else {
  713. $column = $this->getQualifiedFirstKeyName();
  714. $grammar = $this->query->getQuery()->getGrammar();
  715. if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) {
  716. $column = 'laravel_through_key';
  717. }
  718. $this->query->groupLimit($value, $column);
  719. }
  720. return $this;
  721. }
  722. /**
  723. * Get the qualified foreign key on the related model.
  724. *
  725. * @return string
  726. */
  727. public function getQualifiedFarKeyName()
  728. {
  729. return $this->getQualifiedForeignKeyName();
  730. }
  731. /**
  732. * Get the foreign key on the "through" model.
  733. *
  734. * @return string
  735. */
  736. public function getFirstKeyName()
  737. {
  738. return $this->firstKey;
  739. }
  740. /**
  741. * Get the qualified foreign key on the "through" model.
  742. *
  743. * @return string
  744. */
  745. public function getQualifiedFirstKeyName()
  746. {
  747. return $this->throughParent->qualifyColumn($this->firstKey);
  748. }
  749. /**
  750. * Get the foreign key on the related model.
  751. *
  752. * @return string
  753. */
  754. public function getForeignKeyName()
  755. {
  756. return $this->secondKey;
  757. }
  758. /**
  759. * Get the qualified foreign key on the related model.
  760. *
  761. * @return string
  762. */
  763. public function getQualifiedForeignKeyName()
  764. {
  765. return $this->related->qualifyColumn($this->secondKey);
  766. }
  767. /**
  768. * Get the local key on the far parent model.
  769. *
  770. * @return string
  771. */
  772. public function getLocalKeyName()
  773. {
  774. return $this->localKey;
  775. }
  776. /**
  777. * Get the qualified local key on the far parent model.
  778. *
  779. * @return string
  780. */
  781. public function getQualifiedLocalKeyName()
  782. {
  783. return $this->farParent->qualifyColumn($this->localKey);
  784. }
  785. /**
  786. * Get the local key on the intermediary model.
  787. *
  788. * @return string
  789. */
  790. public function getSecondLocalKeyName()
  791. {
  792. return $this->secondLocalKey;
  793. }
  794. }