Skip to content

Livewire v4 components fail to resolve under Apache + OPcache (ComponentNotFoundException) #93

Description

@MichaelWalker-git

Description

On Livewire v4, CommentionsServiceProvider::packageBooted() uses Livewire::addNamespace() for component discovery. This works in development (PHP CLI, validate_timestamps=1) but fails in production under Apache mod_php + OPcache with validate_timestamps=0.

Root cause

Livewire v4's Finder::resolveClassComponentClassName() handles namespaced components (commentions::*) in a dedicated branch that:

  1. Generates a class name from convention (Kirschbaum\Commentions\Livewire\Comments)
  2. Calls class_exists($class)
  3. If class_exists() returns false, returns null immediately — it never falls through to check classComponents or any other resolution path

Under Apache + OPcache with validate_timestamps=0 and Composer's optimize-autoloader, class_exists() can return false for vendor classes that weren't in the OPcache file cache at build time. This causes a ComponentNotFoundException for all Commentions Livewire components.

The v3 branch uses explicit Livewire::component() calls which bypass class_exists() entirely — that's why v3 never had this issue.

Relevant Livewire v4 code (Finder::resolveClassComponentClassName)

public function resolveClassComponentClassName($name): ?string
{
    [$namespace, $componentName] = $this->parseNamespaceAndName($name);

    if ($namespace !== null) {
        if (isset($this->classNamespaces[$namespace]['classNamespace'])) {
            $class = $this->generateClassFromName($componentName, [$this->classNamespaces[$namespace]['classNamespace']]);
            if (class_exists($class)) {
                return $class;
            }
        }
        return null;  // ← never falls through to classComponents
    }

    // classComponents check is unreachable for namespaced components
    if (isset($this->classComponents[$name])) {
        return $this->classComponents[$name];
    }
    // ...
}

Suggested fix

Add Livewire::resolveMissingComponent() as a fallback alongside addNamespace(). This is the only hook in Livewire v4's Factory::resolveComponentNameAndClass() that runs after all resolution paths fail:

if ($this->isLivewireV4()) {
    Livewire::addNamespace('commentions', classNamespace: __NAMESPACE__ . '\\Livewire');

    // Explicit fallback: addNamespace() relies on class_exists() which can fail
    // under Apache + OPcache (validate_timestamps=0) when vendor classes aren't
    // in the OPcache file cache. resolveMissingComponent runs after all other
    // resolution paths fail, ensuring components are always found.
    Livewire::resolveMissingComponent(fn (string $name) => match ($name) {
        'commentions::comment' => Comment::class,
        'commentions::comment-list' => CommentList::class,
        'commentions::comments' => Comments::class,
        'commentions::reactions' => Reactions::class,
        'commentions::subscription-sidebar' => SubscriptionSidebar::class,
        default => null,
    });
} else {
    // ... existing v3 registrations
}

Environment

  • Commentions: v0.7.10
  • Livewire: v4
  • PHP: 8.4, Apache mod_php
  • OPcache: validate_timestamps=0, Composer optimize-autoloader: true
  • Deployment: Docker on AWS ECS Fargate

Workaround

Add the resolveMissingComponent() fallback in your application's AppServiceProvider::boot() method until this is fixed upstream.

Happy to submit a PR if you'd like.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions