Skip to content

Commit 61e6686

Browse files
committed
[11.x] Add the ability to defer resolving models until inside the controller action
1 parent 16c01c2 commit 61e6686

File tree

4 files changed

+686
-0
lines changed

4 files changed

+686
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Eloquent\Concerns;
4+
5+
use Closure;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Relations\Relation;
8+
9+
trait DeferRouteBinding
10+
{
11+
protected ?object $deferredInit = null;
12+
13+
protected ?bool $deferredInitResolved = null;
14+
15+
public function resolveRouteBinding($value, $field = null)
16+
{
17+
$this->defer($value, $field, false);
18+
19+
return $this;
20+
}
21+
22+
public function resolveSoftDeletableRouteBinding($value, $field = null)
23+
{
24+
$this->defer($value, $field, true);
25+
26+
return $this;
27+
}
28+
29+
public function resolveChildRouteBinding($childType, $value, $field)
30+
{
31+
return $this->deferScoped($childType, $value, $field, false);
32+
}
33+
34+
public function resolveSoftDeletableChildRouteBinding($childType, $value, $field)
35+
{
36+
return $this->deferScoped($childType, $value, $field, true);
37+
}
38+
39+
public function __invoke(): static
40+
{
41+
if ($this->deferredInit !== null) {
42+
$deferredInit = $this->deferredInit;
43+
$this->deferredInit = null;
44+
parent::__construct();
45+
$model = $deferredInit();
46+
$this->exists = true;
47+
$this->setRawAttributes((array) $model->attributes, true);
48+
$this->setConnection($model->connection);
49+
$this->fireModelEvent('retrieved', false);
50+
$this->deferredInitResolved = true;
51+
}
52+
53+
return $this;
54+
}
55+
56+
public function __get($key)
57+
{
58+
$this();
59+
60+
return parent::__get($key);
61+
}
62+
63+
public function __set($key, $value)
64+
{
65+
$this();
66+
67+
parent::__set($key, $value);
68+
}
69+
70+
public function __call($method, $parameters)
71+
{
72+
$this();
73+
74+
return parent::__call($method, $parameters);
75+
}
76+
77+
public function __isset($key)
78+
{
79+
$this();
80+
81+
return parent::__isset($key);
82+
}
83+
84+
public function __unset($key)
85+
{
86+
$this();
87+
88+
parent::__unset($key);
89+
}
90+
91+
public function __toString()
92+
{
93+
$this();
94+
95+
return parent::__toString();
96+
}
97+
98+
public function toArray()
99+
{
100+
$this();
101+
102+
return parent::toArray();
103+
}
104+
105+
public function toJson($options = 0)
106+
{
107+
$this();
108+
109+
return parent::toJson($options);
110+
}
111+
112+
public function jsonSerialize(): mixed
113+
{
114+
$this();
115+
116+
return parent::jsonSerialize();
117+
}
118+
119+
public function update(array $attributes = [], array $options = [])
120+
{
121+
$this();
122+
123+
return parent::update($attributes, $options);
124+
}
125+
126+
public function updateOrFail(array $attributes = [], array $options = [])
127+
{
128+
$this();
129+
130+
return parent::updateOrFail($attributes, $options);
131+
}
132+
133+
public function updateQuietly(array $attributes = [], array $options = [])
134+
{
135+
$this();
136+
137+
return parent::updateQuietly($attributes, $options);
138+
}
139+
140+
public function delete()
141+
{
142+
$this();
143+
144+
return parent::delete();
145+
}
146+
147+
public function deleteOrFail()
148+
{
149+
$this();
150+
151+
return parent::deleteOrFail();
152+
}
153+
154+
public function deleteQuietly()
155+
{
156+
$this();
157+
158+
return parent::deleteQuietly();
159+
}
160+
161+
public function save(array $options = [])
162+
{
163+
$this();
164+
165+
return parent::save($options);
166+
}
167+
168+
public function saveOrFail(array $options = [])
169+
{
170+
$this();
171+
172+
return parent::saveOrFail($options);
173+
}
174+
175+
public function saveQuietly(array $options = [])
176+
{
177+
$this();
178+
179+
return parent::saveQuietly($options);
180+
}
181+
182+
public function deferred(object $deferredInit): void
183+
{
184+
$this->deferredInitResolved = false;
185+
$this->deferredInit = $deferredInit;
186+
}
187+
188+
protected function defer(mixed $value, ?string $field, bool $withTrashed): void
189+
{
190+
$this->deferredInitResolved = false;
191+
192+
$closure = $withTrashed
193+
? fn () => $this->resolveRouteBindingQuery($this, $value, $field)->withTrashed()->firstOrFail()
194+
: fn () => $this->resolveRouteBindingQuery($this, $value, $field)->firstOrFail();
195+
196+
$this->deferredInit = new class('resolveRouteBindingQuery', $value, $field, $closure)
197+
{
198+
public function __construct(public string $method, public mixed $value, public ?string $field, public Closure $closure)
199+
{
200+
}
201+
202+
public function __invoke()
203+
{
204+
return ($this->closure)();
205+
}
206+
};
207+
}
208+
209+
protected function deferScoped(string $childType, mixed $value, ?string $field, bool $withTrashed): Model
210+
{
211+
/** @var Relation $relationship */
212+
$relationship = $this->{$this->childRouteBindingRelationshipName($childType)}();
213+
214+
$child = $relationship->getModel();
215+
216+
if (! isset(\trait_uses_recursive($child)[DeferRouteBinding::class])) {
217+
return parent::resolveChildRouteBindingQuery($childType, $value, $field)->first();
218+
}
219+
220+
$closure = ! $withTrashed
221+
? fn () => $this->resolveChildRouteBindingQuery($childType, $value, $field)->firstOrFail()
222+
: fn () => $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->firstOrFail();
223+
224+
$deferredInit = new class('resolveChildRouteBindingQuery', $value, $field, $withTrashed, $closure)
225+
{
226+
public function __construct(public string $method, public mixed $value, public ?string $field, public bool $withTrashed, public Closure $closure)
227+
{
228+
}
229+
230+
public function __invoke()
231+
{
232+
return ($this->closure)();
233+
}
234+
};
235+
236+
$child->deferred($deferredInit);
237+
238+
return $child;
239+
}
240+
}

0 commit comments

Comments
 (0)