Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/Fields/BlueprintRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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'))
Expand Down
4 changes: 2 additions & 2 deletions src/Fields/FieldsetRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
42 changes: 42 additions & 0 deletions src/Fields/ParsedYamlFiles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Statamic\Fields;

use Statamic\Facades\File;
use Statamic\Facades\Stache;
use Statamic\Facades\YAML;
use Statamic\Support\Str;

/**
* Parsed blueprint and fieldset YAML, kept in the Stache's cache store
* between requests. Each file's entry is stored with a hash of the
* contents it was parsed from, so a changed file is parsed again.
*
* @internal
*/
class ParsedYamlFiles
{
public static function parse(string $path)
{
$contents = File::get($path);

// Keyed on the path within the app, so a deploy into a new release directory reuses the entries.
$key = 'statamic.parsed-yaml::'.md5(Str::after($path, base_path()));
$hash = hash('xxh128', (string) $contents);
$cache = Stache::cacheStore();

$cached = $cache->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;
}
}
90 changes: 90 additions & 0 deletions tests/Fields/ParsedYamlFilesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Tests\Fields;

use Illuminate\Filesystem\Filesystem;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Stache;
use Statamic\Fields\ParsedYamlFiles;
use Statamic\Support\Str;
use Statamic\Yaml\ParseException;
use Tests\TestCase;

class ParsedYamlFilesTest extends TestCase
{
private $dir;

protected function setUp(): void
{
parent::setUp();

$this->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));
}
}
Loading