codecamp

关联

当然,您的数据库表很可能跟另一张表相关联。例如,一篇 blog 文章可能有很多评论,或是一张订单跟下单客户相关联。 Eloquent 让管理和处理这些关联变得很容易。 Laravel 有很多种关联类型:

  • 一对一
  • 一对多
  • 多对多
  • 远层一对多关联
  • 多态关联
  • 多态的多对多关联

一对一

定义一对一关联

一对一关联是很基本的关联。例如一个 User 模型会对应到一个 Phone 。 在 Eloquent 里可以像下面这样定义关联:

class User extends Model {
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

传到 hasOne 方法里的第一个参数是关联模型的类名称。定义好关联之后,就可以使用 Eloquent 的动态属性取得关联对象:

$phone = User::find(1)->phone;

SQL 会执行如下语句:

select * from users where id = 1
select * from phones where user_id = 1

注意, Eloquent 假设对应的关联模型数据库表里,外键名称是基于模型名称。在这个例子里,默认 Phone 模型数据库表会以 user_id 作为外键。如果想要更改这个默认,可以传入第二个参数到 hasOne 方法里。更进一步,您可以传入第三个参数,指定关联的外键要对应到本身的哪个字段:

return $this->hasOne('App\Phone', 'foreign_key');
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

定义相对的关联

要在 Phone 模型里定义相对的关联,可以使用 belongsTo 方法:

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

在上面的例子里, Eloquent 默认会使用 phones 数据库表的 user_id 字段查询关联。如果想要自己指定外键字段,可以在 belongsTo 方法里传入第二个参数:

class Phone extends Model {
    public function user()
    {
        return $this->belongsTo('App\User', 'local_key');
    }
}

除此之外,也可以传入第三个参数指定要参照上层数据库表的哪个字段:

class Phone extends Model {
    public function user()
    {
        return $this->belongsTo('App\User', 'local_key', 'parent_key');
    }
}

一对多

一对多关联的例子如,一篇 Blog 文章可能「有很多」评论。可以像这样定义关联:

class Post extends Model {
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

现在可以经由动态属性取得文章的评论:

$comments = Post::find(1)->comments;

如果需要增加更多条件限制,可以在调用 comments 方法后面通过链式查询条件方法:

$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first();

同样的,您可以传入第二个参数到 hasMany 方法更改默认的外键名称。以及,如同 hasOne 关联,可以指定本身的对应字段:

return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

定义相对的关联

要在 Comment 模型定义相对应的关联,可使用 belongsTo 方法:

class Comment extends Model {
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

多对多

多对多关联更为复杂。这种关联的例子如,一个用户( user )可能用有很多身份( role ),而一种身份可能很多用户都有。例如很多用户都是「管理者」。多对多关联需要用到三个数据库表: users , roles ,和 role_user 。 role_user 枢纽表命名是以相关联的两个模型数据库表,依照字母顺序命名,枢纽表里面应该要有 user_id 和 role_id 字段。

可以使用 belongsToMany 方法定义多对多关系:

class User extends Model {
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}

现在我们可以从 User 模型取得 roles:

$roles = User::find(1)->roles;

如果不想使用默认的枢纽数据库表命名方式,可以传递数据库表名称作为 belongsToMany 方法的第二个参数:

return $this->belongsToMany('App\Role', 'user_roles');

也可以更改默认的关联字段名称:

return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'foo_id');

当然,也可以在 Role 模型定义相对的关联:

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

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

虽然 posts 数据库表本身没有 country_id 字段,但 hasManyThrough 方法让我们可以使用 $country->posts 取得 country 的 posts。我们可以定义以下关联:

class Country extends Model {
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

如果想要手动指定关联的字段名称,可以传入第三和第四个参数到方法里:

class Country extends Model {
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
    }
}

多态关联

多态关联可以用一个简单的关联方法,就让一个模型同时关联多个模型。例如,您可能想让 photo 模型同时和一个 staff 或 order 模型关联。可以定义关联如下:

class Photo extends Model {

    public function imageable()
    {
        return $this->morphTo();
    }

}
class Staff extends Model {

    public function photos()
    {
        return $this->morphMany('App\Photo', 'imageable');
    }

}

class Order extends Model {

    public function photos()
    {
        return $this->morphMany('App\Photo', 'imageable');
    }

}

取得多态关联对象

现在我们可以从 staff 或 order 模型取得多态关联对象:

$staff = Staff::find(1);
foreach ($staff->photos as $photo)
{
    //
}

取得多态关联对象的拥有者

然而,多态关联真正神奇的地方,在于要从 Photo 模型取得 staff 或 order 对象时:

$photo = Photo::find(1);
$imageable = $photo->imageable;

Photo 模型里的 imageable 关联会返回 Staff 或 Order 实例,取决于这是哪一种模型拥有的照片。
多态关联的数据库表结构

为了理解多态关联的运作机制,来看看它们的数据库表结构:

staff
id - integer
name - string
orders
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string

要注意的重点是 photos 数据库表的 imageable_id 和 imageable_type。在上面的例子里, ID 字段会包含 staff 或 order 的 ID,而 type 是拥有者的模型类名称。这就是让 ORM 在取得 imageable 关联对象时,决定要哪一种模型对象的机制。

多态的多对多关联

Polymorphic Many To Many Relation Table Structure 多态的多对多关联数据库表结构

除了一般的多态关联,也可以使用多对多的多态关联。例如,Blog 的 Post 和 Video 模型可以共用多态的 Tag 关联模型。首先,来看看数据库表结构:

posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string

现在,我们准备好设定模型关联了。 Post 和 Video 模型都可以经由 tags 方法建立 morphToMany 关联:

class Post extends Model {
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
}

在 Tag 模型里针对每一种关联建立一个方法:

class Tag extends Model {
    public function posts()
    {
        return $this->morphedByMany('App\Post', 'taggable');
    }
    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }
}
Global Scopes
关联查询
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

环境配置

系统服务

哈希

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }