自定义 Laravel 的默认 Artisan 命令

Laravel 自带功能强大的Artisan命令行 , 可以给开发带来很多的便利 . 但有一些命令行的命令功能太强大 , 例如migration , 可以直接销毁数据库 . 尽管默认了运行环境( 在生产环境下只有使用 '--force' 才能运行 ) , 仍然会带来意想不到的安全隐患 .

希望对项目的控制权更强一些 , 自然想要有一个可以自己配置的 artisan 命令行 . 这该怎么做呢 ?

为了解决这个问题 , 首先花时间弄明白了 laravel 命令是如何加载的 .

以Migration为例 . 所有的migration相关命令 , 其实是由 Illuminate\Database\MigrationServiceProvider 加载的 . 加载代码如下:

    /**
     * Register all of the migration commands.
     *
     * @return void
     */
    protected function registerCommands()
    {
        $commands = ['Migrate', 'Rollback', 'Reset', 'Refresh', 'Install', 'Make', 'Status'];

        // We'll simply spin through the list of commands that are migration related
        // and register each one of them with an application container. They will
        // be resolved in the Artisan start file and registered on the console.
        foreach ($commands as $command) {
            $this->{'register'.$command.'Command'}();
        }

        // Once the commands are registered in the application IoC container we will
        // register them with the Artisan start event so that these are available
        // when the Artisan application actually starts up and is getting used.
        $this->commands(
            'command.migrate', 'command.migrate.make',
            'command.migrate.install', 'command.migrate.rollback',
            'command.migrate.reset', 'command.migrate.refresh',
            'command.migrate.status'
        );
    }

    /**
     * Register the package's custom Artisan commands.
     *
     * @param  array|mixed  $commands
     * @return void
     */
    public function commands($commands)
    {
        $commands = is_array($commands) ? $commands : func_get_args();

        // To register the commands with Artisan, we will grab each of the arguments
        // passed into the method and listen for Artisan "start" event which will
        // give us the Artisan console instance which we will give commands to.
        $events = $this->app['events'];

        $events->listen(ArtisanStarting::class, function ($event) use ($commands) {
            $event->artisan->resolveCommands($commands);
        });
    }

    /**
     * Register the "migrate" migration command.
     *
     * @return void
     */
    protected function registerMigrateCommand()
    {
        $this->app->singleton('command.migrate', function ($app) {
            return new MigrateCommand($app['migrator']);
        });
    }

简单来说 , laravel 加载命令行的流程有这么几步:

  1. bootstrap/app.php中将IoC容器初始化 , artisan调用App\Console\Kernerl
  2. Illuminate\Foundation\Console\Kernel 启动时自动加载artisan相关的provider
  3. 命令行相关的provider,为所有预加载的命令行创建单例, 例如registerMigrateCommand()
  4. 命令行相关的provider,会把注册命令的闭包作为监听者 , 监听Events\ArtisanStarting 事件
  5. laravel 执行artisan的时候 ,IoC容器会自动生成Illuminate\Console\Application 单例 , 用来执行命令
  6. 实例化Illuminate\Console\Application 时会触发事件 $events->fire(new Events\ArtisanStarting($this));
  7. 触发事件后 , Illuminate\Console\Application 先通过监听者加载所有命令 , 然后再执行命令

看源代码 , Illuminate\Console\Application 只能加载命令行 , 不能移除已经加载的命令行 . 所以要修改laravel默认的命令行 , 只能通过Provider加载的环节进行修改 .

那命令行相关的provider是怎么加载的呢? 通过代码追踪后可见 , 关键在于config/app.php 中默认加载的provider , 其中之一 Illuminate\Foundation\Providers\ConsoleSupportServiceProvider . 打开来会发现有个数组:

/**
 * The provider class names.
 *
 * @var array
 */
protected $providers = [
    'Illuminate\Foundation\Providers\ArtisanServiceProvider',
    'Illuminate\Console\ScheduleServiceProvider',
    'Illuminate\Database\MigrationServiceProvider',
    'Illuminate\Database\SeedServiceProvider',
    'Illuminate\Foundation\Providers\ComposerServiceProvider',
    'Illuminate\Queue\ConsoleServiceProvider',
];

显然这个数组负责加载所有命令行相关的Provider . 在这个数组里添加或者移除Provider , 就能添加或删除一些默认的命令行.

再进一步看Illuminate\Foundation\Providers\ArtisanServiceProvider , 有这样的属性:

/**
 * The commands to be registered.
 *
 * @var array
 */
protected $commands = [
    'ClearCompiled' => 'command.clear-compiled',
    'ClearResets' => 'command.auth.resets.clear',
    'ConfigCache' => 'command.config.cache',
    'ConfigClear' => 'command.config.clear',
    'Down' => 'command.down',
    'Environment' => 'command.environment',
    'KeyGenerate' => 'command.key.generate',
    'Optimize' => 'command.optimize',
    'RouteCache' => 'command.route.cache',
    'RouteClear' => 'command.route.clear',
    'RouteList' => 'command.route.list',
    'Tinker' => 'command.tinker',
    'Up' => 'command.up',
    'ViewClear' => 'command.view.clear',
];

/**
 * The commands to be registered.
 *
 * @var array
 */
protected $devCommands = [
    'AppName' => 'command.app.name',
    'AuthMake' => 'command.auth.make',
    'CacheTable' => 'command.cache.table',
    'ConsoleMake' => 'command.console.make',
    'ControllerMake' => 'command.controller.make',
    'EventGenerate' => 'command.event.generate',
    'EventMake' => 'command.event.make',
    'JobMake' => 'command.job.make',
    'ListenerMake' => 'command.listener.make',
    'MiddlewareMake' => 'command.middleware.make',
    'ModelMake' => 'command.model.make',
    'PolicyMake' => 'command.policy.make',
    'ProviderMake' => 'command.provider.make',
    'QueueFailedTable' => 'command.queue.failed-table',
    'QueueTable' => 'command.queue.table',
    'RequestMake' => 'command.request.make',
    'SeederMake' => 'command.seeder.make',
    'SessionTable' => 'command.session.table',
    'Serve' => 'command.serve',
    'TestMake' => 'command.test.make',
    'VendorPublish' => 'command.vendor.publish',
];

很明显 , 修改这些数组 , 能够开启或关闭默认的命令 .

所以结论就很明显了 , 需要自定义laravel默认的命令行 , 我们只需要自己创建若干个Providers , 继承Illuminate\Foundation\Providers\ConsoleSupportServiceProvider 以及它所加载的各个Provider , 重写这些provider的部分数组和方法 , 最后把config/app.php 文件中的ConsoleSupportServiceProvider 替换成我们自己定义的Provider 就可以了.

多轮对话机器人框架 commune/chatbot 项目发布了demo测试. 微信关注CommuneChatbot 可以测试. QQ交流群: 907985715
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1
 foreach ($commands as $command) {
            $this->{'register'.$command.'Command'}();
        }

楼主这里面 $this->{'xxxxx'}() 形如这般的方式不常见,可否告知下

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!