Skip to content

Commit cea87be

Browse files
pizkaztaylorotwell
andauthored
Update compiled views only if they actually changed (#55450)
* Update compiled views only if they actually changed When running in a read-only filesystem container with views pre-cached at build time, Laravel checks if the cached view's timestamp is older than the source's to decide when to recompile. This is fine as long as we do not make OCI image builds reproducible. If we do however, the timestamps match (because timestamps of all files are set to 01.01.1970), so Laravel recompiles the view, tries to write to a read-only filesystem and the container crashes. This patch computes SHA256 hashes over existing and newly compiled file contents, compares those and writes only if the file actually changes. * Update BladeCompiler.php --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 064ed2e commit cea87be

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

src/Illuminate/View/Compilers/BladeCompiler.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,17 @@ public function compile($path = null)
193193
$compiledPath = $this->getCompiledPath($this->getPath())
194194
);
195195

196-
$this->files->put($compiledPath, $contents);
196+
if (! $this->files->exists($compiledPath)) {
197+
$this->files->put($compiledPath, $contents);
198+
199+
return;
200+
}
201+
202+
$compiledHash = $this->files->hash($compiledPath, 'sha256');
203+
204+
if ($compiledHash !== hash('sha256', $contents)) {
205+
$this->files->put($compiledPath, $contents);
206+
}
197207
}
198208
}
199209

tests/View/ViewBladeCompilerTest.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,42 @@ public function testCompileCompilesFileAndReturnsContents()
6666
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
6767
$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
6868
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
69+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false);
6970
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
7071
$compiler->compile('foo');
7172
}
7273

7374
public function testCompileCompilesFileAndReturnsContentsCreatingDirectory()
7475
{
76+
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
77+
$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
78+
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
79+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false);
80+
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
81+
$compiler->compile('foo');
82+
}
83+
84+
public function testCompileUpdatesCacheIfChanged()
85+
{
86+
$compiledPath = __DIR__.'/'.hash('xxh128', 'v2foo').'.php';
87+
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
88+
$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
89+
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
90+
$files->shouldReceive('exists')->once()->with($compiledPath)->andReturn(true);
91+
$files->shouldReceive('hash')->once()->with($compiledPath, 'sha256')->andReturn(hash('sha256', 'outdated content'));
92+
$files->shouldReceive('put')->once()->with($compiledPath, 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
93+
$compiler->compile('foo');
94+
}
95+
96+
public function testCompileKeepsCacheIfUnchanged()
97+
{
98+
$compiledPath = __DIR__.'/'.hash('xxh128', 'v2foo').'.php';
7599
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
76100
$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
77101
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(false);
78102
$files->shouldReceive('makeDirectory')->once()->with(__DIR__, 0777, true, true);
79-
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
103+
$files->shouldReceive('exists')->once()->with($compiledPath)->andReturn(true);
104+
$files->shouldReceive('hash')->once()->with($compiledPath, 'sha256')->andReturn(hash('sha256', 'Hello World<?php /**PATH foo ENDPATH**/ ?>'));
80105
$compiler->compile('foo');
81106
}
82107

@@ -85,6 +110,7 @@ public function testCompileCompilesAndGetThePath()
85110
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
86111
$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
87112
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
113+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false);
88114
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
89115
$compiler->compile('foo');
90116
$this->assertSame('foo', $compiler->getPath());
@@ -102,6 +128,7 @@ public function testCompileWithPathSetBefore()
102128
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
103129
$files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
104130
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
131+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false);
105132
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
106133
// set path before compilation
107134
$compiler->setPath('foo');
@@ -132,6 +159,7 @@ public function testIncludePathToTemplate($content, $compiled)
132159
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
133160
$files->shouldReceive('get')->once()->with('foo')->andReturn($content);
134161
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
162+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php')->andReturn(false);
135163
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2foo').'.php', $compiled);
136164

137165
$compiler->compile('foo');
@@ -187,6 +215,7 @@ public function testDontIncludeEmptyPath()
187215
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
188216
$files->shouldReceive('get')->once()->with('')->andReturn('Hello World');
189217
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
218+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php')->andReturn(false);
190219
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World');
191220
$compiler->setPath('');
192221
$compiler->compile();
@@ -197,6 +226,7 @@ public function testDontIncludeNullPath()
197226
$compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
198227
$files->shouldReceive('get')->once()->with(null)->andReturn('Hello World');
199228
$files->shouldReceive('exists')->once()->with(__DIR__)->andReturn(true);
229+
$files->shouldReceive('exists')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php')->andReturn(false);
200230
$files->shouldReceive('put')->once()->with(__DIR__.'/'.hash('xxh128', 'v2').'.php', 'Hello World');
201231
$compiler->setPath(null);
202232
$compiler->compile();

0 commit comments

Comments
 (0)