/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
PHP | 1297 lines | 530 code | 202 blank | 565 comment | 41 complexity | b2fdae119b27d56a098db1302d339dcc MD5 | raw file
- <?php
- namespace Illuminate\Database\Eloquent\Relations;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Str;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Database\Eloquent\Builder;
- use Illuminate\Database\Eloquent\Collection;
- use Illuminate\Database\Eloquent\ModelNotFoundException;
- class BelongsToMany extends Relation
- {
- /**
- * The intermediate table for the relation.
- *
- * @var string
- */
- protected $table;
- /**
- * The foreign key of the parent model.
- *
- * @var string
- */
- protected $foreignKey;
- /**
- * The associated key of the relation.
- *
- * @var string
- */
- protected $otherKey;
- /**
- * The "name" of the relationship.
- *
- * @var string
- */
- protected $relationName;
- /**
- * The pivot table columns to retrieve.
- *
- * @var array
- */
- protected $pivotColumns = [];
- /**
- * Any pivot table restrictions.
- *
- * @var array
- */
- protected $pivotWheres = [];
- /**
- * The custom pivot table column for the created_at timestamp.
- *
- * @var string
- */
- protected $pivotCreatedAt;
- /**
- * The custom pivot table column for the updated_at timestamp.
- *
- * @var string
- */
- protected $pivotUpdatedAt;
- /**
- * The count of self joins.
- *
- * @var int
- */
- protected static $selfJoinCount = 0;
- /**
- * Create a new belongs to many relationship instance.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $table
- * @param string $foreignKey
- * @param string $otherKey
- * @param string $relationName
- * @return void
- */
- public function __construct(Builder $query, Model $parent, $table, $foreignKey, $otherKey, $relationName = null)
- {
- $this->table = $table;
- $this->otherKey = $otherKey;
- $this->foreignKey = $foreignKey;
- $this->relationName = $relationName;
- parent::__construct($query, $parent);
- }
- /**
- * Get the results of the relationship.
- *
- * @return mixed
- */
- public function getResults()
- {
- return $this->get();
- }
- /**
- * Set a where clause for a pivot table column.
- *
- * @param string $column
- * @param string $operator
- * @param mixed $value
- * @param string $boolean
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
- {
- $this->pivotWheres[] = func_get_args();
- return $this->where($this->table.'.'.$column, $operator, $value, $boolean);
- }
- /**
- * Set a "where in" clause for a pivot table column.
- *
- * @param string $column
- * @param mixed $values
- * @param string $boolean
- * @param bool $not
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function wherePivotIn($column, $values, $boolean = 'and', $not = false)
- {
- $this->pivotWheres[] = func_get_args();
- return $this->whereIn($this->table.'.'.$column, $values, $boolean, $not);
- }
- /**
- * Set an "or where" clause for a pivot table column.
- *
- * @param string $column
- * @param string $operator
- * @param mixed $value
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function orWherePivot($column, $operator = null, $value = null)
- {
- return $this->wherePivot($column, $operator, $value, 'or');
- }
- /**
- * Set an "or where in" clause for a pivot table column.
- *
- * @param string $column
- * @param mixed $values
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function orWherePivotIn($column, $values)
- {
- return $this->wherePivotIn($column, $values, 'or');
- }
- /**
- * Execute the query and get the first result.
- *
- * @param array $columns
- * @return mixed
- */
- public function first($columns = ['*'])
- {
- $results = $this->take(1)->get($columns);
- return count($results) > 0 ? $results->first() : null;
- }
- /**
- * Execute the query and get the first result or throw an exception.
- *
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Model|static
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function firstOrFail($columns = ['*'])
- {
- if (! is_null($model = $this->first($columns))) {
- return $model;
- }
- throw (new ModelNotFoundException)->setModel(get_class($this->parent));
- }
- /**
- * Execute the query as a "select" statement.
- *
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Collection
- */
- public function get($columns = ['*'])
- {
- // First we'll add the proper select columns onto the query so it is run with
- // the proper columns. Then, we will get the results and hydrate out pivot
- // models with the result of those columns as a separate model relation.
- $columns = $this->query->getQuery()->columns ? [] : $columns;
- $select = $this->getSelectColumns($columns);
- $builder = $this->query->applyScopes();
- $models = $builder->addSelect($select)->getModels();
- $this->hydratePivotRelation($models);
- // If we actually found models we will also eager load any relationships that
- // have been specified as needing to be eager loaded. This will solve the
- // n + 1 query problem for the developer and also increase performance.
- if (count($models) > 0) {
- $models = $builder->eagerLoadRelations($models);
- }
- return $this->related->newCollection($models);
- }
- /**
- * Get a paginator for the "select" statement.
- *
- * @param int $perPage
- * @param array $columns
- * @param string $pageName
- * @param int|null $page
- * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
- */
- public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
- {
- $this->query->addSelect($this->getSelectColumns($columns));
- $paginator = $this->query->paginate($perPage, $columns, $pageName, $page);
- $this->hydratePivotRelation($paginator->items());
- return $paginator;
- }
- /**
- * Paginate the given query into a simple paginator.
- *
- * @param int $perPage
- * @param array $columns
- * @param string $pageName
- * @return \Illuminate\Contracts\Pagination\Paginator
- */
- public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page')
- {
- $this->query->addSelect($this->getSelectColumns($columns));
- $paginator = $this->query->simplePaginate($perPage, $columns, $pageName);
- $this->hydratePivotRelation($paginator->items());
- return $paginator;
- }
- /**
- * Chunk the results of the query.
- *
- * @param int $count
- * @param callable $callback
- * @return bool
- */
- public function chunk($count, callable $callback)
- {
- $this->query->addSelect($this->getSelectColumns());
- return $this->query->chunk($count, function ($results) use ($callback) {
- $this->hydratePivotRelation($results->all());
- return $callback($results);
- });
- }
- /**
- * Hydrate the pivot table relationship on the models.
- *
- * @param array $models
- * @return void
- */
- protected function hydratePivotRelation(array $models)
- {
- // To hydrate the pivot relationship, we will just gather the pivot attributes
- // and create a new Pivot model, which is basically a dynamic model that we
- // will set the attributes, table, and connections on so it they be used.
- foreach ($models as $model) {
- $pivot = $this->newExistingPivot($this->cleanPivotAttributes($model));
- $model->setRelation('pivot', $pivot);
- }
- }
- /**
- * Get the pivot attributes from a model.
- *
- * @param \Illuminate\Database\Eloquent\Model $model
- * @return array
- */
- protected function cleanPivotAttributes(Model $model)
- {
- $values = [];
- foreach ($model->getAttributes() as $key => $value) {
- // To get the pivots attributes we will just take any of the attributes which
- // begin with "pivot_" and add those to this arrays, as well as unsetting
- // them from the parent's models since they exist in a different table.
- if (strpos($key, 'pivot_') === 0) {
- $values[substr($key, 6)] = $value;
- unset($model->$key);
- }
- }
- return $values;
- }
- /**
- * Set the base constraints on the relation query.
- *
- * @return void
- */
- public function addConstraints()
- {
- $this->setJoin();
- if (static::$constraints) {
- $this->setWhere();
- }
- }
- /**
- * Add the constraints for a relationship query.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Builder $parent
- * @param array|mixed $columns
- * @return \Illuminate\Database\Eloquent\Builder
- */
- public function getRelationQuery(Builder $query, Builder $parent, $columns = ['*'])
- {
- if ($parent->getQuery()->from == $query->getQuery()->from) {
- return $this->getRelationQueryForSelfJoin($query, $parent, $columns);
- }
- $this->setJoin($query);
- return parent::getRelationQuery($query, $parent, $columns);
- }
- /**
- * Add the constraints for a relationship query on the same table.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Builder $parent
- * @param array|mixed $columns
- * @return \Illuminate\Database\Eloquent\Builder
- */
- public function getRelationQueryForSelfJoin(Builder $query, Builder $parent, $columns = ['*'])
- {
- $query->select($columns);
- $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash());
- $this->related->setTable($hash);
- $this->setJoin($query);
- return parent::getRelationQuery($query, $parent, $columns);
- }
- /**
- * Get a relationship join table hash.
- *
- * @return string
- */
- public function getRelationCountHash()
- {
- return 'laravel_reserved_'.static::$selfJoinCount++;
- }
- /**
- * Set the select clause for the relation query.
- *
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- protected function getSelectColumns(array $columns = ['*'])
- {
- if ($columns == ['*']) {
- $columns = [$this->related->getTable().'.*'];
- }
- return array_merge($columns, $this->getAliasedPivotColumns());
- }
- /**
- * Get the pivot columns for the relation.
- *
- * @return array
- */
- protected function getAliasedPivotColumns()
- {
- $defaults = [$this->foreignKey, $this->otherKey];
- // We need to alias all of the pivot columns with the "pivot_" prefix so we
- // can easily extract them out of the models and put them into the pivot
- // relationships when they are retrieved and hydrated into the models.
- $columns = [];
- foreach (array_merge($defaults, $this->pivotColumns) as $column) {
- $columns[] = $this->table.'.'.$column.' as pivot_'.$column;
- }
- return array_unique($columns);
- }
- /**
- * Determine whether the given column is defined as a pivot column.
- *
- * @param string $column
- * @return bool
- */
- protected function hasPivotColumn($column)
- {
- return in_array($column, $this->pivotColumns);
- }
- /**
- * Set the join clause for the relation query.
- *
- * @param \Illuminate\Database\Eloquent\Builder|null $query
- * @return $this
- */
- protected function setJoin($query = null)
- {
- $query = $query ?: $this->query;
- // We need to join to the intermediate table on the related model's primary
- // key column with the intermediate table's foreign key for the related
- // model instance. Then we can set the "where" for the parent models.
- $baseTable = $this->related->getTable();
- $key = $baseTable.'.'.$this->related->getKeyName();
- $query->join($this->table, $key, '=', $this->getOtherKey());
- return $this;
- }
- /**
- * Set the where clause for the relation query.
- *
- * @return $this
- */
- protected function setWhere()
- {
- $foreign = $this->getForeignKey();
- $this->query->where($foreign, '=', $this->parent->getKey());
- return $this;
- }
- /**
- * Set the constraints for an eager load of the relation.
- *
- * @param array $models
- * @return void
- */
- public function addEagerConstraints(array $models)
- {
- $this->query->whereIn($this->getForeignKey(), $this->getKeys($models));
- }
- /**
- * Initialize the relation on a set of models.
- *
- * @param array $models
- * @param string $relation
- * @return array
- */
- public function initRelation(array $models, $relation)
- {
- foreach ($models as $model) {
- $model->setRelation($relation, $this->related->newCollection());
- }
- return $models;
- }
- /**
- * Match the eagerly loaded results to their parents.
- *
- * @param array $models
- * @param \Illuminate\Database\Eloquent\Collection $results
- * @param string $relation
- * @return array
- */
- public function match(array $models, Collection $results, $relation)
- {
- $dictionary = $this->buildDictionary($results);
- // Once we have an array dictionary of child objects we can easily match the
- // children back to their parent using the dictionary and the keys on the
- // the parent models. Then we will return the hydrated models back out.
- foreach ($models as $model) {
- if (isset($dictionary[$key = $model->getKey()])) {
- $collection = $this->related->newCollection($dictionary[$key]);
- $model->setRelation($relation, $collection);
- }
- }
- return $models;
- }
- /**
- * Build model dictionary keyed by the relation's foreign key.
- *
- * @param \Illuminate\Database\Eloquent\Collection $results
- * @return array
- */
- protected function buildDictionary(Collection $results)
- {
- $foreign = $this->foreignKey;
- // First we will build a dictionary of child models keyed by the foreign key
- // of the relation so that we will easily and quickly match them to their
- // parents without having a possibly slow inner loops for every models.
- $dictionary = [];
- foreach ($results as $result) {
- $dictionary[$result->pivot->$foreign][] = $result;
- }
- return $dictionary;
- }
- /**
- * Touch all of the related models for the relationship.
- *
- * E.g.: Touch all roles associated with this user.
- *
- * @return void
- */
- public function touch()
- {
- $key = $this->getRelated()->getKeyName();
- $columns = $this->getRelatedFreshUpdate();
- // If we actually have IDs for the relation, we will run the query to update all
- // the related model's timestamps, to make sure these all reflect the changes
- // to the parent models. This will help us keep any caching synced up here.
- $ids = $this->getRelatedIds();
- if (count($ids) > 0) {
- $this->getRelated()->newQuery()->whereIn($key, $ids)->update($columns);
- }
- }
- /**
- * Get all of the IDs for the related models.
- *
- * @return \Illuminate\Support\Collection
- */
- public function getRelatedIds()
- {
- $related = $this->getRelated();
- $fullKey = $related->getQualifiedKeyName();
- return $this->getQuery()->select($fullKey)->pluck($related->getKeyName());
- }
- /**
- * Save a new model and attach it to the parent model.
- *
- * @param \Illuminate\Database\Eloquent\Model $model
- * @param array $joining
- * @param bool $touch
- * @return \Illuminate\Database\Eloquent\Model
- */
- public function save(Model $model, array $joining = [], $touch = true)
- {
- $model->save(['touch' => false]);
- $this->attach($model->getKey(), $joining, $touch);
- return $model;
- }
- /**
- * Save an array of new models and attach them to the parent model.
- *
- * @param \Illuminate\Support\Collection|array $models
- * @param array $joinings
- * @return array
- */
- public function saveMany($models, array $joinings = [])
- {
- foreach ($models as $key => $model) {
- $this->save($model, (array) Arr::get($joinings, $key), false);
- }
- $this->touchIfTouching();
- return $models;
- }
- /**
- * Find a related model by its primary key.
- *
- * @param mixed $id
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
- */
- public function find($id, $columns = ['*'])
- {
- if (is_array($id)) {
- return $this->findMany($id, $columns);
- }
- $this->where($this->getRelated()->getQualifiedKeyName(), '=', $id);
- return $this->first($columns);
- }
- /**
- * Find multiple related models by their primary keys.
- *
- * @param mixed $ids
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Collection
- */
- public function findMany($ids, $columns = ['*'])
- {
- if (empty($ids)) {
- return $this->getRelated()->newCollection();
- }
- $this->whereIn($this->getRelated()->getQualifiedKeyName(), $ids);
- return $this->get($columns);
- }
- /**
- * Find a related model by its primary key or throw an exception.
- *
- * @param mixed $id
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function findOrFail($id, $columns = ['*'])
- {
- $result = $this->find($id, $columns);
- if (is_array($id)) {
- if (count($result) == count(array_unique($id))) {
- return $result;
- }
- } elseif (! is_null($result)) {
- return $result;
- }
- throw (new ModelNotFoundException)->setModel(get_class($this->parent));
- }
- /**
- * Find a related model by its primary key or return new instance of the related model.
- *
- * @param mixed $id
- * @param array $columns
- * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
- */
- public function findOrNew($id, $columns = ['*'])
- {
- if (is_null($instance = $this->find($id, $columns))) {
- $instance = $this->getRelated()->newInstance();
- }
- return $instance;
- }
- /**
- * Get the first related model record matching the attributes or instantiate it.
- *
- * @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model
- */
- public function firstOrNew(array $attributes)
- {
- if (is_null($instance = $this->where($attributes)->first())) {
- $instance = $this->related->newInstance($attributes);
- }
- return $instance;
- }
- /**
- * Get the first related record matching the attributes or create it.
- *
- * @param array $attributes
- * @param array $joining
- * @param bool $touch
- * @return \Illuminate\Database\Eloquent\Model
- */
- public function firstOrCreate(array $attributes, array $joining = [], $touch = true)
- {
- if (is_null($instance = $this->where($attributes)->first())) {
- $instance = $this->create($attributes, $joining, $touch);
- }
- return $instance;
- }
- /**
- * Create or update a related record matching the attributes, and fill it with values.
- *
- * @param array $attributes
- * @param array $values
- * @param array $joining
- * @param bool $touch
- * @return \Illuminate\Database\Eloquent\Model
- */
- public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
- {
- if (is_null($instance = $this->where($attributes)->first())) {
- return $this->create($values, $joining, $touch);
- }
- $instance->fill($values);
- $instance->save(['touch' => false]);
- return $instance;
- }
- /**
- * Create a new instance of the related model.
- *
- * @param array $attributes
- * @param array $joining
- * @param bool $touch
- * @return \Illuminate\Database\Eloquent\Model
- */
- public function create(array $attributes, array $joining = [], $touch = true)
- {
- $instance = $this->related->newInstance($attributes);
- // Once we save the related model, we need to attach it to the base model via
- // through intermediate table so we'll use the existing "attach" method to
- // accomplish this which will insert the record and any more attributes.
- $instance->save(['touch' => false]);
- $this->attach($instance->getKey(), $joining, $touch);
- return $instance;
- }
- /**
- * Create an array of new instances of the related models.
- *
- * @param array $records
- * @param array $joinings
- * @return array
- */
- public function createMany(array $records, array $joinings = [])
- {
- $instances = [];
- foreach ($records as $key => $record) {
- $instances[] = $this->create($record, (array) Arr::get($joinings, $key), false);
- }
- $this->touchIfTouching();
- return $instances;
- }
- /**
- * Sync the intermediate tables with a list of IDs or collection of models.
- *
- * @param \Illuminate\Database\Eloquent\Collection|array $ids
- * @param bool $detaching
- * @return array
- */
- public function sync($ids, $detaching = true)
- {
- $changes = [
- 'attached' => [], 'detached' => [], 'updated' => [],
- ];
- if ($ids instanceof Collection) {
- $ids = $ids->modelKeys();
- }
- // First we need to attach any of the associated models that are not currently
- // in this joining table. We'll spin through the given IDs, checking to see
- // if they exist in the array of current ones, and if not we will insert.
- $current = $this->newPivotQuery()->pluck($this->otherKey);
- $records = $this->formatSyncList($ids);
- $detach = array_diff($current, array_keys($records));
- // Next, we will take the differences of the currents and given IDs and detach
- // all of the entities that exist in the "current" array but are not in the
- // the array of the IDs given to the method which will complete the sync.
- if ($detaching && count($detach) > 0) {
- $this->detach($detach);
- $changes['detached'] = (array) array_map(function ($v) {
- return is_numeric($v) ? (int) $v : (string) $v;
- }, $detach);
- }
- // Now we are finally ready to attach the new records. Note that we'll disable
- // touching until after the entire operation is complete so we don't fire a
- // ton of touch operations until we are totally done syncing the records.
- $changes = array_merge(
- $changes, $this->attachNew($records, $current, false)
- );
- if (count($changes['attached']) || count($changes['updated'])) {
- $this->touchIfTouching();
- }
- return $changes;
- }
- /**
- * Format the sync list so that it is keyed by ID.
- *
- * @param array $records
- * @return array
- */
- protected function formatSyncList(array $records)
- {
- $results = [];
- foreach ($records as $id => $attributes) {
- if (! is_array($attributes)) {
- list($id, $attributes) = [$attributes, []];
- }
- $results[$id] = $attributes;
- }
- return $results;
- }
- /**
- * Attach all of the IDs that aren't in the current array.
- *
- * @param array $records
- * @param array $current
- * @param bool $touch
- * @return array
- */
- protected function attachNew(array $records, array $current, $touch = true)
- {
- $changes = ['attached' => [], 'updated' => []];
- foreach ($records as $id => $attributes) {
- // If the ID is not in the list of existing pivot IDs, we will insert a new pivot
- // record, otherwise, we will just update this existing record on this joining
- // table, so that the developers will easily update these records pain free.
- if (! in_array($id, $current)) {
- $this->attach($id, $attributes, $touch);
- $changes['attached'][] = is_numeric($id) ? (int) $id : (string) $id;
- }
- // Now we'll try to update an existing pivot record with the attributes that were
- // given to the method. If the model is actually updated we will add it to the
- // list of updated pivot records so we return them back out to the consumer.
- elseif (count($attributes) > 0 &&
- $this->updateExistingPivot($id, $attributes, $touch)) {
- $changes['updated'][] = is_numeric($id) ? (int) $id : (string) $id;
- }
- }
- return $changes;
- }
- /**
- * Update an existing pivot record on the table.
- *
- * @param mixed $id
- * @param array $attributes
- * @param bool $touch
- * @return int
- */
- public function updateExistingPivot($id, array $attributes, $touch = true)
- {
- if (in_array($this->updatedAt(), $this->pivotColumns)) {
- $attributes = $this->setTimestampsOnAttach($attributes, true);
- }
- $updated = $this->newPivotStatementForId($id)->update($attributes);
- if ($touch) {
- $this->touchIfTouching();
- }
- return $updated;
- }
- /**
- * Attach a model to the parent.
- *
- * @param mixed $id
- * @param array $attributes
- * @param bool $touch
- * @return void
- */
- public function attach($id, array $attributes = [], $touch = true)
- {
- if ($id instanceof Model) {
- $id = $id->getKey();
- }
- if ($id instanceof Collection) {
- $id = $id->modelKeys();
- }
- $query = $this->newPivotStatement();
- $query->insert($this->createAttachRecords((array) $id, $attributes));
- if ($touch) {
- $this->touchIfTouching();
- }
- }
- /**
- * Create an array of records to insert into the pivot table.
- *
- * @param array $ids
- * @param array $attributes
- * @return array
- */
- protected function createAttachRecords($ids, array $attributes)
- {
- $records = [];
- $timed = ($this->hasPivotColumn($this->createdAt()) ||
- $this->hasPivotColumn($this->updatedAt()));
- // To create the attachment records, we will simply spin through the IDs given
- // and create a new record to insert for each ID. Each ID may actually be a
- // key in the array, with extra attributes to be placed in other columns.
- foreach ($ids as $key => $value) {
- $records[] = $this->attacher($key, $value, $attributes, $timed);
- }
- return $records;
- }
- /**
- * Create a full attachment record payload.
- *
- * @param int $key
- * @param mixed $value
- * @param array $attributes
- * @param bool $timed
- * @return array
- */
- protected function attacher($key, $value, $attributes, $timed)
- {
- list($id, $extra) = $this->getAttachId($key, $value, $attributes);
- // To create the attachment records, we will simply spin through the IDs given
- // and create a new record to insert for each ID. Each ID may actually be a
- // key in the array, with extra attributes to be placed in other columns.
- $record = $this->createAttachRecord($id, $timed);
- return array_merge($record, $extra);
- }
- /**
- * Get the attach record ID and extra attributes.
- *
- * @param mixed $key
- * @param mixed $value
- * @param array $attributes
- * @return array
- */
- protected function getAttachId($key, $value, array $attributes)
- {
- if (is_array($value)) {
- return [$key, array_merge($value, $attributes)];
- }
- return [$value, $attributes];
- }
- /**
- * Create a new pivot attachment record.
- *
- * @param int $id
- * @param bool $timed
- * @return array
- */
- protected function createAttachRecord($id, $timed)
- {
- $record[$this->foreignKey] = $this->parent->getKey();
- $record[$this->otherKey] = $id;
- // If the record needs to have creation and update timestamps, we will make
- // them by calling the parent model's "freshTimestamp" method which will
- // provide us with a fresh timestamp in this model's preferred format.
- if ($timed) {
- $record = $this->setTimestampsOnAttach($record);
- }
- return $record;
- }
- /**
- * Set the creation and update timestamps on an attach record.
- *
- * @param array $record
- * @param bool $exists
- * @return array
- */
- protected function setTimestampsOnAttach(array $record, $exists = false)
- {
- $fresh = $this->parent->freshTimestamp();
- if (! $exists && $this->hasPivotColumn($this->createdAt())) {
- $record[$this->createdAt()] = $fresh;
- }
- if ($this->hasPivotColumn($this->updatedAt())) {
- $record[$this->updatedAt()] = $fresh;
- }
- return $record;
- }
- /**
- * Detach models from the relationship.
- *
- * @param int|array $ids
- * @param bool $touch
- * @return int
- */
- public function detach($ids = [], $touch = true)
- {
- if ($ids instanceof Model) {
- $ids = (array) $ids->getKey();
- }
- $query = $this->newPivotQuery();
- // If associated IDs were passed to the method we will only delete those
- // associations, otherwise all of the association ties will be broken.
- // We'll return the numbers of affected rows when we do the deletes.
- $ids = (array) $ids;
- if (count($ids) > 0) {
- $query->whereIn($this->otherKey, (array) $ids);
- }
- // Once we have all of the conditions set on the statement, we are ready
- // to run the delete on the pivot table. Then, if the touch parameter
- // is true, we will go ahead and touch all related models to sync.
- $results = $query->delete();
- if ($touch) {
- $this->touchIfTouching();
- }
- return $results;
- }
- /**
- * If we're touching the parent model, touch.
- *
- * @return void
- */
- public function touchIfTouching()
- {
- if ($this->touchingParent()) {
- $this->getParent()->touch();
- }
- if ($this->getParent()->touches($this->relationName)) {
- $this->touch();
- }
- }
- /**
- * Determine if we should touch the parent on sync.
- *
- * @return bool
- */
- protected function touchingParent()
- {
- return $this->getRelated()->touches($this->guessInverseRelation());
- }
- /**
- * Attempt to guess the name of the inverse of the relation.
- *
- * @return string
- */
- protected function guessInverseRelation()
- {
- return Str::camel(Str::plural(class_basename($this->getParent())));
- }
- /**
- * Create a new query builder for the pivot table.
- *
- * @return \Illuminate\Database\Query\Builder
- */
- protected function newPivotQuery()
- {
- $query = $this->newPivotStatement();
- foreach ($this->pivotWheres as $whereArgs) {
- call_user_func_array([$query, 'where'], $whereArgs);
- }
- return $query->where($this->foreignKey, $this->parent->getKey());
- }
- /**
- * Get a new plain query builder for the pivot table.
- *
- * @return \Illuminate\Database\Query\Builder
- */
- public function newPivotStatement()
- {
- return $this->query->getQuery()->newQuery()->from($this->table);
- }
- /**
- * Get a new pivot statement for a given "other" ID.
- *
- * @param mixed $id
- * @return \Illuminate\Database\Query\Builder
- */
- public function newPivotStatementForId($id)
- {
- return $this->newPivotQuery()->where($this->otherKey, $id);
- }
- /**
- * Create a new pivot model instance.
- *
- * @param array $attributes
- * @param bool $exists
- * @return \Illuminate\Database\Eloquent\Relations\Pivot
- */
- public function newPivot(array $attributes = [], $exists = false)
- {
- $pivot = $this->related->newPivot($this->parent, $attributes, $this->table, $exists);
- return $pivot->setPivotKeys($this->foreignKey, $this->otherKey);
- }
- /**
- * Create a new existing pivot model instance.
- *
- * @param array $attributes
- * @return \Illuminate\Database\Eloquent\Relations\Pivot
- */
- public function newExistingPivot(array $attributes = [])
- {
- return $this->newPivot($attributes, true);
- }
- /**
- * Set the columns on the pivot table to retrieve.
- *
- * @param array|mixed $columns
- * @return $this
- */
- public function withPivot($columns)
- {
- $columns = is_array($columns) ? $columns : func_get_args();
- $this->pivotColumns = array_merge($this->pivotColumns, $columns);
- return $this;
- }
- /**
- * Specify that the pivot table has creation and update timestamps.
- *
- * @param mixed $createdAt
- * @param mixed $updatedAt
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function withTimestamps($createdAt = null, $updatedAt = null)
- {
- $this->pivotCreatedAt = $createdAt;
- $this->pivotUpdatedAt = $updatedAt;
- return $this->withPivot($this->createdAt(), $this->updatedAt());
- }
- /**
- * Get the name of the "created at" column.
- *
- * @return string
- */
- public function createdAt()
- {
- return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn();
- }
- /**
- * Get the name of the "updated at" column.
- *
- * @return string
- */
- public function updatedAt()
- {
- return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn();
- }
- /**
- * Get the related model's updated at column name.
- *
- * @return string
- */
- public function getRelatedFreshUpdate()
- {
- return [$this->related->getUpdatedAtColumn() => $this->related->freshTimestamp()];
- }
- /**
- * Get the key for comparing against the parent key in "has" query.
- *
- * @return string
- */
- public function getHasCompareKey()
- {
- return $this->getForeignKey();
- }
- /**
- * Get the fully qualified foreign key for the relation.
- *
- * @return string
- */
- public function getForeignKey()
- {
- return $this->table.'.'.$this->foreignKey;
- }
- /**
- * Get the fully qualified "other key" for the relation.
- *
- * @return string
- */
- public function getOtherKey()
- {
- return $this->table.'.'.$this->otherKey;
- }
- /**
- * Get the intermediate table for the relationship.
- *
- * @return string
- */
- public function getTable()
- {
- return $this->table;
- }
- /**
- * Get the relationship name for the relationship.
- *
- * @return string
- */
- public function getRelationName()
- {
- return $this->relationName;
- }
- }