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
再使用如下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模型实例。
上述代码会输出相应的User模型实例。
注意我们并没有在调用belongsTo的时候指定相应的外键信息,那么Eloquent模型底层是怎么判断User与UserAccount的对应关系的呢?
默认情况下,Eloquent将调用belongsTo的关联方法名user作为关联关系 relation的值,并将 relation.’_id’作为默认外键名对应users表的id,如果表中没有相应列,又没有在定义关联关系的时候指定具体的外键,就会报错。
那么又该如何在定义关联关系的时候指定外键呢?
实际上在底层无论是hasOne方法还是belongsTo方法都可以接收额外参数,比如如果user_accounts中关联users的外键是 foreignkey,该外键对应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。
同样,我们可以在文章模型Post中定义文章所属用户模型User的对应关系:
// 这里使用的不是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,在没有传入第二个参数 foreignkey的时候,使用 relation.’id’作为 foreignkey(如果 relation为驼峰式命名且包括大写字母的话将大写字母转化为小写字母并在前面添加’’)。因此,如果方法名为user,对应 foreignkey为userid;如果方法名为author,不指定外键userid的话,对应 foreign_key为author_id,而author_id在posts表中不存在,所以是永远也找不到对应关联模型的。
我们可以使用如下方式获取指定文章对应的用户模型实例:
$author = Post::find(1)->author; dd($author);
3、多对多
另外一种常见的关联关系是多对多,即表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
4、远层一对多
所谓的“远层一对多”指的是通过一个中间关联对象访问远层的关联关系,比如用户与文章之间存在一对多关系,国家与用户之间也存在一对多关系,那么通过用户可以建立国家与文章的之间的一对多关联关系,我们称之为“远层一对多”。
为了测试该关联关系我们新建一个国家表countries并初始化两条记录:
接下来我们为用户表users新增一个country_id字段。例子中定义用户id为的用户的country_id为1
文章表posts之前我们已经定义过,不再赘述。
然后我们创建一个模型类Country,并在其中定义国家与文章的远层一对多关系如下:
public function posts() {
return $this->hasManyThrough('App\Models\Post','App\User');
}
由此可见我们通过hasManyThrough方法来定义远层一对多关联。其中第一个参数是关联对象类名,第二个参数是中间对象类名。
如果users表中表示用户对应国家的字段不是county_id(假设为 countryid),并且posts表中表示文章所属用户的字段不是userid(假设为 user_id),我们可以传递更多参数到hasManyThrough方法:
public function posts() {
return $this->hasManyThrough('App\Models\Post','App\User',$country_id,$user_id);
}
接下来我们在控制器中定义测试代码如下:
$country = Country::find(1);
$posts = $country->posts;
echo 'Country#'.$country->name.'下的文章:<br>';
foreach($posts as $post){
echo '<<'.$post->title.'>><br>';
}
页面输出如下:
<<test update>>
<<test2>>