[ Laravel 从入门到精通 ] 数据库和 Eloquent 入门 —— 通过迁移文件定义数据表结构

在对数据库进行操作之前,需要先创建数据表,在诸如 Laravel 这种现代框架中,通过代码驱动让数据表结构的定义变得非常简单。每一张新表、每个新的字段、索引、以及外键都可以通过编写代码来定义,这样做的好处是在任何新环境中,你可以通过执行一个命令几秒钟就搞定项目的数据库结构。

这种代码驱动的数据表结构定义功能我们把它叫做迁移(Migrations),意为方便你在项目的不同环境中快速迁移数据表结构变动。

定义迁移

数据表的每次变动(创建、修改、删除)都对应一个迁移文件,这些迁移文件位于 database/migrations 目录下,以日期时间为条件确定执行的先后顺序。每个迁移文件中包含一个迁移类,这个迁移类有两部分组成:负责执行数据库迁移的 up 方法,以及负责回滚此次迁移的 down 方法。以 Laravel 自带的 users 表迁移文件为例,代码如下所示:

正如你所看到的,这个迁移类包含了 up 方法和 down 方法,分别用于创建 users 表和删除 users 表。

当我们迁移数据库时,系统获取所有数据库迁移文件(包括 database/migrations 目录下和扩展包中注册的),然后按照文件名中包含的日期时间排序,从最早的迁移文件开始,依次执行每个迁移类中的 up 方法,最后完成数据库迁移;反之,当我们回滚数据库时,按照日期时间排序,从最晚的迁移文件开始,依次执行每个迁移类的 down 方法,最后完成数据库回滚,如果指定回滚其中某几步的话,回滚到对应的迁移文件即终止。

创建迁移文件

正如我们在 Artisan 命令中所提到的,Laravel 提供了一个 Artisan 命令 make:migration 帮助我们快速生成数据库迁移文件,该命名包含一个参数,就是要创建的迁移的名称,比如要创建 users 表对应迁移文件,可以通过 php artisan make:migration create_users_table 命令来完成。

此外,这个 Artisan 命令还支持两个可选的选项,--create= 用于指定要创建的数据表名称,以及 --table= 用于指定要修改的数据表名称,前者在定义创建数据表迁移文件时使用,后者在定义更新数据表迁移文件时使用,比如我们还是以 users 表为例:

 php artisan make:migration create_users_table --create=users  # 创建数据表迁移
 php artisan make:migration alter_users_add_nickname --table=users  # 更新数据表迁移

创建数据表

有了迁移文件后,就可以在迁移文件对应迁移类的 up 方法中编写创建数据表的逻辑了,以 create_users_table 迁移为例:

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        ...
    });
}

我们对数据库的迁移操作都是基于 Schema 门面来完成(底层对应的类是 Illuminate\Database\Schema\Builder),比如创建数据表,需要调用该门面的 create 方法,该方法的第一个参数是要创建的数据表的名称,第二个参数是一个闭包,其中定义的是新增数据表的所有字段信息。

创建新字段

接下来,我们看一下如何为创建的数据表添加字段,前面我们已经说了,这个操作在 Schema::ceate 方法的第二个参数的闭包函数中完成:

Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();        
});

我们为该闭包函数注入了 Blueprint 类实例,并通过该实例提供的方法完成数据表字段的定义。Blueprint 类为我们提供了丰富的数据表字段定义方法,通过这些方法我们完成所有与数据表字段相关的操作,包括新增字段、删除字段、修改字段、添加索引和外键等等。

关于 Blueprint 类提供的新增数据表字段方法,可以通过查看文档一目了然,你自己新增字段的时候对着这个列表套用对应方法即可。

构建字段额外属性

绝大部分数据表字段都有自己的属性,比如长度、是否为空、默认值、注释信息等,比如我们可以为 users 表的 name 字段设置一些额外属性,将其长度设置为 100(默认是255),并且为该字段添加注释信息:

$table->string('name', 100)->comment('用户名');

关于字段其它可以设置的额外属性,可以查看文档中列改修器部分列出的属性列表

删除表

删除数据表很简单,调用 Schema::drop 方法即可,但是我们还有一个更好的方法 dropIfExists,该方法会先检查数据表是否存在,存在才会删除,create_users_table 迁移的 down 方法就是这么做的:

public function down()
{
    Schema::dropIfExists('users');
}

修改表

数据表创建完成后,并不意味着一成不变,随着需求的变动,可能需要对数据表结构进行调整,要修改一个数据表字段,千万不要直接到创建表的迁移文件中直接修改,而是以增量的方式新增一个迁移文件,比如我们如果要为 users 表新增一个 nickname 字段,可以通过以下命令新增一个迁移文件:

php artisan make:migration alter_users_add_nickname --table=users

这样就会在 database/migrations 目录下新增一个迁移文件:

可以看到新生成的迁移类中 up 方法和 down 方法和之前通过指定 --create= 选项创建的方法不一样,因为这个迁移文件是用于修改数据表,对应的,我们只需在已有 users 表基础上增删字段即可,不需要新增或删除数据表。

显然,我们是通过 Schema 门面上的 table 方法来修改数据表结构。我们定义 up 方法如下来新增一个 nickname 字段:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('nickname', 100)->after('name')->nullable()->comment('用户昵称');
    });
}

我们定义 nickname 字段是一个长度为 100 的字符串,该字段会插入到 name 字段后面,允许为空,注释信息是用户昵称。关于新增字段和设置字段额外属性参考上面创建新字段构建字段额外属性部分。

接下来,我们通过这个新增迁移类的 down 方法来演示如何删除一个已存在的字段:

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('nickname');
    });
}

