Skip to content

ensureCompiled() in-memory cache causes require_once failure when compiled views are cleared mid-request #96

@erhanurgun

Description

@erhanurgun

Describe the bug

BlazeRuntime::ensureCompiled() uses an in-memory $this->compiled array to skip re-compilation within the same request. However, if compiled view files are deleted mid-request (e.g. via Artisan::call('optimize:clear')), subsequent calls to ensureCompiled() return early based on the stale in-memory cache — without verifying the file still exists on disk. The following require_once then fails with "No such file or directory".

The issue occurs reliably when a Livewire component action triggers cache clearing (e.g. toggling a module that calls optimize:clear) and the component re-renders in the same request.

Component causing the issue

Any Blaze-compiled component used inside a view that re-renders after optimize:clear is called within the same request. In my case, <flux:switch> inside a module settings page:

@foreach($modules as $module)
    @if($module['is_protected'])
        <flux:switch disabled :checked="$module['is_enabled']" />
    @else
        <flux:switch wire:click="toggleModule('{{ $module['name'] }}')" :checked="$module['is_enabled']" />
    @endif
@endforeach

The toggleModule action calls Artisan::call('optimize:clear') which deletes all compiled views, then Livewire re-renders the component in the same request.

Expected behavior

After compiled views are cleared mid-request, ensureCompiled() should detect the missing file and recompile it before require_once executes.

Actual behavior

ensureCompiled() returns immediately at line 48-50 because $this->compiled[$path] is still set from the first render. The compiled file no longer exists on disk, so the require_once on the next line fails:

require_once(.../storage/framework/views/ad784b2e43740a3a648880f501461253.php):
Failed to open stream: No such file or directory

Root cause

In BlazeRuntime::ensureCompiled() (line 46-59):

public function ensureCompiled(string $path, string $compiledPath): void
{
    if (isset($this->compiled[$path])) {
        return; // ← Returns early even if file was deleted
    }

    $this->compiled[$path] = true;

    if (file_exists($compiledPath) && filemtime($path) <= filemtime($compiledPath)) {
        return;
    }

    $this->compiler->compile($path);
}

The $this->compiled guard at line 48 does not verify the compiled file still exists. Once a path is marked as "compiled" during the first render, all subsequent calls skip compilation entirely — even if optimize:clear has deleted the files in between.

Suggested fix

Add a file_exists() check to the in-memory cache guard:

public function ensureCompiled(string $path, string $compiledPath): void
{
    if (isset($this->compiled[$path]) && file_exists($compiledPath)) {
        return;
    }

    $this->compiled[$path] = true;

    if (file_exists($compiledPath) && filemtime($path) <= filemtime($compiledPath)) {
        return;
    }

    $this->compiler->compile($path);
}

This preserves the performance benefit of the in-memory cache (avoiding redundant filemtime calls) while correctly handling the case where files are deleted mid-request.

Steps to reproduce

  1. Create a Livewire component that renders a Blaze-compiled view (e.g. any view using <flux:*> components)
  2. Add an action to the component that calls Artisan::call('optimize:clear') (or view:clear)
  3. After the action, the component re-renders in the same request
  4. The re-render fails with require_once ... Failed to open stream: No such file or directory

Minimal test case:

// In a Livewire component
public function clearAndRerender(): void
{
    Artisan::call('optimize:clear');
    // Component re-renders after this → crash
}

Environment

  • Laravel version: 12.53.0
  • PHP version: 8.4.18
  • Blaze version: 1.0.3

Additional context

This bug surfaces consistently in test suites (both --parallel and sequential) because:

  • Tests start with a clean view cache
  • First render compiles views and populates $this->compiled
  • A Livewire action triggers optimize:clear (e.g. module toggle, settings save)
  • Same test re-renders the component → require_once fails

My current workaround is extending BlazeRuntime and overriding ensureCompiled() with the fix above, then rebinding in AppServiceProvider::register():

$this->app->singleton(BlazeRuntime::class, FixedBlazeRuntime::class);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions