AsPivot.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. namespace Illuminate\Database\Eloquent\Relations\Concerns;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Support\Str;
  5. trait AsPivot
  6. {
  7. /**
  8. * The parent model of the relationship.
  9. *
  10. * @var \Illuminate\Database\Eloquent\Model
  11. */
  12. public $pivotParent;
  13. /**
  14. * The name of the foreign key column.
  15. *
  16. * @var string
  17. */
  18. protected $foreignKey;
  19. /**
  20. * The name of the "other key" column.
  21. *
  22. * @var string
  23. */
  24. protected $relatedKey;
  25. /**
  26. * Create a new pivot model instance.
  27. *
  28. * @param \Illuminate\Database\Eloquent\Model $parent
  29. * @param array $attributes
  30. * @param string $table
  31. * @param bool $exists
  32. * @return static
  33. */
  34. public static function fromAttributes(Model $parent, $attributes, $table, $exists = false)
  35. {
  36. $instance = new static;
  37. $instance->timestamps = $instance->hasTimestampAttributes($attributes);
  38. // The pivot model is a "dynamic" model since we will set the tables dynamically
  39. // for the instance. This allows it work for any intermediate tables for the
  40. // many to many relationship that are defined by this developer's classes.
  41. $instance->setConnection($parent->getConnectionName())
  42. ->setTable($table)
  43. ->forceFill($attributes)
  44. ->syncOriginal();
  45. // We store off the parent instance so we will access the timestamp column names
  46. // for the model, since the pivot model timestamps aren't easily configurable
  47. // from the developer's point of view. We can use the parents to get these.
  48. $instance->pivotParent = $parent;
  49. $instance->exists = $exists;
  50. return $instance;
  51. }
  52. /**
  53. * Create a new pivot model from raw values returned from a query.
  54. *
  55. * @param \Illuminate\Database\Eloquent\Model $parent
  56. * @param array $attributes
  57. * @param string $table
  58. * @param bool $exists
  59. * @return static
  60. */
  61. public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false)
  62. {
  63. $instance = static::fromAttributes($parent, [], $table, $exists);
  64. $instance->timestamps = $instance->hasTimestampAttributes($attributes);
  65. $instance->setRawAttributes(
  66. array_merge($instance->getRawOriginal(), $attributes), $exists
  67. );
  68. return $instance;
  69. }
  70. /**
  71. * Set the keys for a select query.
  72. *
  73. * @param \Illuminate\Database\Eloquent\Builder $query
  74. * @return \Illuminate\Database\Eloquent\Builder
  75. */
  76. protected function setKeysForSelectQuery($query)
  77. {
  78. if (isset($this->attributes[$this->getKeyName()])) {
  79. return parent::setKeysForSelectQuery($query);
  80. }
  81. $query->where($this->foreignKey, $this->getOriginal(
  82. $this->foreignKey, $this->getAttribute($this->foreignKey)
  83. ));
  84. return $query->where($this->relatedKey, $this->getOriginal(
  85. $this->relatedKey, $this->getAttribute($this->relatedKey)
  86. ));
  87. }
  88. /**
  89. * Set the keys for a save update query.
  90. *
  91. * @param \Illuminate\Database\Eloquent\Builder $query
  92. * @return \Illuminate\Database\Eloquent\Builder
  93. */
  94. protected function setKeysForSaveQuery($query)
  95. {
  96. return $this->setKeysForSelectQuery($query);
  97. }
  98. /**
  99. * Delete the pivot model record from the database.
  100. *
  101. * @return int
  102. */
  103. public function delete()
  104. {
  105. if (isset($this->attributes[$this->getKeyName()])) {
  106. return (int) parent::delete();
  107. }
  108. if ($this->fireModelEvent('deleting') === false) {
  109. return 0;
  110. }
  111. $this->touchOwners();
  112. return tap($this->getDeleteQuery()->delete(), function () {
  113. $this->exists = false;
  114. $this->fireModelEvent('deleted', false);
  115. });
  116. }
  117. /**
  118. * Get the query builder for a delete operation on the pivot.
  119. *
  120. * @return \Illuminate\Database\Eloquent\Builder
  121. */
  122. protected function getDeleteQuery()
  123. {
  124. return $this->newQueryWithoutRelationships()->where([
  125. $this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)),
  126. $this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)),
  127. ]);
  128. }
  129. /**
  130. * Get the table associated with the model.
  131. *
  132. * @return string
  133. */
  134. public function getTable()
  135. {
  136. if (! isset($this->table)) {
  137. $this->setTable(str_replace(
  138. '\\', '', Str::snake(Str::singular(class_basename($this)))
  139. ));
  140. }
  141. return $this->table;
  142. }
  143. /**
  144. * Get the foreign key column name.
  145. *
  146. * @return string
  147. */
  148. public function getForeignKey()
  149. {
  150. return $this->foreignKey;
  151. }
  152. /**
  153. * Get the "related key" column name.
  154. *
  155. * @return string
  156. */
  157. public function getRelatedKey()
  158. {
  159. return $this->relatedKey;
  160. }
  161. /**
  162. * Get the "related key" column name.
  163. *
  164. * @return string
  165. */
  166. public function getOtherKey()
  167. {
  168. return $this->getRelatedKey();
  169. }
  170. /**
  171. * Set the key names for the pivot model instance.
  172. *
  173. * @param string $foreignKey
  174. * @param string $relatedKey
  175. * @return $this
  176. */
  177. public function setPivotKeys($foreignKey, $relatedKey)
  178. {
  179. $this->foreignKey = $foreignKey;
  180. $this->relatedKey = $relatedKey;
  181. return $this;
  182. }
  183. /**
  184. * Determine if the pivot model or given attributes has timestamp attributes.
  185. *
  186. * @param array|null $attributes
  187. * @return bool
  188. */
  189. public function hasTimestampAttributes($attributes = null)
  190. {
  191. return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes);
  192. }
  193. /**
  194. * Get the name of the "created at" column.
  195. *
  196. * @return string
  197. */
  198. public function getCreatedAtColumn()
  199. {
  200. return $this->pivotParent
  201. ? $this->pivotParent->getCreatedAtColumn()
  202. : parent::getCreatedAtColumn();
  203. }
  204. /**
  205. * Get the name of the "updated at" column.
  206. *
  207. * @return string
  208. */
  209. public function getUpdatedAtColumn()
  210. {
  211. return $this->pivotParent
  212. ? $this->pivotParent->getUpdatedAtColumn()
  213. : parent::getUpdatedAtColumn();
  214. }
  215. /**
  216. * Get the queueable identity for the entity.
  217. *
  218. * @return mixed
  219. */
  220. public function getQueueableId()
  221. {
  222. if (isset($this->attributes[$this->getKeyName()])) {
  223. return $this->getKey();
  224. }
  225. return sprintf(
  226. '%s:%s:%s:%s',
  227. $this->foreignKey, $this->getAttribute($this->foreignKey),
  228. $this->relatedKey, $this->getAttribute($this->relatedKey)
  229. );
  230. }
  231. /**
  232. * Get a new query to restore one or more models by their queueable IDs.
  233. *
  234. * @param int[]|string[]|string $ids
  235. * @return \Illuminate\Database\Eloquent\Builder
  236. */
  237. public function newQueryForRestoration($ids)
  238. {
  239. if (is_array($ids)) {
  240. return $this->newQueryForCollectionRestoration($ids);
  241. }
  242. if (! str_contains($ids, ':')) {
  243. return parent::newQueryForRestoration($ids);
  244. }
  245. $segments = explode(':', $ids);
  246. return $this->newQueryWithoutScopes()
  247. ->where($segments[0], $segments[1])
  248. ->where($segments[2], $segments[3]);
  249. }
  250. /**
  251. * Get a new query to restore multiple models by their queueable IDs.
  252. *
  253. * @param int[]|string[] $ids
  254. * @return \Illuminate\Database\Eloquent\Builder
  255. */
  256. protected function newQueryForCollectionRestoration(array $ids)
  257. {
  258. $ids = array_values($ids);
  259. if (! str_contains($ids[0], ':')) {
  260. return parent::newQueryForRestoration($ids);
  261. }
  262. $query = $this->newQueryWithoutScopes();
  263. foreach ($ids as $id) {
  264. $segments = explode(':', $id);
  265. $query->orWhere(function ($query) use ($segments) {
  266. return $query->where($segments[0], $segments[1])
  267. ->where($segments[2], $segments[3]);
  268. });
  269. }
  270. return $query;
  271. }
  272. /**
  273. * Unset all the loaded relations for the instance.
  274. *
  275. * @return $this
  276. */
  277. public function unsetRelations()
  278. {
  279. $this->pivotParent = null;
  280. $this->relations = [];
  281. return $this;
  282. }
  283. }