如你所见,通过 BlueprintdropColumn 方法即可删除指定字段(该方法依赖下面修改表字段安装的 doctrine/dbal 扩展包)。

修改表字段

有时候,你的需求可能对已存在的数据表字段进行修改,比如重命名某个字段名称,或者将字段长度做调整,借助 Blueprint 提供的方法也可以实现。但是在此之前,需要先通过 Composer 安装 doctrine/dbal 扩展包:

composer require doctrine/dbal

如果你是想修改某个字段的长度,可以在定义完新的字段属性后调用 change 方法执行变更:

$table->string('nickname', 50)->change();

上面提到的构建字段额外属性的所有方法都可以在这里使用,只要最后加上 change 方法让变动生效即可。

如果你是想重命名某个字段,可以调用 renameColumn 即可:

$table->renameColumn('name', 'username');

索引和外键

前面我们已经演示了如何创建、删除、修改表字段,接下来我们要讨论如何对表字段设置索引和外键。

添加索引

对字段设置索引我们已经在 create_users_table 中看到过了:

$table->string('email')->unique();

通过这种调用方式,我们将 email 字段设置为了唯一索引。我们还可以为某个字段设置普通索引:

$table->string('name')->index();

主键索引通过 increments 方法创建,该方法会创建一个自动增长的主键索引:

$table->increments('id');

除了上述这些调用方式外,我们还可以通过另一种方式来为字段创建索引:

$table->primary('id');
$table->index('name');
$table->unique('email');

这样做的一个好处是一次支持传入多个字段,从而构建混合索引:

$table->index(['name', 'email']);

移除索引

移除索引也很简单:

$table->dropIndex('name');
$table->dropUnique('email');
$table->dropPrimary('id');

添加、移除外键

所谓外键指的是一张表的字段 A 引用另一张表的字段 B,那么字段 A 就是外键,通过外键可以建立起两张表之间的关联关系,这样,数据表之间就是有关联的了,而不是一个个孤立的数据集。

在迁移类中,如果我们想建立文章表中的 user_id 字段与用户表中的 id 之间的关联关系,可以通过这种方式来定义外键索引来实现:

$table->foreign('user_id')->references('id')->on('users');

如果你还想进一步指定外键约束(级联删除和更新,比如我们删除了 users 表中的某个 id 对应记录,那么其在文章表中对应 user_id 的所有文章会被删除,好危险!),可以通过 onDeleteonUpdate 方法来实现:

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade');

如果你想要删除外键索引,可以通过 dropForeign 方法来实现:

$table->dropForeign(['user_id']);

或者通过完整的外键索引名称来删除:

$table->dropForeign('posts_user_id_foreign');

注:不推荐使用外键,更不要使用外键约束功能,因为影响数据库性能,而且级联删除有可能造成非常严重的无法挽回的后果。关联关系我们建议通过业务逻辑代码来实现,比如后面介绍的 Eloquent ORM 专门提供了常见的关联关系方法。

运行迁移

至此,我们已经介绍完了常见的数据表结构定义,基本能满足你99%的日常需求,接下来,我们来运行上面定义的迁移文件执行数据库变更。常见的操作有两种,一种是执行变更,一种是回滚变更。

执行变更很简单,通过:

php artisan migrate 

就可以按照迁移文件生成时间的先后顺序依次执行所有的数据库迁移。

回滚要稍微复杂点,Laravel 支持多种形式的回滚,如果只回滚最后一个迁移文件的变更,可以通过:

php artisan migrate:rollback

来实现,如果要回滚多个迁移文件的变更,可以通过 --step= 指定步数(按照迁移文件生成时间逆序执行):

php artisan migrate:rollback --step=5

如果是要回滚所有迁移文件的变更,将数据库恢复到初始状态,需要运行以下命令:

php artisan migrate:reset

友情提示:在本地开发环境,如果数据库没有安装在本地,比如你使用的是 Homestead 或 Laradcok 的话,需要登录到对应环境执行 migrate 相关迁移命令,否则会因连不上数据库而报错。

学院君 has written 1269 articles

Laravel学院院长,终身学习者

积分:177237 等级:P12 职业:手艺人 城市:杭州

6 条回复

  1. 学院君 学院君 says:
    @ Jiao

    那肯定的,所以一开始规划的时候,尽可能把字符串的长度规划的长一些,比如 content 这种能长就不要短,了解 varchar 的原理后,你会知道在设计数据表时,varchar(100)和 varchar(200)占用的存储空间是一样的,那么为什么不在设计的时候尽可能把长度设置大一些的,日后调整成本显然更高

  2. Jiao Jiao says:

    学院君,你好,实操中遇到一个问题。 迁移内容为变更字段content的长度100为200,执行迁移 之后插入数据,其中字段content的长度大于100 此时rollback会遇到类似“Data truncated for column 'content'” 这种问题,一般怎么处理呢?

  3. JackyBen JackyBen says:
    @ Scorpion

    还有就是虽然现在大多解决方案都采用了云数据库,团队协作的时候都对同一个数据库进行操作。但是在离线的情况下,在本地连接的数据库也能很好的同步数据库结构,只需要在线的时候更新到云数据库就可以了。能有效的面对多种复杂的开发环境,而保证整个团队的协作统一。

  4. Lee Lee says:
    @ Scorpion

    个人认为团队中每个人的开发环境不一样,版本不一样,你写的sql文件不一定完全可以在团队中所有人电脑上运行,迁移脱离了sql文件的底层限制,可以无视数据库版本等

登录后才能进行评论,立即登录?