Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.11% covered (success)
98.11%
52 / 53
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExcludesSelf
98.11% covered (success)
98.11%
52 / 53
85.71% covered (warning)
85.71%
6 / 7
16
0.00% covered (danger)
0.00%
0 / 1
 withoutSelf
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 withSelf
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 match
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 excludeSelfFromMatchesIfExcluded
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
8.04
 getRelationExistenceQueryForSelfRelation
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getRelationExistenceQueryForSelfJoin
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 excludeSelfFromRelationExistenceQueryIfExcluded
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Baril\Bonsai\Relations\Concerns;
4
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Collection as EloquentCollection;
7use Illuminate\Database\Eloquent\Model;
8
9/**
10 * @mixin \Illuminate\Database\Eloquent\Relations\Relation
11 */
12trait 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}