跳转到主要内容

什么是 GeneratorCommand

Illuminate\Console\GeneratorCommand 是 Laravel 中所有代码生成命令的抽象基类。make:modelmake:controllermake:request 等都继承自它。 如果你的包提供了独立的 make:xxx 命令,用户就不必手动创建类文件,而只需运行 php artisan make:handler OrderHandler,即可在正确的命名空间中生成文件。
GeneratorCommand 在官方文档中几乎没有涉及,只有直接阅读框架源码才能了解。它是典型的进阶主题。

最小实现

继承 GeneratorCommand 的类只需实现 getStub() 方法。其他属性是可选的,但实际中通常都会一起配好。
成员类型作用
$name属性命令名(例如 make:handler
$description属性命令说明
$type属性生成物类型名,会显示在成功消息中(例如 Handler
getStub()方法(必需)返回 stub 文件路径
getDefaultNamespace()方法控制生成目标的默认命名空间
下面是实现 make:handler 命令的示例。
<?php

namespace Vendor\Package\Console\Commands;

use Illuminate\Console\GeneratorCommand;

class HandlerMakeCommand extends GeneratorCommand
{
    protected $name = 'make:handler';

    protected $description = 'Create a new handler class';

    protected $type = 'Handler';

    protected function getStub(): string
    {
        return __DIR__.'/stubs/handler.stub';
    }

    protected function getDefaultNamespace($rootNamespace): string
    {
        return $rootNamespace.'\Handlers';
    }
}
设置好 getDefaultNamespace() 后,运行 php artisan make:handler OrderHandler 会在 app/Handlers/OrderHandler.php 生成 App\Handlers\OrderHandler 类。

创建 stub 文件

将 stub 文件放到 getStub() 返回的路径。stub 是模板 PHP 文件,其中的占位符会被替换为命名空间与类名。
src
Console
Commands
HandlerMakeCommand.php
stubs
handler.stub
stub 文件示例:
handler.stub
<?php

namespace {{ namespace }};

class {{ class }}
{
    public function handle(): void
    {
        //
    }
}
GeneratorCommand 会自动替换 stub 中的以下占位符。
占位符替换后的值备用形式
{{ namespace }}生成类的命名空间DummyNamespace
{{ class }}生成的类名DummyClass
{{ rootNamespace }}应用的根命名空间DummyRootNamespace
{{ namespace }}DummyNamespace 的效果相同。Laravel 内置的 stub 同时使用了两种形式,但新建时推荐使用 {{ namespace }} 形式。

让 stub 支持自定义

要为用户提供可覆盖 stub 的机制,请使用 resolveStubPath() 模式。先在 Service Provider 的 boot() 中发布 stub。
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->publishes([
            __DIR__.'/../Console/Commands/stubs' => base_path('stubs'),
        ], 'stubs');
    }
}
然后在 getStub() 中优先使用用户自定义的 stub。
protected function getStub(): string
{
    return $this->resolveStubPath('/stubs/handler.stub');
}

protected function resolveStubPath(string $stub): string
{
    return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
        ? $customPath
        : __DIR__.$stub;
}
用户执行 php artisan vendor:publish --tag=stubs 之后,可以编辑项目根目录下的 stubs/handler.stub 来修改模板。
resolveStubPath() 也是 Laravel 内置 RequestMakeCommand 中使用的模式。发布你的包时建议采用这一模式。

注册到 Service Provider

命令在 Service Provider 的 boot() 方法中注册。通过 runningInConsole() 判断,可以避免 Web 请求时不必要的加载。
<?php

namespace Vendor\Package;

use Illuminate\Support\ServiceProvider;
use Vendor\Package\Console\Commands\HandlerMakeCommand;

class PackageServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                HandlerMakeCommand::class,
            ]);

            $this->publishes([
                __DIR__.'/../Console/Commands/stubs' => base_path('stubs'),
            ], 'stubs');
        }
    }
}
在包的 composer.json 中配置 extra.laravel,用户就不必手动注册 Service Provider。
"extra": {
    "laravel": {
        "providers": [
            "Vendor\\Package\\PackageServiceProvider"
        ]
    }
}

应用示例

以下列举了 GeneratorCommand 大显身手的几个场景。
生成继承自定义基类(包含认证检查、自定义验证逻辑)的 Request 类的命令。在 getDefaultNamespace() 返回 App\Http\Requests
生成 Data Transfer Object 样板的命令。准备包含 readonly 属性、from() 工厂方法的 stub。
生成单一职责的 Action 类的命令。在 App\Actions 命名空间下生成拥有 execute() 方法的类。
make:livewire 命令由继承 GeneratorCommandMakeCommand 类实现。为了同时生成组件类与 Blade 视图两个文件,它覆写了 handle()

测试

使用 Orchestra Testbench 测试命令是否正确生成文件。
<?php

namespace Tests\Feature\Console;

use Illuminate\Support\Facades\File;
use Orchestra\Testbench\TestCase;
use Vendor\Package\PackageServiceProvider;

class HandlerMakeCommandTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [PackageServiceProvider::class];
    }

    protected function tearDown(): void
    {
        File::deleteDirectory(app_path('Handlers'));

        parent::tearDown();
    }

    public function test_make_handler_creates_file(): void
    {
        $path = app_path('Handlers/OrderHandler.php');

        $this->artisan('make:handler', ['name' => 'OrderHandler'])
            ->assertSuccessful();

        $this->assertFileExists($path);
        $this->assertStringContainsString('namespace App\Handlers;', File::get($path));
        $this->assertStringContainsString('class OrderHandler', File::get($path));
    }

    public function test_make_handler_does_not_overwrite_existing_file(): void
    {
        $path = app_path('Handlers/OrderHandler.php');
        File::ensureDirectoryExists(dirname($path));
        File::put($path, '<?php // existing');

        $this->artisan('make:handler', ['name' => 'OrderHandler'])
            ->assertFailed();
    }
}
Generator 命令会写入实际的文件系统。请务必在 tearDown() 中清理,确保即便测试中途失败也能可靠执行。

相关页面

Laravel 包开发

了解基于 Service Provider 的包开发基础。

使用 Orchestra Testbench 测试 Laravel 包

了解如何构建包的测试基础设施。
最后修改于 2026年7月13日