Skip to content

Commit 695d6dc

Browse files
SjorsOtaylorotwell
andauthored
[9.x] Allow model accessors to cache any value (#41673)
* allow caching any value with an accessor * fix accessors caching null values * use shouldCache Co-authored-by: Taylor Otwell <[email protected]>
1 parent f9b1a2a commit 695d6dc

File tree

3 files changed

+122
-7
lines changed

3 files changed

+122
-7
lines changed

src/Illuminate/Database/Eloquent/Casts/Attribute.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ class Attribute
1818
*/
1919
public $set;
2020

21+
/**
22+
* Indicates if caching is enabled for this attribute.
23+
*
24+
* @var bool
25+
*/
26+
public $withCaching = false;
27+
2128
/**
2229
* Indicates if caching of objects is enabled for this attribute.
2330
*
@@ -83,4 +90,16 @@ public function withoutObjectCaching()
8390

8491
return $this;
8592
}
93+
94+
/**
95+
* Enable caching for the attribute.
96+
*
97+
* @return static
98+
*/
99+
public function shouldCache()
100+
{
101+
$this->withCaching = true;
102+
103+
return $this;
104+
}
86105
}

src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ protected function mutateAttribute($key, $value)
630630
*/
631631
protected function mutateAttributeMarkedAttribute($key, $value)
632632
{
633-
if (isset($this->attributeCastCache[$key])) {
633+
if (array_key_exists($key, $this->attributeCastCache)) {
634634
return $this->attributeCastCache[$key];
635635
}
636636

@@ -640,10 +640,10 @@ protected function mutateAttributeMarkedAttribute($key, $value)
640640
return $value;
641641
}, $value, $this->attributes);
642642

643-
if (! is_object($value) || ! $attribute->withObjectCaching) {
644-
unset($this->attributeCastCache[$key]);
645-
} else {
643+
if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
646644
$this->attributeCastCache[$key] = $value;
645+
} else {
646+
unset($this->attributeCastCache[$key]);
647647
}
648648

649649
return $value;
@@ -1023,10 +1023,10 @@ protected function setAttributeMarkedMutatedAttributeValue($key, $value)
10231023
)
10241024
);
10251025

1026-
if (! is_object($value) || ! $attribute->withObjectCaching) {
1027-
unset($this->attributeCastCache[$key]);
1028-
} else {
1026+
if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
10291027
$this->attributeCastCache[$key] = $value;
1028+
} else {
1029+
unset($this->attributeCastCache[$key]);
10301030
}
10311031
}
10321032

tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,70 @@ public function testCastsThatOnlyHaveGetterThatReturnsPrimitivesAreNotCached()
202202
}
203203
}
204204

205+
public function testAttributesCanCacheStrings()
206+
{
207+
$model = new TestEloquentModelWithAttributeCast;
208+
209+
$previous = $model->virtual_string_cached;
210+
211+
$this->assertIsString($previous);
212+
213+
$this->assertSame($previous, $model->virtual_string_cached);
214+
}
215+
216+
public function testAttributesCanCacheBooleans()
217+
{
218+
$model = new TestEloquentModelWithAttributeCast;
219+
220+
$first = $model->virtual_boolean_cached;
221+
222+
$this->assertIsBool($first);
223+
224+
foreach (range(0, 10) as $ignored) {
225+
$this->assertSame($first, $model->virtual_boolean_cached);
226+
}
227+
}
228+
229+
public function testAttributesCanCacheNull()
230+
{
231+
$model = new TestEloquentModelWithAttributeCast;
232+
233+
$this->assertSame(0, $model->virtualNullCalls);
234+
235+
$first = $model->virtual_null_cached;
236+
237+
$this->assertNull($first);
238+
239+
$this->assertSame(1, $model->virtualNullCalls);
240+
241+
foreach (range(0, 10) as $ignored) {
242+
$this->assertSame($first, $model->virtual_null_cached);
243+
}
244+
245+
$this->assertSame(1, $model->virtualNullCalls);
246+
}
247+
248+
public function testAttributesByDefaultDontCacheBooleans()
249+
{
250+
$model = new TestEloquentModelWithAttributeCast;
251+
252+
$first = $model->virtual_boolean;
253+
254+
$this->assertIsBool($first);
255+
256+
foreach (range(0, 50) as $ignored) {
257+
$current = $model->virtual_boolean;
258+
259+
$this->assertIsBool($current);
260+
261+
if ($first !== $current) {
262+
return;
263+
}
264+
}
265+
266+
$this->fail('"virtual_boolean" seems to be cached.');
267+
}
268+
205269
public function testCastsThatOnlyHaveGetterThatReturnsObjectAreCached()
206270
{
207271
$model = new TestEloquentModelWithAttributeCast;
@@ -362,6 +426,38 @@ function () {
362426
);
363427
}
364428

429+
public function virtualStringCached(): Attribute
430+
{
431+
return Attribute::get(function () {
432+
return Str::random(10);
433+
})->shouldCache();
434+
}
435+
436+
public function virtualBooleanCached(): Attribute
437+
{
438+
return Attribute::get(function () {
439+
return (bool) mt_rand(0, 1);
440+
})->shouldCache();
441+
}
442+
443+
public function virtualBoolean(): Attribute
444+
{
445+
return Attribute::get(function () {
446+
return (bool) mt_rand(0, 1);
447+
});
448+
}
449+
450+
public $virtualNullCalls = 0;
451+
452+
public function virtualNullCached(): Attribute
453+
{
454+
return Attribute::get(function () {
455+
$this->virtualNullCalls++;
456+
457+
return null;
458+
})->shouldCache();
459+
}
460+
365461
public function virtualObject(): Attribute
366462
{
367463
return new Attribute(

0 commit comments

Comments
 (0)