Laravel 5.6 下,在数据库迁移中通过 connection 属性配置新的数据库连接无效。

最近,公司老大准备将项目不同模块单独拆分开,包括数据库,这样就涉及到将不同模块的数据持久化到不同的数据库中,也就是不同模块的迁移文件有不同的连接。

在配置的时候,发现 Migration 抽象类中有这样一个属性

abstract class Migration
{
    /**
     * The name of the database connection to use.
     *
     * @var string
     */
    protected $connection;

上面的英文注释意思是:配置使用的数据库连接的名称

于是我在迁移文件中重写了这个属性:

class CreateTestTable extends Migration
{

    protected $connection = 'mysql_test1';

问题来了:
我运行迁移后新建的表始终是在默认的数据库中而不是通过 connection 配置的数据库中。
这样我就很纳闷了,因为我觉得既然在抽象类 Migration 中存在这个属性,并且注释也表明可以通过配置该属性来配置将表迁移到配置的库中,但是为什么没有效果。

后面通过阅读源码,有了一些发现,同时也又产生了一些疑问:

在 Migrator.php 这个文件中,我找到了执行迁移中 up 的方法:

    /**
     * Run a migration inside a transaction if the database supports it.
     *
     * @param  object  $migration
     * @param  string  $method
     * @return void
     */
    protected function runMigration($migration, $method)
    {
        //通过在 Migration 中的 connection 配置生成对应的连接
        $connection = $this->resolveConnection(
            $migration->getConnection()
        );

       //将迁移中的 UP 方法以匿名函数的形式赋值给 $callback
        $callback = function () use ($migration, $method) {
            if (method_exists($migration, $method)) {
                $migration->{$method}();
            }
        };

        //检查连接是否支持事务,并且是否开启事务,如果支持并且开启事务,
        //则在连接的事务中去执行迁移中的 up 方法。
        $this->getSchemaGrammar($connection)->supportsSchemaTransactions()
            && $migration->withinTransaction
                    ? $connection->transaction($callback)
                    : $callback();
    }

上面的方法中,我将关键的逻辑都注释了出来。

这里确实是通过配置生成了一个新连接,但是在这里只是用到了该连接的的事务。
最终还是执行的迁移文件中的 up 方法,这个 up 方法中的数据模型如果没有配置连接的话,就是默认的连接。
具体迁移文件中 up 方法中的逻辑是怎样的,就不在此说明,有兴趣的可以看一下。

到此,这个困扰我的问题在我心中有了一个答案,就是 Migration 中配置的 connection 属性并没有它注释上的效果,这应该是一个 bug (可能我有见解不对的地方,请大家指出)。

在这里,我还发现了一个有趣的事:

/**
     * Run a migration inside a transaction if the database supports it.
     *
     * @param  object  $migration
     * @param  string  $method
     * @return void
     */
    protected function runMigration($migration, $method)
    {
        $connection = $this->resolveConnection(
            $migration->getConnection()
        );

        //该匿名函数没有参数
        $callback = function () use ($migration, $method) {
            if (method_exists($migration, $method)) {
                $migration->{$method}();
            }
        };

        $this->getSchemaGrammar($connection)->supportsSchemaTransactions()
            && $migration->withinTransaction
                    ? $connection->transaction($callback)
                    : $callback();
    }

上面的 $callback 匿名函数是没有参数的,
继续看上面的代码,$connection->transaction($callback) , 我们追到 transaction() 方法中:

  public function transaction(Closure $callback, $attempts = 1)
    {
        for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
            $this->beginTransaction();

            // We'll simply execute the given callback within a try / catch block and if we
            // catch any exception we can rollback this transaction so that none of this
            // gets actually persisted to a database or stored in a permanent fashion.
            try {
                //这里调用 callback 竟然添加了参数 $this
                return tap($callback($this), function ($result) {
                    $this->commit();
                });
            }

看上面的代码,tap 竟然给 $callback 传了一个参数, 实际上 $callback 这个匿名函数是没有参数的。
我又到本地去写了测试代码去测试了,后面得出了一个结论:

php 中匿名函数的参数是一个可变参数。

上面针对在 Migration 中配置属性 connection 无效的事件做了一个自己的见解,可能有误,如果大家有什么更好的看法,请说出来大家一起讨论讨论。

2018-8-28 9:00 跟新:

老大将上述迁移问题提到githab,链接在此,得到的回复确实是一个bug,证明我的猜想是正确的。

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 6

是的,这可能是一个bug,这里是issue:https://github.com/laravel/framework/issue...

5年前 评论

5.8版本怎么还有这个问题?

4年前 评论

你不是定义就完事了,需要用 DB::connection('mysql_test1') 选择这个库啊。 :joy:

4年前 评论
pcl___ 2年前
逆天西瓜 (作者) 1年前

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