From ce00dd9dbc96711c923e625dd0401753764675b2 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Sat, 7 Nov 2026 02:12:19 +0000 Subject: [PATCH] Keep parsed blueprint and fieldset YAML between requests --- src/Fields/BlueprintRepository.php | 3 +- src/Fields/FieldsetRepository.php | 4 +- src/Fields/ParsedYamlFiles.php | 42 +++++++++++++ tests/Fields/ParsedYamlFilesTest.php | 90 ++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 src/Fields/ParsedYamlFiles.php create mode 100644 tests/Fields/ParsedYamlFilesTest.php diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php index 2db699c46a2..62e357902ed 100644 --- a/src/Fields/BlueprintRepository.php +++ b/src/Fields/BlueprintRepository.php @@ -9,7 +9,6 @@ use Statamic\Facades\Blink; use Statamic\Facades\File; use Statamic\Facades\Path; -use Statamic\Facades\YAML; use Statamic\Support\Arr; use Statamic\Support\Str; @@ -326,7 +325,7 @@ protected function makeBlueprintFromFile($path, $namespace = null) $handle = Str::of($path)->afterLast('/')->before('.'); } - $contents = YAML::file($path)->parse(); + $contents = ParsedYamlFiles::parse($path); return $this->make($handle) ->setHidden(Arr::pull($contents, 'hide')) diff --git a/src/Fields/FieldsetRepository.php b/src/Fields/FieldsetRepository.php index 7a3867b63a9..2faf5ecb866 100644 --- a/src/Fields/FieldsetRepository.php +++ b/src/Fields/FieldsetRepository.php @@ -46,7 +46,7 @@ public function find(string $handle): ?Fieldset $fieldset = $this ->make($handle) ->initialPath($path) - ->setContents(YAML::file($path)->parse()); + ->setContents(ParsedYamlFiles::parse($path)); $this->fieldsets[$handle] = $fieldset; @@ -228,7 +228,7 @@ protected function getFieldsetsByDirectory(string $directory, ?string $namespace return $this ->make($handle) ->initialPath($file) - ->setContents(YAML::file($file)->parse()); + ->setContents(ParsedYamlFiles::parse($file)); }) ->keyBy->handle() ->collect(); diff --git a/src/Fields/ParsedYamlFiles.php b/src/Fields/ParsedYamlFiles.php new file mode 100644 index 00000000000..12a20ed4ca1 --- /dev/null +++ b/src/Fields/ParsedYamlFiles.php @@ -0,0 +1,42 @@ +get($key); + + if (is_array($cached) && ($cached['hash'] ?? null) === $hash) { + return $cached['data']; + } + + $data = YAML::file($path)->parse($contents); + + if (is_array($data)) { + $cache->forever($key, ['hash' => $hash, 'data' => $data]); + } + + return $data; + } +} diff --git a/tests/Fields/ParsedYamlFilesTest.php b/tests/Fields/ParsedYamlFilesTest.php new file mode 100644 index 00000000000..ddef22d875a --- /dev/null +++ b/tests/Fields/ParsedYamlFilesTest.php @@ -0,0 +1,90 @@ +dir = sys_get_temp_dir().'/statamic-parsed-yaml-'.uniqid(); + mkdir($this->dir); + } + + public function tearDown(): void + { + (new Filesystem)->deleteDirectory($this->dir); + + parent::tearDown(); + } + + #[Test] + public function it_parses_a_file() + { + file_put_contents($path = $this->dir.'/test.yaml', "title: Test\nfields:\n - handle: one\n"); + + $this->assertSame(['title' => 'Test', 'fields' => [['handle' => 'one']]], ParsedYamlFiles::parse($path)); + } + + #[Test] + public function it_reuses_what_it_parsed_while_the_file_is_unchanged() + { + file_put_contents($path = $this->dir.'/test.yaml', 'title: Test'); + + ParsedYamlFiles::parse($path); + + // Swap what's stored for something parsing the file couldn't give, to show it's what comes back. + $key = 'statamic.parsed-yaml::'.md5(Str::after($path, base_path())); + Stache::cacheStore()->forever($key, ['data' => ['title' => 'From the cache']] + Stache::cacheStore()->get($key)); + + $this->assertSame(['title' => 'From the cache'], ParsedYamlFiles::parse($path)); + } + + #[Test] + public function it_parses_the_file_again_when_it_changes() + { + file_put_contents($path = $this->dir.'/test.yaml', 'title: Before'); + $this->assertSame(['title' => 'Before'], ParsedYamlFiles::parse($path)); + + // Same length and modification time, different contents. + $mtime = filemtime($path); + file_put_contents($path, 'title: Behind'); + touch($path, $mtime); + + $this->assertSame(['title' => 'Behind'], ParsedYamlFiles::parse($path)); + } + + #[Test] + public function it_doesnt_store_anything_for_a_file_that_doesnt_parse() + { + file_put_contents($path = $this->dir.'/test.yaml', "title: [unclosed\n"); + + try { + ParsedYamlFiles::parse($path); + $this->fail('The file should not have parsed.'); + } catch (ParseException $e) { + $this->assertSame($path, $e->getFile()); + } + + $this->assertNull(Stache::cacheStore()->get('statamic.parsed-yaml::'.md5(Str::after($path, base_path())))); + } + + #[Test] + public function an_empty_file_is_an_empty_array() + { + file_put_contents($path = $this->dir.'/test.yaml', ''); + + $this->assertSame([], ParsedYamlFiles::parse($path)); + } +}