1: <?php
2:
3: namespace Baril\Bonsai;
4:
5: use Illuminate\Database\Eloquent\Builder;
6: use Illuminate\Database\Eloquent\Model;
7: use Illuminate\Database\Eloquent\Relations\Pivot;
8:
9: class Closure extends Pivot
10: {
11: /**
12: * Added for compatibility with older Laravel versions.
13: *
14: * @todo remove in v4
15: *
16: * @var \Illuminate\Database\Eloquent\Model
17: */
18: public $pivotRelated;
19:
20: /**
21: * Alias for the query builder instance that references
22: * this model.
23: *
24: * @var string
25: */
26: protected $as;
27:
28: /**
29: * @return string
30: */
31: protected function getAlias()
32: {
33: return $this->as ?? $this->getTable();
34: }
35:
36: /** @inheritDoc */
37: public function newInstance($attributes = [], $exists = false)
38: {
39: return parent::newInstance($attributes, $exists)
40: ->setPivotKeys($this->foreignKey, $this->relatedKey)
41: ->setParentModel($this->pivotParent)
42: ->setRelatedModel($this->pivotRelated)
43: ->hydrateRelatedKey($attributes);
44: }
45:
46: /**
47: * Set the parent model of the relationship.
48: *
49: * @param \Illuminate\Database\Eloquent\Model $parent
50: * @return $this
51: */
52: public function setParentModel(Model $parent)
53: {
54: $this->pivotParent = $parent;
55:
56: return $this;
57: }
58:
59: /**
60: * Set the related model of the relationship.
61: * Added for compatibility with older Laravel versions.
62: *
63: * @todo remove in v4
64: *
65: * @param \Illuminate\Database\Eloquent\Model|null $related
66: * @return $this
67: */
68: public function setRelatedModel(?Model $related = null)
69: {
70: $this->pivotRelated = $related;
71:
72: return $this;
73: }
74:
75: /** @inheritDoc */
76: public function setRawAttributes(array $attributes, $sync = false)
77: {
78: return parent::setRawAttributes($attributes, $sync)
79: ->hydrateRelatedKey($attributes);
80: }
81:
82: /**
83: * Hydrate the related model id when the closure is hydrated.
84: *
85: * @param array $attributes
86: * @return $this
87: */
88: protected function hydrateRelatedKey($attributes)
89: {
90: if (
91: array_key_exists($this->relatedKey, $attributes)
92: && $this->pivotRelated->getKey() != $attributes[$this->relatedKey]
93: ) {
94: $this->pivotRelated = $this->pivotRelated
95: ->newInstance([], true)
96: ->setRawAttributes([
97: $this->pivotRelated->getKeyName() => $attributes[$this->relatedKey]
98: ], true);
99: }
100:
101: return $this;
102: }
103:
104: /**
105: * Belongs-to relation to the related model.
106: *
107: * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
108: */
109: public function related()
110: {
111: return $this->belongsTo(
112: get_class($this->pivotRelated),
113: $this->getRelatedKey()
114: );
115: }
116:
117: /**
118: * @param \Illuminate\Database\Eloquent\Builder $query
119: * @param string $as
120: * @return void
121: */
122: public function scopeAs(Builder $query, $as)
123: {
124: $this->as = $as;
125:
126: $from = $query->getQuery()->from;
127: $table = preg_split('/\s+as\s+/i', $from)[0];
128:
129: $query->getQuery()->from = "$table as $as";
130: }
131:
132: /**
133: * @param \Illuminate\Database\Eloquent\Builder $query
134: * @param string $as
135: * @return void
136: */
137: public function scopeSelfCrossJoin(Builder $query, $as)
138: {
139: $table = $this->getTable();
140:
141: $query->crossJoin("$table as $as");
142: }
143:
144: /**
145: * @param \Illuminate\Database\Eloquent\Builder $query
146: * @param string $as
147: * @param string $first
148: * @param string $second
149: * @return void
150: */
151: public function scopeSelfJoin(Builder $query, $as, $first = null, $second = null)
152: {
153: $table = $this->getTable();
154:
155: $first = $first ?? $this->getRelatedKey();
156: $second = $second ?? $first;
157:
158: if (false === strstr($first, '.')) {
159: $first = $this->getAlias() . ".$first";
160: }
161: if (false === strstr($second, '.')) {
162: $second = "$as.$second";
163: }
164:
165: $query->join(
166: "$table as $as",
167: $first,
168: '=',
169: $second
170: );
171: }
172:
173: /**
174: * @param \Illuminate\Database\Eloquent\Builder $query
175: * @param mixed|\Illuminate\Database\Eloquent\Model $node
176: * @return void
177: */
178: public function scopeWhereAncestor(Builder $query, $node)
179: {
180: $value = $this->parseIds($node);
181:
182: $method = is_array($value) ? 'whereIn' : 'where';
183:
184: $query->$method('ancestor_id', $value);
185: }
186:
187: /**
188: * @param \Illuminate\Database\Eloquent\Builder $query
189: * @param mixed|\Illuminate\Database\Eloquent\Model $node
190: * @return void
191: */
192: public function scopeWhereDescendant(Builder $query, $node)
193: {
194: $value = $this->parseIds($node);
195:
196: $method = is_array($value) ? 'whereIn' : 'where';
197:
198: $query->$method('descendant_id', $value);
199: }
200:
201: /**
202: * @param \Illuminate\Database\Eloquent\Builder $query
203: * @param string $operator
204: * @param int $value
205: * @return void
206: */
207: public function scopeWhereDepth(Builder $query, $operator, $value)
208: {
209: $query->where('depth', $operator, $value);
210: }
211:
212: /**
213: * @param \Illuminate\Database\Eloquent\Builder $query
214: * @return void
215: */
216: public function scopeWithoutSelf(Builder $query)
217: {
218: $query->where('depth', '>', 0);
219: }
220:
221: /**
222: * Get the ID or IDs from the given mixed value.
223: *
224: * @param mixed $value
225: * @return mixed
226: */
227: protected function parseIds($value)
228: {
229: if ($value instanceof Model) {
230: return $value->getKey();
231: }
232:
233: if (!is_iterable($value)) {
234: return $value;
235: }
236:
237: $parsedIds = [];
238: foreach ($value as $id) {
239: $parsedIds[] = $this->parseIds($id);
240: }
241:
242: return $parsedIds;
243: }
244: }
245: