Eloquent ORM 实例教程 —— 关联关系及其在模型中的定义(一)

guide-to-eloquent-orm

数据表之间往往不是孤立的,而是纵横交叉、相互关联的,比如一个用户发表了多篇文章,一个文章又有多个评论,等等。Eloquent模型支持多种关联关系,下面让我们一一道来。

1、一对一

一对一是最简单的关联关系,表示表A和表B的记录一一对应,比如一个用户对应一个社交账号,在演示该关联关系之前我们先创建一个社交账号表user_accounts

php artisan make:migration create_user_accounts_table --create=user_accounts

编辑生成的迁移文件如下:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUserAccountsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('user_accounts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id');
            $table->string('qq',20)->nullable();
            $table->string('weixin',100)->nullable();
            $table->string('weibo',100)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('user_accounts');
    }
}

然后运行Artisan命令:

php artisan migrate

在生成的表中插入一条数据:

user-accounts

再使用如下Artisan命令生成模型UserAccount

php artisan make:model Models/UserAccount

接下来我们开始在User中定义与UserAccount的一对一对应关系:

public function account()
{
    return $this->hasOne('App\Models\UserAccount');
}

最后在控制器中编写测试代码如下:

$account = User::find(1)->account;
dd($account);

浏览器中会输出相对应的UserAccount模型实例。

相对的,我们也可以在UserAccount模型中定义与User的一对一关系:

public function user()
{
    return $this->belongsTo('App\User');
}

相应的测试代码为:

$user = UserAccount::find(1)->user;
dd($user);

上述代码会输出相应的User模型实例。

注意我们并没有在调用belongsTo的时候指定相应的外键信息,那么Eloquent模型底层是怎么判断UserUserAccount的对应关系的呢?

默认情况下,Eloquent将调用belongsTo的关联方法名user作为关联关系$relation的值,并将$relation.'_id'作为默认外键名对应users表的id,如果表中没有相应列,又没有在定义关联关系的时候指定具体的外键,就会报错。

那么又该如何在定义关联关系的时候指定外键呢?

实际上在底层无论是hasOne方法还是belongsTo方法都可以接收额外参数,比如如果user_accounts中关联users的外键是$foreign_key,该外键对应users表中的列是$local_key,那么我们可以这样调用hasOne方法:

$this->hasOne('App\Models\UserAccount',$foreign_key,$local_key);

调用belongsTo方法也是一样:

$this->belongsTo('App\User',$foreign_key,$local_key);

此外,belongsTo还接收一个额外参数$relation,用于指定关联关系名称,其默认值为调用belongsTo的方法名,这里是user

2、一对多

一对多是一种常见的关联关系,用于表示表A的某条记录对应表B的多条记录,反之表B的某条记录归属于表A的某条记录,比如一个用户发表多篇文章,定义一对多的关系也很简单,我们在用户模型User中定义与文章模型Post的一对多关系如下:

public function posts()
{
    return $this->hasMany('App\Models\Post');
}

对应的测试代码:

$posts = User::find(1)->posts;
dd($posts);

这样就能获取id为1的用户所有发表的文章。

由于关联模型实例本身是一个查询构建器,我们可以添加查询条件到该实例:

$posts = User::find(1)->posts()->where('views','>',100)->get();
dd($posts);

需要注意的是这里我们调用的是posts方法,而不是动态属性posts。对应输出为:

user-posts

同样,我们可以在文章模型Post中定义文章所属用户模型User的对应关系:

public function author()
{
    return $this->belongsTo('App\User','user_id','id');
}

注意我们在belongsTo方法中传入了额外参数,意为posts表中的user_id对应users表中的id。如果我们定义的方法名为user,则不需传入这些参数:

public function user()
{
    return $this->belongsTo('App\User');
}

正如我们在上面一对一关系中提到的,这是因为如果我们在调用belongsTo方法时如果没有传入第四个参数$relation,则默认使用当前调用belongsTo的方法名为关联关系名称并赋值给$relation,在没有传入第二个参数$foreign_key的时候,使用$relation.'_id'作为$foreign_key(如果$relation为驼峰式命名且包括大写字母的话将大写字母转化为小写字母并在前面添加’_’)。因此,如果方法名为user,对应$foreign_keyuser_id;如果方法名为author,不指定外键user_id的话,对应$foreign_keyauthor_id,而author_idposts表中不存在,所以是永远也找不到对应关联模型的。

我们可以使用如下方式获取指定文章对应的用户模型实例:

$author = Post::find(1)->author;
dd($author);

页面输出为:

post-author

3、多对多

另外一种常见的关联关系是多对多,即表A的某条记录通过中间表C与表B的多条记录关联,反之亦然。比如一个用户有多种角色,反之一个角色对应多个用户。

为了测试该关联关系,我们创建一个角色表roles,并添加一些初始化数据:

用户角色表roles

同时我们创建一个中间表role_user用于记录users表与roles表的对应关系:

中间表role_user

注意我们定义中间表的时候没有在结尾加s并且命名规则是按照字母表顺序,将role放在前面,user放在后面,并且用_分隔,这一切都是为了适应Eloquent模型关联的默认设置:在定义多对多关联的时候如果没有指定中间表,Eloquent默认的中间表使用这种规则拼接出来。

然后我们还要创建一个Role模型:

php artisan make:model Models/Role

下面我们在User中定义多对多关联如下:

public function roles()
{
    return $this->belongsToMany('App\Models\Role');
}

注:正如我们上面提到的,如果中间表不是role_user,那么需要将中间表作为第二个参数传入belongsToMany方法,如果中间表中的字段不是user_idrole_id,这里我们姑且将其命名为$user_id$role_id,那么需要将$user_id作为第三个参数传入该方法,$role_id作为第四个参数传入该方法,如果关联方法名不是roles还可以将对应的关联方法名作为第五个参数传入该方法。

接下来我们在控制器中编写测试代码:

$user = User::find(1);
$roles = $user->roles;
echo 'User#'.$user->name.'所拥有的角色:<br>';
foreach($roles as $role)
{
    echo $role->name.'<br>';
}

对应输出为:

User#Laravel所拥有的角色:
writer
reader

相对的我们也可以在模型Role中定义获取对应User模型的方法:

public function users()
{
    return $this->belongsToMany('App\User');
}

测试代码如下:

$role = Role::find(4);
$users = $role->users;
echo 'Role#'.$role->name.'下面的用户:<br>';
foreach ($users as $user) {
    echo $user->name.'<br>';
}

对应输出为:

Role#reader下面的用户:
Laravel
Academy

此外我们还可以通过动态属性pivot获取中间表字段:

$roles = User::find(1)->roles;
foreach ($roles as $role) {
    echo $role->pivot->role_id.'<br>';
}

对应输出为:

3
4

这一节我们先讲到这里,下一节我们将讨论更复杂的三种关联关系及其在模型中的定义及使用。

学院君

学院君 has written 550 articles

资深PHP工程师,Laravel学院院长