1: <?php
2:
3: namespace Baril\Bonsai\Relations\Concerns;
4:
5: use Illuminate\Database\Eloquent\Builder;
6: use Illuminate\Database\Eloquent\Collection as EloquentCollection;
7: use Illuminate\Database\Eloquent\Model;
8:
9: /**
10: * @mixin \Illuminate\Database\Eloquent\Relations\Relation
11: */
12: trait ExcludesSelf
13: {
14: /**
15: * @var bool
16: */
17: protected $excludeSelf = false;
18:
19: /**
20: * @return static
21: */
22: public function withoutSelf()
23: {
24: $this->excludeSelf = true;
25:
26: if (static::$constraints) {
27: // Exclude parent model from results:
28: $this->getRelationQuery()->withGlobalScope(
29: 'excludeSelfFromResults',
30: function ($query) {
31: return $query->whereKeyNot($this->parent->getKey());
32: }
33: );
34: }
35:
36: return $this;
37: }
38:
39: /**
40: * @return static
41: */
42: public function withSelf()
43: {
44: $this->excludeSelf = false;
45:
46: if (static::$constraints) {
47: $this->getRelationQuery()->withoutGlobalScope('excludeSelfFromResults');
48: }
49:
50: return $this;
51: }
52:
53: /**
54: * Match the eagerly loaded results to their parents.
55: *
56: * @param array<int, \Illuminate\Database\Eloquent\Model> $models
57: * @param \Illuminate\Database\Eloquent\Collection<int, TRelatedModel> $results
58: * @param string $relation
59: * @return array<int, \Illuminate\Database\Eloquent\Model>
60: */
61: public function match(array $models, EloquentCollection $results, $relation)
62: {
63: return $this->excludeSelfFromMatchesIfExcluded(
64: parent::match($models, $results, $relation),
65: $relation
66: );
67: }
68:
69: /**
70: * @param array<int, \Illuminate\Database\Eloquent\Model> $models
71: * @param string $relation
72: * @return array<int, \Illuminate\Database\Eloquent\Model>
73: */
74: protected function excludeSelfFromMatchesIfExcluded(array $models, $relation)
75: {
76: if (! $this->excludeSelf) {
77: return $models;
78: }
79:
80: foreach ($models as $model) {
81: $related = $model->getRelation($relation);
82: if (
83: $related instanceof EloquentCollection
84: && $related->contains($model)
85: ) {
86: $model->setRelation($relation, $related->except($model->getKey()));
87: }
88: if (
89: $related instanceof Model
90: && $related->getTable() == $model->getTable()
91: && $related->getKey() === $model->getKey()
92: ) {
93: $this->initRelation([$model], $relation);
94: }
95: }
96:
97: return $models;
98: }
99:
100: /**
101: * Add the constraints for a relationship query on the same table.
102: *
103: * @see \Illuminate\Database\Eloquent\Relations\HasOneOrMany::getRelationExistenceQueryForSelfRelation()
104: *
105: * @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
106: * @param \Illuminate\Database\Eloquent\Builder<TDeclaringModel> $parentQuery
107: * @param mixed $columns
108: * @return \Illuminate\Database\Eloquent\Builder<TRelatedModel>
109: */
110: public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
111: {
112: return $this->excludeSelfFromRelationExistenceQueryIfExcluded(
113: parent::getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns),
114: $parentQuery
115: );
116: }
117:
118: /**
119: * Add the constraints for a relationship query on the same table.
120: *
121: * @see \Illuminate\Database\Eloquent\Relations\BelongsToMany::getRelationExistenceQueryForSelfJoin()
122: *
123: * @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
124: * @param \Illuminate\Database\Eloquent\Builder<TDeclaringModel> $parentQuery
125: * @param mixed $columns
126: * @return \Illuminate\Database\Eloquent\Builder<TRelatedModel>
127: */
128: public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*'])
129: {
130: return $this->excludeSelfFromRelationExistenceQueryIfExcluded(
131: parent::getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns),
132: $parentQuery
133: );
134: }
135:
136: /**
137: * @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
138: * @param \Illuminate\Database\Eloquent\Builder<TDeclaringModel> $parentQuery
139: * @return \Illuminate\Database\Eloquent\Builder<TRelatedModel>
140: */
141: protected function excludeSelfFromRelationExistenceQueryIfExcluded(
142: Builder $query,
143: Builder $parentQuery
144: ) {
145: return $query
146: ->when($this->excludeSelf, function ($query) use ($parentQuery) {
147: $query->withGlobalScope(
148: 'excludeSelfFromResults',
149: function ($query) use ($parentQuery) {
150: $query->whereColumn(
151: $parentQuery->qualifyColumn($this->parent->getKeyName()),
152: '!=',
153: $query->qualifyColumn($this->related->getKeyName())
154: );
155: }
156: );
157: $query->macro('withSelf', function ($query) {
158: $query->withoutGlobalScope('excludeSelfFromResults');
159: });
160: });
161: }
162: }
163: