diff --git a/src/Fieldtypes/Replicator.php b/src/Fieldtypes/Replicator.php index 816098c086f..73e0bc0f48b 100644 --- a/src/Fieldtypes/Replicator.php +++ b/src/Fieldtypes/Replicator.php @@ -7,6 +7,7 @@ use Statamic\Data\NestedFieldUpdater; use Statamic\Facades\Blink; use Statamic\Facades\GraphQL; +use Statamic\Fields\Field; use Statamic\Fields\Fields; use Statamic\Fields\Fieldtype; use Statamic\Fields\Values; @@ -25,6 +26,14 @@ class Replicator extends Fieldtype protected $categories = ['structured']; protected $keywords = ['builder', 'page builder', 'content']; protected $rules = ['array']; + protected ?string $flattenedSetsConfigBlinkKey = null; + + public function setField(Field $field) + { + $this->flattenedSetsConfigBlinkKey = null; + + return parent::setField($field); + } protected function configFieldItems(): array { @@ -276,9 +285,12 @@ private function shouldProcessNewValues(): bool public function flattenedSetsConfig() { - $blink = md5($this->field?->handle().json_encode($this->field?->config())); + // Re-serializing the config on every call is expensive on fields with lots + // of sets, and this gets hit repeatedly by fields(), preload() and augment(). + // Invalidated in setField(), which is the only thing that replaces the field. + $this->flattenedSetsConfigBlinkKey ??= md5($this->field?->handle().json_encode($this->field?->config())); - return Blink::once($blink, function () { + return Blink::once($this->flattenedSetsConfigBlinkKey, function () { $sets = collect($this->config('sets')); // If the first set doesn't have a nested "set" key, it would be the legacy format. diff --git a/tests/Fieldtypes/BardTest.php b/tests/Fieldtypes/BardTest.php index 100eb745c25..48ceab677c4 100644 --- a/tests/Fieldtypes/BardTest.php +++ b/tests/Fieldtypes/BardTest.php @@ -1577,6 +1577,46 @@ private function bard($config = []) return (new Bard)->setField(new Field('test', array_merge(['type' => 'bard', 'sets' => ['one' => []]], $config))); } + #[Test] + public function it_gets_flattened_sets_config_for_each_field_it_is_given() + { + $fieldtype = new Bard; + + $fieldtype->setField($this->fieldWithSet('alpha')); + $this->assertSame(['alpha'], $fieldtype->flattenedSetsConfig()->keys()->all()); + + $fieldtype->setField($this->fieldWithSet('bravo')); + $this->assertSame(['bravo'], $fieldtype->flattenedSetsConfig()->keys()->all()); + } + + #[Test] + public function it_gets_flattened_sets_config_when_the_field_is_replaced_without_being_read() + { + $fieldtype = new Bard; + + $fieldtype->setField($this->fieldWithSet('alpha')); + $fieldtype->flattenedSetsConfig(); + + $fieldtype->setField($this->fieldWithSet('bravo')); + $fieldtype->setField($this->fieldWithSet('charlie')); + + $this->assertSame(['charlie'], $fieldtype->flattenedSetsConfig()->keys()->all()); + } + + private function fieldWithSet(string $set) + { + return new Field($set.'_field', [ + 'type' => 'bard', + 'sets' => [ + 'main' => [ + 'sets' => [ + $set => ['fields' => [['handle' => 'words', 'field' => ['type' => 'text']]]], + ], + ], + ], + ]); + } + public static function groupedSetsProvider() { return [ diff --git a/tests/Fieldtypes/ReplicatorTest.php b/tests/Fieldtypes/ReplicatorTest.php index ea15c10c54c..a1a1ac64273 100644 --- a/tests/Fieldtypes/ReplicatorTest.php +++ b/tests/Fieldtypes/ReplicatorTest.php @@ -1232,6 +1232,61 @@ public function it_has_button_label_config() $this->assertSame('Add Set', $configFields['button_label']['placeholder']); } + #[Test] + public function it_gets_flattened_sets_config_for_each_field_it_is_given() + { + $fieldtype = new Replicator; + + $fieldtype->setField($this->fieldWithSet('alpha')); + $this->assertSame(['alpha'], $fieldtype->flattenedSetsConfig()->keys()->all()); + + $fieldtype->setField($this->fieldWithSet('bravo')); + $this->assertSame(['bravo'], $fieldtype->flattenedSetsConfig()->keys()->all()); + } + + #[Test] + public function it_gets_flattened_sets_config_when_the_field_is_replaced_without_being_read() + { + // The field is cloned into the fieldtype, so replacing it frees the previous + // clone and its spl_object_id becomes available to the next one. + $fieldtype = new Replicator; + + $fieldtype->setField($this->fieldWithSet('alpha')); + $fieldtype->flattenedSetsConfig(); + + $fieldtype->setField($this->fieldWithSet('bravo')); + $fieldtype->setField($this->fieldWithSet('charlie')); + + $this->assertSame(['charlie'], $fieldtype->flattenedSetsConfig()->keys()->all()); + } + + #[Test] + public function it_doesnt_use_another_fields_flattened_sets_config_when_cloned() + { + $fieldtype = new Replicator; + $fieldtype->setField($this->fieldWithSet('alpha')); + $fieldtype->flattenedSetsConfig(); + + $clone = (clone $fieldtype)->setField($this->fieldWithSet('bravo')); + + $this->assertSame(['bravo'], $clone->flattenedSetsConfig()->keys()->all()); + $this->assertSame(['alpha'], $fieldtype->flattenedSetsConfig()->keys()->all()); + } + + private function fieldWithSet(string $set) + { + return new Field($set.'_field', [ + 'type' => 'replicator', + 'sets' => [ + 'main' => [ + 'sets' => [ + $set => ['fields' => [['handle' => 'words', 'field' => ['type' => 'text']]]], + ], + ], + ], + ]); + } + public static function groupedSetsProvider() { return [