BelongsToMany.php 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. <?php
  2. namespace Illuminate\Database\Eloquent\Relations;
  3. use Illuminate\Support\Str;
  4. use InvalidArgumentException;
  5. use Illuminate\Database\Eloquent\Model;
  6. use Illuminate\Database\Eloquent\Builder;
  7. use Illuminate\Database\Eloquent\Collection;
  8. use Illuminate\Database\Eloquent\ModelNotFoundException;
  9. class BelongsToMany extends Relation
  10. {
  11. use Concerns\InteractsWithPivotTable;
  12. /**
  13. * The intermediate table for the relation.
  14. *
  15. * @var string
  16. */
  17. protected $table;
  18. /**
  19. * The foreign key of the parent model.
  20. *
  21. * @var string
  22. */
  23. protected $foreignPivotKey;
  24. /**
  25. * The associated key of the relation.
  26. *
  27. * @var string
  28. */
  29. protected $relatedPivotKey;
  30. /**
  31. * The key name of the parent model.
  32. *
  33. * @var string
  34. */
  35. protected $parentKey;
  36. /**
  37. * The key name of the related model.
  38. *
  39. * @var string
  40. */
  41. protected $relatedKey;
  42. /**
  43. * The "name" of the relationship.
  44. *
  45. * @var string
  46. */
  47. protected $relationName;
  48. /**
  49. * The pivot table columns to retrieve.
  50. *
  51. * @var array
  52. */
  53. protected $pivotColumns = [];
  54. /**
  55. * Any pivot table restrictions for where clauses.
  56. *
  57. * @var array
  58. */
  59. protected $pivotWheres = [];
  60. /**
  61. * Any pivot table restrictions for whereIn clauses.
  62. *
  63. * @var array
  64. */
  65. protected $pivotWhereIns = [];
  66. /**
  67. * The default values for the pivot columns.
  68. *
  69. * @var array
  70. */
  71. protected $pivotValues = [];
  72. /**
  73. * Indicates if timestamps are available on the pivot table.
  74. *
  75. * @var bool
  76. */
  77. public $withTimestamps = false;
  78. /**
  79. * The custom pivot table column for the created_at timestamp.
  80. *
  81. * @var string
  82. */
  83. protected $pivotCreatedAt;
  84. /**
  85. * The custom pivot table column for the updated_at timestamp.
  86. *
  87. * @var string
  88. */
  89. protected $pivotUpdatedAt;
  90. /**
  91. * The class name of the custom pivot model to use for the relationship.
  92. *
  93. * @var string
  94. */
  95. protected $using;
  96. /**
  97. * The name of the accessor to use for the "pivot" relationship.
  98. *
  99. * @var string
  100. */
  101. protected $accessor = 'pivot';
  102. /**
  103. * The count of self joins.
  104. *
  105. * @var int
  106. */
  107. protected static $selfJoinCount = 0;
  108. /**
  109. * Create a new belongs to many relationship instance.
  110. *
  111. * @param \Illuminate\Database\Eloquent\Builder $query
  112. * @param \Illuminate\Database\Eloquent\Model $parent
  113. * @param string $table
  114. * @param string $foreignPivotKey
  115. * @param string $relatedPivotKey
  116. * @param string $parentKey
  117. * @param string $relatedKey
  118. * @param string $relationName
  119. * @return void
  120. */
  121. public function __construct(Builder $query, Model $parent, $table, $foreignPivotKey,
  122. $relatedPivotKey, $parentKey, $relatedKey, $relationName = null)
  123. {
  124. $this->table = $table;
  125. $this->parentKey = $parentKey;
  126. $this->relatedKey = $relatedKey;
  127. $this->relationName = $relationName;
  128. $this->relatedPivotKey = $relatedPivotKey;
  129. $this->foreignPivotKey = $foreignPivotKey;
  130. parent::__construct($query, $parent);
  131. }
  132. /**
  133. * Set the base constraints on the relation query.
  134. *
  135. * @return void
  136. */
  137. public function addConstraints()
  138. {
  139. $this->performJoin();
  140. if (static::$constraints) {
  141. $this->addWhereConstraints();
  142. }
  143. }
  144. /**
  145. * Set the join clause for the relation query.
  146. *
  147. * @param \Illuminate\Database\Eloquent\Builder|null $query
  148. * @return $this
  149. */
  150. protected function performJoin($query = null)
  151. {
  152. $query = $query ?: $this->query;
  153. // We need to join to the intermediate table on the related model's primary
  154. // key column with the intermediate table's foreign key for the related
  155. // model instance. Then we can set the "where" for the parent models.
  156. $baseTable = $this->related->getTable();
  157. $key = $baseTable.'.'.$this->relatedKey;
  158. $query->join($this->table, $key, '=', $this->getQualifiedRelatedPivotKeyName());
  159. return $this;
  160. }
  161. /**
  162. * Set the where clause for the relation query.
  163. *
  164. * @return $this
  165. */
  166. protected function addWhereConstraints()
  167. {
  168. $this->query->where(
  169. $this->getQualifiedForeignPivotKeyName(), '=', $this->parent->{$this->parentKey}
  170. );
  171. return $this;
  172. }
  173. /**
  174. * Set the constraints for an eager load of the relation.
  175. *
  176. * @param array $models
  177. * @return void
  178. */
  179. public function addEagerConstraints(array $models)
  180. {
  181. $this->query->whereIn($this->getQualifiedForeignPivotKeyName(), $this->getKeys($models, $this->parentKey));
  182. }
  183. /**
  184. * Initialize the relation on a set of models.
  185. *
  186. * @param array $models
  187. * @param string $relation
  188. * @return array
  189. */
  190. public function initRelation(array $models, $relation)
  191. {
  192. foreach ($models as $model) {
  193. $model->setRelation($relation, $this->related->newCollection());
  194. }
  195. return $models;
  196. }
  197. /**
  198. * Match the eagerly loaded results to their parents.
  199. *
  200. * @param array $models
  201. * @param \Illuminate\Database\Eloquent\Collection $results
  202. * @param string $relation
  203. * @return array
  204. */
  205. public function match(array $models, Collection $results, $relation)
  206. {
  207. $dictionary = $this->buildDictionary($results);
  208. // Once we have an array dictionary of child objects we can easily match the
  209. // children back to their parent using the dictionary and the keys on the
  210. // the parent models. Then we will return the hydrated models back out.
  211. foreach ($models as $model) {
  212. if (isset($dictionary[$key = $model->{$this->parentKey}])) {
  213. $model->setRelation(
  214. $relation, $this->related->newCollection($dictionary[$key])
  215. );
  216. }
  217. }
  218. return $models;
  219. }
  220. /**
  221. * Build model dictionary keyed by the relation's foreign key.
  222. *
  223. * @param \Illuminate\Database\Eloquent\Collection $results
  224. * @return array
  225. */
  226. protected function buildDictionary(Collection $results)
  227. {
  228. // First we will build a dictionary of child models keyed by the foreign key
  229. // of the relation so that we will easily and quickly match them to their
  230. // parents without having a possibly slow inner loops for every models.
  231. $dictionary = [];
  232. foreach ($results as $result) {
  233. $dictionary[$result->{$this->accessor}->{$this->foreignPivotKey}][] = $result;
  234. }
  235. return $dictionary;
  236. }
  237. /**
  238. * Get the class being used for pivot models.
  239. *
  240. * @return string
  241. */
  242. public function getPivotClass()
  243. {
  244. return $this->using ?? Pivot::class;
  245. }
  246. /**
  247. * Specify the custom pivot model to use for the relationship.
  248. *
  249. * @param string $class
  250. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  251. */
  252. public function using($class)
  253. {
  254. $this->using = $class;
  255. return $this;
  256. }
  257. /**
  258. * Specify the custom pivot accessor to use for the relationship.
  259. *
  260. * @param string $accessor
  261. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  262. */
  263. public function as($accessor)
  264. {
  265. $this->accessor = $accessor;
  266. return $this;
  267. }
  268. /**
  269. * Set a where clause for a pivot table column.
  270. *
  271. * @param string $column
  272. * @param string $operator
  273. * @param mixed $value
  274. * @param string $boolean
  275. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  276. */
  277. public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
  278. {
  279. $this->pivotWheres[] = func_get_args();
  280. return $this->where($this->table.'.'.$column, $operator, $value, $boolean);
  281. }
  282. /**
  283. * Set a "where in" clause for a pivot table column.
  284. *
  285. * @param string $column
  286. * @param mixed $values
  287. * @param string $boolean
  288. * @param bool $not
  289. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  290. */
  291. public function wherePivotIn($column, $values, $boolean = 'and', $not = false)
  292. {
  293. $this->pivotWhereIns[] = func_get_args();
  294. return $this->whereIn($this->table.'.'.$column, $values, $boolean, $not);
  295. }
  296. /**
  297. * Set an "or where" clause for a pivot table column.
  298. *
  299. * @param string $column
  300. * @param string $operator
  301. * @param mixed $value
  302. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  303. */
  304. public function orWherePivot($column, $operator = null, $value = null)
  305. {
  306. return $this->wherePivot($column, $operator, $value, 'or');
  307. }
  308. /**
  309. * Set a where clause for a pivot table column.
  310. *
  311. * In addition, new pivot records will receive this value.
  312. *
  313. * @param string $column
  314. * @param mixed $value
  315. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  316. */
  317. public function withPivotValue($column, $value = null)
  318. {
  319. if (is_array($column)) {
  320. foreach ($column as $name => $value) {
  321. $this->withPivotValue($name, $value);
  322. }
  323. return $this;
  324. }
  325. if (is_null($value)) {
  326. throw new InvalidArgumentException('The provided value may not be null.');
  327. }
  328. $this->pivotValues[] = compact('column', 'value');
  329. return $this->wherePivot($column, '=', $value);
  330. }
  331. /**
  332. * Set an "or where in" clause for a pivot table column.
  333. *
  334. * @param string $column
  335. * @param mixed $values
  336. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  337. */
  338. public function orWherePivotIn($column, $values)
  339. {
  340. return $this->wherePivotIn($column, $values, 'or');
  341. }
  342. /**
  343. * Find a related model by its primary key or return new instance of the related model.
  344. *
  345. * @param mixed $id
  346. * @param array $columns
  347. * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
  348. */
  349. public function findOrNew($id, $columns = ['*'])
  350. {
  351. if (is_null($instance = $this->find($id, $columns))) {
  352. $instance = $this->related->newInstance();
  353. }
  354. return $instance;
  355. }
  356. /**
  357. * Get the first related model record matching the attributes or instantiate it.
  358. *
  359. * @param array $attributes
  360. * @return \Illuminate\Database\Eloquent\Model
  361. */
  362. public function firstOrNew(array $attributes)
  363. {
  364. if (is_null($instance = $this->where($attributes)->first())) {
  365. $instance = $this->related->newInstance($attributes);
  366. }
  367. return $instance;
  368. }
  369. /**
  370. * Get the first related record matching the attributes or create it.
  371. *
  372. * @param array $attributes
  373. * @param array $joining
  374. * @param bool $touch
  375. * @return \Illuminate\Database\Eloquent\Model
  376. */
  377. public function firstOrCreate(array $attributes, array $joining = [], $touch = true)
  378. {
  379. if (is_null($instance = $this->where($attributes)->first())) {
  380. $instance = $this->create($attributes, $joining, $touch);
  381. }
  382. return $instance;
  383. }
  384. /**
  385. * Create or update a related record matching the attributes, and fill it with values.
  386. *
  387. * @param array $attributes
  388. * @param array $values
  389. * @param array $joining
  390. * @param bool $touch
  391. * @return \Illuminate\Database\Eloquent\Model
  392. */
  393. public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
  394. {
  395. if (is_null($instance = $this->where($attributes)->first())) {
  396. return $this->create($values, $joining, $touch);
  397. }
  398. $instance->fill($values);
  399. $instance->save(['touch' => false]);
  400. return $instance;
  401. }
  402. /**
  403. * Find a related model by its primary key.
  404. *
  405. * @param mixed $id
  406. * @param array $columns
  407. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
  408. */
  409. public function find($id, $columns = ['*'])
  410. {
  411. return is_array($id) ? $this->findMany($id, $columns) : $this->where(
  412. $this->getRelated()->getQualifiedKeyName(), '=', $id
  413. )->first($columns);
  414. }
  415. /**
  416. * Find multiple related models by their primary keys.
  417. *
  418. * @param mixed $ids
  419. * @param array $columns
  420. * @return \Illuminate\Database\Eloquent\Collection
  421. */
  422. public function findMany($ids, $columns = ['*'])
  423. {
  424. return empty($ids) ? $this->getRelated()->newCollection() : $this->whereIn(
  425. $this->getRelated()->getQualifiedKeyName(), $ids
  426. )->get($columns);
  427. }
  428. /**
  429. * Find a related model by its primary key or throw an exception.
  430. *
  431. * @param mixed $id
  432. * @param array $columns
  433. * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
  434. *
  435. * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
  436. */
  437. public function findOrFail($id, $columns = ['*'])
  438. {
  439. $result = $this->find($id, $columns);
  440. if (is_array($id)) {
  441. if (count($result) === count(array_unique($id))) {
  442. return $result;
  443. }
  444. } elseif (! is_null($result)) {
  445. return $result;
  446. }
  447. throw (new ModelNotFoundException)->setModel(get_class($this->related));
  448. }
  449. /**
  450. * Execute the query and get the first result.
  451. *
  452. * @param array $columns
  453. * @return mixed
  454. */
  455. public function first($columns = ['*'])
  456. {
  457. $results = $this->take(1)->get($columns);
  458. return count($results) > 0 ? $results->first() : null;
  459. }
  460. /**
  461. * Execute the query and get the first result or throw an exception.
  462. *
  463. * @param array $columns
  464. * @return \Illuminate\Database\Eloquent\Model|static
  465. *
  466. * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
  467. */
  468. public function firstOrFail($columns = ['*'])
  469. {
  470. if (! is_null($model = $this->first($columns))) {
  471. return $model;
  472. }
  473. throw (new ModelNotFoundException)->setModel(get_class($this->related));
  474. }
  475. /**
  476. * Get the results of the relationship.
  477. *
  478. * @return mixed
  479. */
  480. public function getResults()
  481. {
  482. return $this->get();
  483. }
  484. /**
  485. * Execute the query as a "select" statement.
  486. *
  487. * @param array $columns
  488. * @return \Illuminate\Database\Eloquent\Collection
  489. */
  490. public function get($columns = ['*'])
  491. {
  492. // First we'll add the proper select columns onto the query so it is run with
  493. // the proper columns. Then, we will get the results and hydrate out pivot
  494. // models with the result of those columns as a separate model relation.
  495. $columns = $this->query->getQuery()->columns ? [] : $columns;
  496. $builder = $this->query->applyScopes();
  497. $models = $builder->addSelect(
  498. $this->shouldSelect($columns)
  499. )->getModels();
  500. $this->hydratePivotRelation($models);
  501. // If we actually found models we will also eager load any relationships that
  502. // have been specified as needing to be eager loaded. This will solve the
  503. // n + 1 query problem for the developer and also increase performance.
  504. if (count($models) > 0) {
  505. $models = $builder->eagerLoadRelations($models);
  506. }
  507. return $this->related->newCollection($models);
  508. }
  509. /**
  510. * Get the select columns for the relation query.
  511. *
  512. * @param array $columns
  513. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  514. */
  515. protected function shouldSelect(array $columns = ['*'])
  516. {
  517. if ($columns == ['*']) {
  518. $columns = [$this->related->getTable().'.*'];
  519. }
  520. return array_merge($columns, $this->aliasedPivotColumns());
  521. }
  522. /**
  523. * Get the pivot columns for the relation.
  524. *
  525. * "pivot_" is prefixed ot each column for easy removal later.
  526. *
  527. * @return array
  528. */
  529. protected function aliasedPivotColumns()
  530. {
  531. $defaults = [$this->foreignPivotKey, $this->relatedPivotKey];
  532. return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
  533. return $this->table.'.'.$column.' as pivot_'.$column;
  534. })->unique()->all();
  535. }
  536. /**
  537. * Get a paginator for the "select" statement.
  538. *
  539. * @param int $perPage
  540. * @param array $columns
  541. * @param string $pageName
  542. * @param int|null $page
  543. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
  544. */
  545. public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  546. {
  547. $this->query->addSelect($this->shouldSelect($columns));
  548. return tap($this->query->paginate($perPage, $columns, $pageName, $page), function ($paginator) {
  549. $this->hydratePivotRelation($paginator->items());
  550. });
  551. }
  552. /**
  553. * Paginate the given query into a simple paginator.
  554. *
  555. * @param int $perPage
  556. * @param array $columns
  557. * @param string $pageName
  558. * @param int|null $page
  559. * @return \Illuminate\Contracts\Pagination\Paginator
  560. */
  561. public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
  562. {
  563. $this->query->addSelect($this->shouldSelect($columns));
  564. return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function ($paginator) {
  565. $this->hydratePivotRelation($paginator->items());
  566. });
  567. }
  568. /**
  569. * Chunk the results of the query.
  570. *
  571. * @param int $count
  572. * @param callable $callback
  573. * @return bool
  574. */
  575. public function chunk($count, callable $callback)
  576. {
  577. $this->query->addSelect($this->shouldSelect());
  578. return $this->query->chunk($count, function ($results) use ($callback) {
  579. $this->hydratePivotRelation($results->all());
  580. return $callback($results);
  581. });
  582. }
  583. /**
  584. * Hydrate the pivot table relationship on the models.
  585. *
  586. * @param array $models
  587. * @return void
  588. */
  589. protected function hydratePivotRelation(array $models)
  590. {
  591. // To hydrate the pivot relationship, we will just gather the pivot attributes
  592. // and create a new Pivot model, which is basically a dynamic model that we
  593. // will set the attributes, table, and connections on it so it will work.
  594. foreach ($models as $model) {
  595. $model->setRelation($this->accessor, $this->newExistingPivot(
  596. $this->migratePivotAttributes($model)
  597. ));
  598. }
  599. }
  600. /**
  601. * Get the pivot attributes from a model.
  602. *
  603. * @param \Illuminate\Database\Eloquent\Model $model
  604. * @return array
  605. */
  606. protected function migratePivotAttributes(Model $model)
  607. {
  608. $values = [];
  609. foreach ($model->getAttributes() as $key => $value) {
  610. // To get the pivots attributes we will just take any of the attributes which
  611. // begin with "pivot_" and add those to this arrays, as well as unsetting
  612. // them from the parent's models since they exist in a different table.
  613. if (strpos($key, 'pivot_') === 0) {
  614. $values[substr($key, 6)] = $value;
  615. unset($model->$key);
  616. }
  617. }
  618. return $values;
  619. }
  620. /**
  621. * If we're touching the parent model, touch.
  622. *
  623. * @return void
  624. */
  625. public function touchIfTouching()
  626. {
  627. if ($this->touchingParent()) {
  628. $this->getParent()->touch();
  629. }
  630. if ($this->getParent()->touches($this->relationName)) {
  631. $this->touch();
  632. }
  633. }
  634. /**
  635. * Determine if we should touch the parent on sync.
  636. *
  637. * @return bool
  638. */
  639. protected function touchingParent()
  640. {
  641. return $this->getRelated()->touches($this->guessInverseRelation());
  642. }
  643. /**
  644. * Attempt to guess the name of the inverse of the relation.
  645. *
  646. * @return string
  647. */
  648. protected function guessInverseRelation()
  649. {
  650. return Str::camel(Str::plural(class_basename($this->getParent())));
  651. }
  652. /**
  653. * Touch all of the related models for the relationship.
  654. *
  655. * E.g.: Touch all roles associated with this user.
  656. *
  657. * @return void
  658. */
  659. public function touch()
  660. {
  661. $key = $this->getRelated()->getKeyName();
  662. $columns = [
  663. $this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(),
  664. ];
  665. // If we actually have IDs for the relation, we will run the query to update all
  666. // the related model's timestamps, to make sure these all reflect the changes
  667. // to the parent models. This will help us keep any caching synced up here.
  668. if (count($ids = $this->allRelatedIds()) > 0) {
  669. $this->getRelated()->newQuery()->whereIn($key, $ids)->update($columns);
  670. }
  671. }
  672. /**
  673. * Get all of the IDs for the related models.
  674. *
  675. * @return \Illuminate\Support\Collection
  676. */
  677. public function allRelatedIds()
  678. {
  679. return $this->newPivotQuery()->pluck($this->relatedPivotKey);
  680. }
  681. /**
  682. * Save a new model and attach it to the parent model.
  683. *
  684. * @param \Illuminate\Database\Eloquent\Model $model
  685. * @param array $pivotAttributes
  686. * @param bool $touch
  687. * @return \Illuminate\Database\Eloquent\Model
  688. */
  689. public function save(Model $model, array $pivotAttributes = [], $touch = true)
  690. {
  691. $model->save(['touch' => false]);
  692. $this->attach($model->getKey(), $pivotAttributes, $touch);
  693. return $model;
  694. }
  695. /**
  696. * Save an array of new models and attach them to the parent model.
  697. *
  698. * @param \Illuminate\Support\Collection|array $models
  699. * @param array $pivotAttributes
  700. * @return array
  701. */
  702. public function saveMany($models, array $pivotAttributes = [])
  703. {
  704. foreach ($models as $key => $model) {
  705. $this->save($model, (array) ($pivotAttributes[$key] ?? []), false);
  706. }
  707. $this->touchIfTouching();
  708. return $models;
  709. }
  710. /**
  711. * Create a new instance of the related model.
  712. *
  713. * @param array $attributes
  714. * @param array $joining
  715. * @param bool $touch
  716. * @return \Illuminate\Database\Eloquent\Model
  717. */
  718. public function create(array $attributes = [], array $joining = [], $touch = true)
  719. {
  720. $instance = $this->related->newInstance($attributes);
  721. // Once we save the related model, we need to attach it to the base model via
  722. // through intermediate table so we'll use the existing "attach" method to
  723. // accomplish this which will insert the record and any more attributes.
  724. $instance->save(['touch' => false]);
  725. $this->attach($instance->getKey(), $joining, $touch);
  726. return $instance;
  727. }
  728. /**
  729. * Create an array of new instances of the related models.
  730. *
  731. * @param array $records
  732. * @param array $joinings
  733. * @return array
  734. */
  735. public function createMany(array $records, array $joinings = [])
  736. {
  737. $instances = [];
  738. foreach ($records as $key => $record) {
  739. $instances[] = $this->create($record, (array) ($joinings[$key] ?? []), false);
  740. }
  741. $this->touchIfTouching();
  742. return $instances;
  743. }
  744. /**
  745. * Add the constraints for a relationship query.
  746. *
  747. * @param \Illuminate\Database\Eloquent\Builder $query
  748. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  749. * @param array|mixed $columns
  750. * @return \Illuminate\Database\Eloquent\Builder
  751. */
  752. public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
  753. {
  754. if ($parentQuery->getQuery()->from == $query->getQuery()->from) {
  755. return $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns);
  756. }
  757. $this->performJoin($query);
  758. return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
  759. }
  760. /**
  761. * Add the constraints for a relationship query on the same table.
  762. *
  763. * @param \Illuminate\Database\Eloquent\Builder $query
  764. * @param \Illuminate\Database\Eloquent\Builder $parentQuery
  765. * @param array|mixed $columns
  766. * @return \Illuminate\Database\Eloquent\Builder
  767. */
  768. public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*'])
  769. {
  770. $query->select($columns);
  771. $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash());
  772. $this->related->setTable($hash);
  773. $this->performJoin($query);
  774. return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
  775. }
  776. /**
  777. * Get the key for comparing against the parent key in "has" query.
  778. *
  779. * @return string
  780. */
  781. public function getExistenceCompareKey()
  782. {
  783. return $this->getQualifiedForeignPivotKeyName();
  784. }
  785. /**
  786. * Get a relationship join table hash.
  787. *
  788. * @return string
  789. */
  790. public function getRelationCountHash()
  791. {
  792. return 'laravel_reserved_'.static::$selfJoinCount++;
  793. }
  794. /**
  795. * Specify that the pivot table has creation and update timestamps.
  796. *
  797. * @param mixed $createdAt
  798. * @param mixed $updatedAt
  799. * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
  800. */
  801. public function withTimestamps($createdAt = null, $updatedAt = null)
  802. {
  803. $this->withTimestamps = true;
  804. $this->pivotCreatedAt = $createdAt;
  805. $this->pivotUpdatedAt = $updatedAt;
  806. return $this->withPivot($this->createdAt(), $this->updatedAt());
  807. }
  808. /**
  809. * Get the name of the "created at" column.
  810. *
  811. * @return string
  812. */
  813. public function createdAt()
  814. {
  815. return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn();
  816. }
  817. /**
  818. * Get the name of the "updated at" column.
  819. *
  820. * @return string
  821. */
  822. public function updatedAt()
  823. {
  824. return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn();
  825. }
  826. /**
  827. * Get the foreign key for the relation.
  828. *
  829. * @return string
  830. */
  831. public function getForeignPivotKeyName()
  832. {
  833. return $this->foreignPivotKey;
  834. }
  835. /**
  836. * Get the fully qualified foreign key for the relation.
  837. *
  838. * @return string
  839. */
  840. public function getQualifiedForeignPivotKeyName()
  841. {
  842. return $this->table.'.'.$this->foreignPivotKey;
  843. }
  844. /**
  845. * Get the "related key" for the relation.
  846. *
  847. * @return string
  848. */
  849. public function getRelatedPivotKeyName()
  850. {
  851. return $this->relatedPivotKey;
  852. }
  853. /**
  854. * Get the fully qualified "related key" for the relation.
  855. *
  856. * @return string
  857. */
  858. public function getQualifiedRelatedPivotKeyName()
  859. {
  860. return $this->table.'.'.$this->relatedPivotKey;
  861. }
  862. /**
  863. * Get the fully qualified parent key name for the relation.
  864. *
  865. * @return string
  866. */
  867. public function getQualifiedParentKeyName()
  868. {
  869. return $this->parent->qualifyColumn($this->parentKey);
  870. }
  871. /**
  872. * Get the intermediate table for the relationship.
  873. *
  874. * @return string
  875. */
  876. public function getTable()
  877. {
  878. return $this->table;
  879. }
  880. /**
  881. * Get the relationship name for the relationship.
  882. *
  883. * @return string
  884. */
  885. public function getRelationName()
  886. {
  887. return $this->relationName;
  888. }
  889. /**
  890. * Get the name of the pivot accessor for this relationship.
  891. *
  892. * @return string
  893. */
  894. public function getPivotAccessor()
  895. {
  896. return $this->accessor;
  897. }
  898. }