在自定义的src目录内自动发现事件监听器

后端开发 2026-07-08

在Laravel 12项目中,我在 src 目录下的监听器自动发现遇到了问题。

应用结构大致如下:

- app (nothing in here)
- bootstrap
-- app.php
- src
-- Modules
--- ModuleA
---- Listeners
----- HandleSomeEvent (namespace: Modules\ModuleA\Listeners)

基本上,我想在 src/Modules/ModuleA/Listeners 路径(命名空间:Modules/ModuleA/Listeners)注册一个 HandleSomeEvent 监听器。为此,在 bootstrap/app.php 中,我添加了如下内容:

->withEvents(discover: [
   __DIR__ . '/../src/Modules/ModuleA/Listeners',
])

php artisan event:list 依然无法显示 HandleSomeEvent 监听器。只有当监听器位于 app/Listeners 时才起作用。有趣的是,当我把监听器的目录改到 app 文件夹,例如:

->withEvents(discover: [
   __DIR__.'/../app/OtherListeners',
])

并且 HandleSomeEvent 位于此目录时,它仍然会被注册。

我不确定这是否与问题相关,但 composer.json 的自动加载(autoload)部分看起来是这样的:

"autoload": {
   "psr-4": {
       "App\\": "app/",
       "Modules\\": "src/Modules/",
   }
},

总之,我无法让监听器的自动发现功能在 src 目录中工作,而在 app 目录则工作正常。

解决方案

我在最近的一个项目中也遇到了完全相同的问题。
问题: 在底层,Laravel使用 Illuminate\Foundation\Events\DiscoverEvents::classFromFile() 方法来猜测刚刚发现的监听器的类名。默认情况下,该方法被硬编码为假设所有被发现的监听器都属于应用程序的根命名空间(通常是 App\)。当自动加载器扫描 src/Modules/ModuleA/Listeners/HandleSomeEvent.php 时,它会尝试将其映射到 App\ 命名空间。因为得到的猜测与实际的 Modules\ModuleA\Listeners\HandleSomeEvent 类不匹配,Reflection会失败,Laravel会悄无声息地忽略该文件,而不会抛出错误。

解决方案: 自定义 classFromFile() 方法:将以下代码放入 App\Providers\AppServiceProviderboot() 方法中:

DiscoverEvents::guessClassNamesUsing(function (\SplFileInfo $file, $basePath) {
    $path = $file->getRealPath();
    $modulesPath = DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Modules';

    // intercept custom Modules directory
    if (str_contains($path, $modulesPath)) {
        $relativePath = Str::after($path, DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR);
        $classPath = Str::replaceLast('.php', '', $relativePath);

        // returns exactly: Modules\ModuleA\Listeners\HandleSomeEvent
        return str_replace(DIRECTORY_SEPARATOR, '\\', $classPath); 

    }

    // Laravel's default behavior for standard app/ directory
    $class = trim(Str::replaceFirst($basePath, '', $path), DIRECTORY_SEPARATOR);

    return app()->getNamespace() . str_replace(
        DIRECTORY_SEPARATOR,
       '\\',
        class_basename($basePath) . '\\' . Str::replaceLast('.php', '', $class)
    );
});

对服务提供者进行修改后,运行:

php artisan optimize:clear
php artisan event:list // shoud show properly registered HandleSomeEvent
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章