Laravel - MySQL数据库的使用详解6(Eloquent ORM用法3:模型关联、关联查询)
一、一对一关联
一对一关联是很基本的关联。假设一个 User 对应到一个 Phone,phones 表结构如下(通过 user_id 关联 user 表的主键):
1,定义一对一关联
(1)User 模型中按下面这样定义关联,传到 hasOne 方法里的第一个参数是关联模型的类名称。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { // 获取用户对应的电话 public function phone() { return $this->hasOne('App\Models\Phone'); } }
(2)定义好关联之后,就可以使用 Eloquent 的动态属性取得关联对象:
$phone = User::find(1)->phone;
(3)上面操作实际上执行了如下 sql:
select * from users where id = 1 select * from phones where user_id = 1
2,指定一对一关联的外键
(1)默认情况下,外键名称是基于模型名称来的。比如上面样例,Phone 模型则自动以 user_id 作为外键。如果想要更改这个默认值,可以传入第二个参数到 hasOne 方法里。
return $this->hasOne('App\Models\Phone', 'my_user_id');
(2)我们还可以传入第三个参数,指定关联的外键要对应到本身的哪个字段:
return $this->hasOne('App\Models\Phone', 'my_user_id', 'uid');
3,定义相对的关联
(1)前面我们是在 phones 表中有个 user_id 用来关联 user 表数据,如果想在 Phone 模型中获取 User 模型对象,可以使用 belongsTo 定义相对的关联。<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Phone extends Model { // 获取电话对应的用户 public function user() { return $this->belongsTo('App\Models\User'); } }
(2)定义好关联之后,就可以使用 Eloquent 的动态属性取得关联对象:
$user = Phone::find(1)->user;
4,指定关联的外键
(1)默认情况下,外键名称是基于模型名称来的。比如上面样例,Phone 模型则自动以 user_id 作为外键。我们也可以在 belongsTo 方法里传入第二个参数,来指定外键字段。return $this->belongsTo('App\Models\User', 'local_key');
(2)除此之外,也可以传入第三个参数指定要参照上层数据库表的哪个字段。
return $this->belongsTo('App\Models\User', 'local_key', 'parent_key');
二、一对多关联
同样以用户、电话表为例。假设一个 User 可以拥有多个 Phone,phones 表结构如下(通过 user_id 关联 user 表的主键):1,定义一对多关联
(1)User 模型中按下面这样定义关联,传到 hasMany 方法里的第一个参数是关联模型的类名称。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { // 获取用户对应的所有电话 public function phones() { return $this->hasMany('App\Models\Phone'); } }
(2)定义好关联之后,就可以使用 Eloquent 的动态属性取得所有关联对象:
// 获取id为1的用户所有电话 $phones = User::find(1)->phones; // 返回的结果同样支持链式调用 $phone = User::find(1)->phones->first();
2,指定一对多关联的外键
(1)默认情况下,外键名称是基于模型名称来的。比如上面样例,Phone 模型则自动以 user_id 作为外键。如果想要更改这个默认值,可以传入第二个参数到 hasMany 方法里。
return $this->hasMany('App\Models\Phone', 'my_user_id');
(2)我们还可以传入第三个参数,指定关联的外键要对应到本身的哪个字段:
return $this->hasMany('App\Models\Phone', 'my_user_id', 'uid');
3,定义相对的关联
如果想要在 Phone 模型中获取 User 模型对象,那么使用 belongsTo 定义相对的关联。具体用法详见上方一对一关联部分。三、远层一对多关联(Has Many Through)
“远层一对多关联”提供了方便简短的方法,可以经由多层间的关联取得远层的关联。例如,一个 Country 模型可能通过 Users 关联到很多 Posts 模型,这些数据库表间的关系如下:
countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string
(1)虽然 posts 数据库表本身没有 country_id 字段,但 hasManyThrough 方法让我们可以使用 $country->posts 取得 country 的 posts。我们可以定义以下关联:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Country extends Model { public function posts() { return $this->hasManyThrough('App\Models\Post', 'App\Models\User'); } }
(2)如果想要手动指定关联的字段名称,可以传入第三和第四个参数到方法里:
return $this->hasManyThrough('App\Models\Post', 'App\Models\User', 'country_id', 'user_id');
四、多对多关联
多对多关联常见有用户、角色之间的相互关联:一个用户( user )可能用有很多角色( role ),而一种角色可能很多用户都有。
所以数据库会存在如下三个表:用户表(users)、角色表(roles)、用户角色关联表(role_user)。其中关联表以关联的两个模型命名(先后按字母顺序排列),关联表结构如下:
1,定义多对多关联
(1)在 User 和 Role 模型中我们可以使用 belongsToMany 方法定义多对多关系(关联表是不需要定义对应的模型的)<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { // 获取用户对应的所有角色 public function roles() { return $this->belongsToMany('App\Models\Role'); } } <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Role extends Model { // 获取角色对应的所有用户 public function users() { return $this->belongsToMany('App\Models\User'); } }
(2)定义好关联之后,就可以使用 Eloquent 的动态属性取得关联对象。下面样例取得 id 为 1 的用户拥有的所有角色:
$roles = User::find(1)->roles;
2,指定关联表名和关联字段
(1)如果我们关联表是按规定的格式命名的话(以关联的两个模型命名,先后按字母顺序排列),是不需要在手动指定关联表。否则,我们可以在 belongsToMany 方法中传入第二个参数,指定使用的关联表名。return $this->belongsToMany('App\Models\Role', 'users_roles');
(2)同样的关联表中的关联字段默认同样采用“模型_id”的命名方式,如果不是的话,我们可以在 belongsToMany 第三、第四个参数指定使用的关联字段。
return $this->belongsToMany('App\Models\Role', 'users_roles', 'user_id', 'role_id');
五、关联查询
在取得模型数据时,我们可能想要以关联模型作为查询限制。下面同样以上面的用户(User)、电话(Phone)这个一对多关联场景进行演示。
1,has 方法
(1)想要取得所有“至少有一个电话”的用户。可以使用 has 方法达成目的。
$users = User::has('phones')->get();
(2)也可以指定运算符和数量:
$users = User::has('phones', '>=', 3)->get();
(3)也可以使用“点号”的形式来获取嵌套的 has 声明:
$users = User::has('phones.xxxx')->get();
2,whereHas 、 orWhereHas 方法
如果想要更进阶的用法,可以使用 whereHas 和 orWhereHas 方法,在 has 查询里设置 “where” 条件 :$users = Post::whereHas('phones', function($q) { $q->where('number', 'like', '189%'); })->get();
附:预载入
预载入是用来减少 N + 1 查询问题。例如,一个 Phone 模型数据会关联到一个 User ,关联会像下面这样定义。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Phone extends Model { // 获取电话对应的用户 public function user() { return $this->belongsTo('App\Models\User'); } }
1,基本用法
(1)假设我们使用下面的循环会执行一次查询取回所有数据库表上的电话,然而每个电话又都会执行一次查询取得用户。所以若我们有 10 个电话记录,就会进行 11 次查询。
foreach (Phone::all() as $phone) { echo $phone->user->username; }
(2)我们可以使用预载入大量减少查询次数。即使用 with 方法指定想要预载入的关联对象:
foreach (Phone::with('user')->get() as $phone) { echo $phone->user->username; }
(3)上面的循环总共只会执行两次查询,对应的 sql 如下:
select * from phones select * from users where id in (1, 2, 3, 4, 5, ...)
2,同时载入多种关联
使用预载入可以大大提高程序的性能,当然 with 方法也是可以同时载入多种关联的。
Phone::with('user', 'operator')->get()
3,预载入条件限制
(1)我们也可以指定预载入时的查询限制,比如下面只预载入李姓用户:
$phone = Phone::with(['user' => function($query) { $query->where('username', 'like', '李%'); }])->get();
(2)当然,预载入的闭合函数里不一定只能加上条件限制,也可以加上排序:
$phone = Phone::with(['user' => function($query) { $query->orderBy('age', 'desc'); }])->get();