Migration Blueprint 继承及其相关问题

分割线下面是原问题细节,实际上想问的问题可以归结为:(看代码)

Schema::create('Person',function(Blueprint $table){
    $table->string('id')->comment('身份证号');
    $table->string('id')->comment('学号')->change();//为什么此句不起作用?
});

上述代码中的第二句为什么不起作用呢?

解决方案

目前的理解是:
change()函数的调用实际上只是将['change'=>true]添加到ColumnDefinition,然后最终在toSql()中进行语法解析,而change操作最终会转变为ChangeColumn::compile()交给doctrine/dbal处理,这里推测doctrine/dbal的操作是在表格已经存在的情况下,所以如果表格不存在,change()没有任何操作。
而如果依旧想实现后面的定义会覆盖前面的,那么根据Blueprint的执行过程,可以重写addColumn()函数,如下:

 public function addColumn($type, $name, array $parameters = [])
    {
       foreach($this->columns as $k=>$v)
       {
            if($v->name==$name)//如果之前有定义
            {
                $this->columns=array_except($this->columns,$k);//把之前的定义删除
                break;
            }
       }
        return parent::addColumn($type,$name,$parameters);
    }

实际上,Blueprint中允许定义两个相同名字的字段,只不过后面的Grammar会进行检测。上面的代码实现了自己的目的不过在定义字段的时候需要记着后面的定义会覆盖前面的!


问题背景

最初通过laravelMigration来管理数据库表的迁移时,都是一个文件对应一个表格。即使表格可能会存在继承关系,还是会写很多重复的代码定义。如下:

M1 extends Migration
{
    public function up
    {
        Schema::create('Person',function(Blueprint $table){

            $table->string('name')->comment('name');
            $table->string('sex')->comment('sex');
            $table->string('id')->comment('身份证号');
            ....
        });
    }
    ......
}
M2 extends Migration
{
    public function up
    {
        Schema::create('Student',function(Blueprint $table){

            $table->string('name')->comment('name');
            $table->string('sex')->comment('sex');
            $table->string('id')->comment('学号');
            ....
        });
    }
    ......
}

如上所示,倘若能够实现Student继承自Person,那么M2中就可以少写很多重复代码

问题分析

由于Schema::create()第二个参数为闭包且传入的是Blueprint对象,因此如果能够继承Blueprint则能够达到上述目的。怀着这个想法查到了下面的问答:Extend Blueprint class?

继承Blueprint的主要步骤举例就是:(代码在下面)

  1. 新建类Person直接继承系统Blueprint类,在构造函数中完成基本的字段定义
  2. Student然后继承Person,其构造函数中增加其独特的字段定义,如:班级
Person extends Blueprint
{
    public function __construct($table,$callback=null,$prefix='')
    {
        parent::__construct($table,$callback,$prefix');
        $table->string('name')->comment('name');
        $table->string('sex')->comment('sex');
        $table->string('id')->comment('身份证号');
    }
}
Student extends Person
{
    public function __construct($table,$callback=null,$prefix='')
    {
        parent::__construct($table,$callback,$prefix');

        //下面的方式1和2是为了在Student中修改Person中的已有定义,后面有用!
        //$table->string('id')->comment('学号')->change();//方式1

        //$table->removeColumn();//方式2
        //$table->string('id')->comment('学号')->change();
    }
}

那么再通过Schema来建表的主要步骤举例就是:(代码在下面)

  1. 获取Schema的实例$schema
  2. 修改$schemablueprintResolver以返回Person/Student实例
  3. $schema->create()的闭包函数function(){}的参数类型修改为Person/Student
M1 extends Migration
{
    public function up
    {
        $schema  =  Schema::getFacadeRoot();
        $schema->blueprintResolver(function($table,  $callback,$prefix)  {
            return  new  Person($table,  $callback,$prefix);
        });
        $schema->('Person',function(Person $table){
            **这里什么都没有**
        });
    }
    ......
}
M2 extends Migration
{
    public function up
    {
        $schema  =  Schema::getFacadeRoot();
        $schema->blueprintResolver(function($table,  $callback,$prefix)  {
            return  new  Student($table,  $callback,$prefix);
        });
        $schema->('Student',function(Student $table){
            **这里什么都没有**
        });
    }
    ......
}

解决了基本的继承问题,不过,如上述代码所示:Personid字段comment值为身份证号,那么显然Student中的id字段comment值继承自Person

但是,如果想要Studentidcomment更改为学号应该怎么实现呢?

上述代码中注释部分写了2种方法,不过没有达到预期效果!

Blueprint继承无法覆盖已定义字段?

如代码中所示,change()以及removeColumn()都没有达到子类覆盖父类的字段定义的目的

change()指的是,如:$student->string('id')->comment('学号')->change()
removeColumn()指的是:$table->removeColumn(‘id’)

经查阅资料发现:包括官网指导在内的很多教程都只是举例如何在php artisan migrate之后,建立新的Migration再调用相应的方法,并且这些Modify Column操作需要额外的依赖composer require doctrine/dbal,从这里揣测,似乎doctrine/dbal所做的工作都是在表格存在的情况下!

那么,在继承的基础上实现上述字段重定义应该怎么做呢?

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

@laravelxx 😂好吧你比我倔,这个问题其实我也遇到过,只是不想你在歧路上越走越远

创建表用的是Schema::create,而修改表是Schema::table,所以change方法无效,而且在执行change的时候表并没有创建,所以是没办法change的,如果需要change只能再下面再用一个Schema::table

示例:

Schema::create('Person',function(Blueprint $table){
    $table->string('id')->comment('身份证号');
});
Schema::table('Person',  function  (Blueprint $table)  {
    $table->string('id')->comment('学号')->change();
});
5年前 评论
laravelxx (楼主) 5年前
讨论数量: 2

我感觉migration本身是一个很简单的东西,没必要非得把它搞复杂,而且程序设计中的各种封装并不是为了少写代码,也不是为了封装而封装,这些都是围绕代码可读性和可维护性展开的,migration本来就是提交后尽量不做更改的东西,保证它可以正常运行就行了,把它搞复杂了就是误入歧途了,就像没必要给单元测试做单元测试

5年前 评论
laravelxx (楼主) 5年前
hausir (作者) 5年前
laravelxx (楼主) 5年前
hausir (作者) 5年前
laravelxx (楼主) 5年前

@laravelxx 😂好吧你比我倔,这个问题其实我也遇到过,只是不想你在歧路上越走越远

创建表用的是Schema::create,而修改表是Schema::table,所以change方法无效,而且在执行change的时候表并没有创建,所以是没办法change的,如果需要change只能再下面再用一个Schema::table

示例:

Schema::create('Person',function(Blueprint $table){
    $table->string('id')->comment('身份证号');
});
Schema::table('Person',  function  (Blueprint $table)  {
    $table->string('id')->comment('学号')->change();
});
5年前 评论
laravelxx (楼主) 5年前

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