1: <?php
2:
3: namespace Baril\Bonsai\Relations\Concerns;
4:
5: use Baril\Bonsai\Closure;
6: use Illuminate\Database\Eloquent\Builder;
7: use Illuminate\Database\Eloquent\Collection as EloquentCollection;
8:
9: /**
10: * @mixin \Illuminate\Database\Eloquent\Relations\BelongsToMany
11: */
12: trait InteractsWithClosureTable
13: {
14: use AutoloadsOtherRelations;
15: use IsReadOnly;
16:
17: /**
18: * @var int|null
19: */
20: protected $depth = null;
21:
22: /**
23: * @param string $relation
24: * @return $this
25: */
26: public function closes($relation, $callback = null)
27: {
28: $defaultCallback = function (array $models, EloquentCollection $results) {
29: // When the relation has been queried with a max depth,
30: // we don't want the closed relation to be set to null
31: // or empty collection on models that belong to the
32: // last level before the limit.
33: if (null !== $this->depth) {
34: $models = array_filter(
35: $models,
36: function ($model) {
37: $depth = $model->closure->depth ?? 0;
38: return $depth < $this->depth;
39: }
40: );
41: }
42:
43: return [
44: $models,
45: $results->unique()
46: ];
47: };
48:
49: return $this->autoloads(
50: $relation,
51: $callback
52: ? function ($models, $results) use ($callback, $defaultCallback) {
53: list($models, $results) = $defaultCallback($models, $results);
54: return $callback($models, $results);
55: }
56: : $defaultCallback
57: );
58: }
59:
60: /**
61: * Set the base constraints on the relation query.
62: *
63: * @return void
64: */
65: public function addConstraints()
66: {
67: parent::addConstraints();
68:
69: $this
70: ->as('closure')
71: ->using(Closure::class)
72: ->withPivot('depth');
73: }
74:
75: /**
76: * Add the constraints for an internal relationship existence query.
77: *
78: * Essentially, these queries compare on column names like whereColumn.
79: *
80: * @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
81: * @param \Illuminate\Database\Eloquent\Builder<TDeclaringModel> $parentQuery
82: * @param mixed $columns
83: * @return \Illuminate\Database\Eloquent\Builder<TRelatedModel>
84: */
85: public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
86: {
87: $query = parent::getRelationExistenceQuery($query, $parentQuery, $columns);
88:
89: $query->macro('maxDepth', function ($query, $depth) {
90: $query->where($this->qualifyPivotColumn('depth'), '<=', $depth);
91: });
92:
93: return $query;
94: }
95:
96: /**
97: * @deprecated
98: *
99: * @return $this
100: */
101: public function excludingSelf()
102: {
103: return $this->withoutSelf();
104: }
105:
106: /**
107: * @deprecated
108: *
109: * @return $this
110: */
111: public function includingSelf()
112: {
113: return $this->withSelf();
114: }
115:
116: /**
117: * @deprecated
118: *
119: * @param int $depth
120: * @return $this
121: */
122: public function upToDepth($depth)
123: {
124: return $this->maxDepth($depth);
125: }
126:
127: /**
128: * @see \Illuminate\Database\Eloquent\Relations\BelongsToMany::wherePivot()
129: *
130: * @param int $depth
131: * @return $this
132: */
133: public function maxDepth($depth)
134: {
135: // We'll need the depth again when we match the eager-loaded models:
136: $this->depth = $depth;
137: return $this->wherePivot('depth', '<=', $depth);
138: }
139:
140: /**
141: * @param string $direction
142: * @return $this
143: */
144: public function orderByDepth($direction = 'asc')
145: {
146: return $this->orderBy($this->table . '.depth', $direction);
147: }
148: }
149: