数据表之间往往不是孤立的,而是纵横交叉、相互关联的,比如一个用户发表了多篇文章,一个文章又有多个评论,等等。Eloquent模型支持多种关联关系,下面让我们一一道来。
一对一是最简单的关联关系,表示表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
在生成的表中插入一条数据:
再使用如下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模型底层是怎么判断User
与UserAccount
的对应关系的呢?
默认情况下,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
。
一对多是一种常见的关联关系,用于表示表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
。对应输出为:
同样,我们可以在文章模型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_key
为user_id
;如果方法名为author
,不指定外键user_id
的话,对应$foreign_key
为author_id
,而author_id
在posts
表中不存在,所以是永远也找不到对应关联模型的。
我们可以使用如下方式获取指定文章对应的用户模型实例:
$author = Post::find(1)->author; dd($author);
页面输出为:
另外一种常见的关联关系是多对多,即表A的某条记录通过中间表C与表B的多条记录关联,反之亦然。比如一个用户有多种角色,反之一个角色对应多个用户。
为了测试该关联关系,我们创建一个角色表roles
,并添加一些初始化数据:
同时我们创建一个中间表role_user
用于记录users
表与roles
表的对应关系:
注意我们定义中间表的时候没有在结尾加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_id
和role_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
这一节我们先讲到这里,下一节我们将讨论更复杂的三种关联关系及其在模型中的定义及使用。