From f9c560fcf3af2a2c72a0c722fa024471f6b56330 Mon Sep 17 00:00:00 2001 From: John Koster Date: Sat, 7 Jun 2025 12:07:20 -0500 Subject: [PATCH 1/3] Adds ability to wrap computed callbacks in a Value object --- src/Data/ContainsComputedData.php | 15 ++++++++++++++- src/Data/HasOrigin.php | 7 ++++++- src/Entries/Entry.php | 7 ++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Data/ContainsComputedData.php b/src/Data/ContainsComputedData.php index fbe4f321bd2..5cf9451b915 100644 --- a/src/Data/ContainsComputedData.php +++ b/src/Data/ContainsComputedData.php @@ -2,6 +2,8 @@ namespace Statamic\Data; +use Statamic\Fields\Value; + trait ContainsComputedData { protected $withComputedData = true; @@ -16,12 +18,23 @@ public function computedKeys() } public function computedData() + { + return $this->getComputedData(false); + } + + public function getComputedData($wrapInValue) { if (! method_exists($this, 'getComputedCallbacks')) { return collect(); } - return collect($this->getComputedCallbacks())->map(function ($callback, $field) { + return collect($this->getComputedCallbacks())->map(function ($callback, $field) use ($wrapInValue) { + if ($wrapInValue) { + return new Value(function () use ($field) { + return $this->getComputed($field); + }); + } + return $this->getComputed($field); }); } diff --git a/src/Data/HasOrigin.php b/src/Data/HasOrigin.php index fe045594ee8..5d390cc4b16 100644 --- a/src/Data/HasOrigin.php +++ b/src/Data/HasOrigin.php @@ -32,12 +32,17 @@ public function keys() } public function values() + { + return $this->getValues(false); + } + + public function getValues($wrapComputed) { $originFallbackValues = method_exists($this, 'getOriginFallbackValues') ? $this->getOriginFallbackValues() : collect(); $originValues = $this->hasOrigin() ? $this->origin()->values() : collect(); - $computedData = method_exists($this, 'computedData') ? $this->computedData() : []; + $computedData = method_exists($this, 'getComputedData') ? $this->getComputedData($wrapComputed) : []; return collect() ->merge($originFallbackValues) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index e83ed2d3b6f..b3d4cd67846 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -900,7 +900,12 @@ public function route() public function routeData() { - $data = $this->values()->merge([ + // This uses the `getValues(true)` method instead of values() + // This is so we can wrap computed fields in Value so we + // can delay their execution. If the computed value + // triggers the routeData() method, we will end + // up in an infinite loop that is not fun. + $data = $this->getValues(true)->merge([ 'id' => $this->id(), 'slug' => $this->slug(), 'published' => $this->published(), From b990d7d64055aa3eeba4f0a975108eff780439a4 Mon Sep 17 00:00:00 2001 From: John Koster Date: Sat, 7 Jun 2025 13:55:36 -0500 Subject: [PATCH 2/3] Add test coverage --- tests/Data/Entries/EntryTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index d5f788782db..19c3846cef4 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -2615,4 +2615,28 @@ public function it_clones_internal_collections() $this->assertEquals('A', $entry->getSupplement('bar')); $this->assertEquals('B', $clone->getSupplement('bar')); } + + #[Test] + public function using_route_data_in_computed_props_does_not_cause_infinite_loops() + { + $collection = + \Statamic\Facades\Collection::make('pages') + ->routes('{slug}') + ->structureContents(['root' => true]) // We need to be in a structure to create the infinite loop condition. + ->save(); + + \Statamic\Facades\Collection::computed('pages', 'custom', function ($entry) { + return 'Custom: '.$entry->uri(); + }); + + EntryFactory::id('entry-id') + ->slug('entry-slug') + ->collection('pages') + ->create(); + + Blink::store('entry-uris')->flush(); + + $entry = Facades\Entry::find('entry-id'); + $this->assertSame('Custom: /', $entry->custom); + } } From 9dfd61ea02cb3de720a68531462ce15afcac493f Mon Sep 17 00:00:00 2001 From: John Koster Date: Sat, 14 Jun 2025 15:49:34 -0500 Subject: [PATCH 3/3] Cleanup --- src/Data/ContainsComputedData.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Data/ContainsComputedData.php b/src/Data/ContainsComputedData.php index 5cf9451b915..cf72d60bd2e 100644 --- a/src/Data/ContainsComputedData.php +++ b/src/Data/ContainsComputedData.php @@ -28,15 +28,11 @@ public function getComputedData($wrapInValue) return collect(); } - return collect($this->getComputedCallbacks())->map(function ($callback, $field) use ($wrapInValue) { - if ($wrapInValue) { - return new Value(function () use ($field) { - return $this->getComputed($field); - }); - } - - return $this->getComputed($field); - }); + return collect($this->getComputedCallbacks()) + ->map(fn ($callback, $field) => $wrapInValue ? + new Value(fn () => $this->getComputed($field)) : + $this->getComputed($field) + ); } public function getComputed($key)