如何干预Laravel默认的迁移执行顺序?

后端开发 2026-07-11

我现在正在用Laravel开发一个面向插件的CMS,但在尝试干预Laravel的默认迁移顺序时遇到了瓶颈。Laravel默认会按文件名的时间戳前缀来执行迁移,但在我的CMS场景中,迁移顺序始终是 core -> addons -> app,到目前为止还没找到这个问题的解决办法。

我知道可以把所有文件直接发布到应用端,并根据它们所属的分组来修改它们的文件名时间戳,但我更愿意不把任何核心逻辑暴露给应用端。

我想做的事:

干预任何依赖迁移顺序的Artisan调用(例如migration:run、migration:down等),并改变默认的文件排序逻辑。

到目前为止我尝试过:

我创建了一个Migrator文件,它扩展了Laravel的默认Migrator。

<?php

namespace CMS\Database\Migrators;

use Illuminate\Database\Migrations\Migrator as LaravelMigrator;

class Migrator extends LaravelMigrator
{
    public function run($paths = [], array $options = []): array
    {
        dd("debug"); // never fires on any db related artisan call
        $this->runCore();
        $this->runAddons();
        return [];
    }
}

然后我在一个服务提供者的register方法里把它设置为单例(这一点在任何数据库相关的服务提供者之前,这应该没有关系)

<?php

namespace CMS\Providers;

use Illuminate\Database\Migrations\MigrationRepositoryInterface;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Migrations\Migrator as LaravelMigrator;
use CMS\Database\Migrators\Migrator as CMSMigrator;

class DatabaseServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(LaravelMigrator::class, function ($app) {
            return new CMSMigrator(
                $app['files'],
                $app['db'],
                $app['events'],
                $app->make(MigrationRepositoryInterface::class)
            );
        });
    }
}

软件信息

{
  "laravel/framework": "^12.0",
  "laravel/tinker": "^2.10.1"
}

谢谢。

解决方案

不要把它做成单例,而是扩展默认的Migrator实例

<?php

namespace CMS\Providers;

use Illuminate\Support\ServiceProvider;
use CMS\Database\Migrators\Migrator as CMSMigrator;
use CMS\Helpers\Package;

class DatabaseServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->extend('migrator', function ($_migrator, $app) {
            return new CMSMigrator(
                $app['migration.repository'],
                $app['db'],
                $app['files'],
                $app['events']
            );
        });
    }
}

用如下构造函数对它进行预加载

<?php

use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Migrations\Migrator as LaravelMigrator;
use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;

class Migrator extends LaravelMigrator
{
    public function __construct()
    {
        $repository = app('migration.repository');
        $resolver   = app(DatabaseManager::class);
        $files      = app(Filesystem::class);
        $dispatcher = app(Dispatcher::class);

        parent::__construct($repository, $resolver, $files, $dispatcher);
    }
}

最后添加一个run方法,参数是字符串数组,返回一个数组

<?php

    public function run($paths = [], array $options = []): array
    {
        $corePath = "my-package-migrations-path";
        $addons = [["name" => "a1", "migrationPath" => "path1"], ["name" => "a2", "migrationPath" => "path2"]];

        parent::run([$corePath]);
        parent::run(array_map(fn ($addon) => $addon['migrationPath'], $addons));

        // Prevent default Laravel from running paths again, I am not sure with this one just yet
        return [];
    }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章