本节将使用用户创建博文来学习数据模型关联的有关知识。
一、Laravel常用知识总结
1.提示信息汉化
如果用Laravel原生的表单提示错误信息,则是英文的,如果需要中文,则需要修改resources/lang/en
的英文,这样比较麻烦,不过,我们可以使用github开源的汉化包,然后引入该文件夹,在config/app.php
配置文件里边修改语言包引入即可使用。
2.Carbon日期友好化处理
<span class="timestamp">
{{ $status->created_at->diffForHumans() }}
</span>
该方法的作用是将日期进行友好化处理,我们可以使用 tinker 来查看该方法的具体输出情况。
$ php artisan tinker
在 tinker 中输出第一位用户的创建时间如下。
>>> $created_at = App\Models\User::first()->created_at
=> Carbon\Carbon {#704
+"date": "1998-12-06 03:15:31.000000",
+"timezone_type": 3,
+"timezone": "UTC",
}
在 tinker 中调用 diffForHumans 方法来输出,结果如下。
>>> $created_at->diffForHumans()
=> "17 years ago"
我们发现 diffForHumans 为我们生成的时间是英文的,如果要使用中文时间,则需要对 Carbon 进行本地化设置。Carbon 是 PHP DateTime 的一个简单扩展,Laravel 将其默认集成到了框架中。
三、Eloquent:关联
数据表之间经常会互相进行关联。例如,一篇博客文章可能会有多条评论,或是一张订单可能对应一个下单客户。Eloquent
让管理和处理这些关联变得很容易,同时也支持多种类型的关联。
定义关联
你可在 Eloquent 模型类内将 Eloquent 关联定义为函数。因为关联像 Eloquent 模型一样也可以作为强大的 查询语句构造器
(数据库:查询构造器),定义关联为函数提供了强而有力的链式调用及查找功能。例如:
$user->posts()->where('active', 1)->get();
不过,在深入了解使用关联之前,先让我们来学习如何定义每个类型:
1.一对一
一对一关联是很基本的关联。例如一个 User 模型也许会对应一个 Phone。要定义这种关联,我们必须将 phone 方法放置于 User 模型上。phone 方法应该要返回基类 Eloquent 上的 hasOne
方法的结果:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 获取与指定用户互相关联的电话纪录。
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
传到 hasOne 方法里的第一个参数是关联模型的类名称。定义好关联之后,我们就可以使用 Eloquent 的动态属性
来获取关联纪录。动态属性
让你能够访问关联函数,就像他们是在模型中定义的属性:
$phone = User::find(1)->phone;
Eloquent 会假设对应关联的外键名称是基于模型名称的。在这个例子里,它会自动假设 Phone 模型拥有 user_id 外键。如果你想要重写这个约定,则可以传入第二个参数到 hasOne 方法里。
return $this->hasOne('App\Phone', 'foreign_key');
此外,Eloquent 的默认外键在上层模型的 id 字段会有个对应值。换句话说,Eloquent 会寻找用户的 id
字段与 Phone
模型的 user_id
字段的值相同的纪录。如果你想让关联使用 id 以外的值,则可以传递第三个参数至 hasOne 方法来指定你自定义的键:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
定义相对的关联
所以,我们可以从 User 访问到 Phone 模型。现在,让我们在 Phone 模型上定义一个关联,此关联能够让我们访问拥有此电话的 User。我们可以定义与 hasOne
关联相对应的 belongsTo
方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* 获取拥有此电话的用户。
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
2. 一对多
一个「一对多」关联使用于定义单个模型拥有任意数量的其它关联模型。例如,一篇博客文章可能会有无限多个评论。就像其它的 Eloquent 关联一样,可以通过放置一个函数到 Eloquent 模型上来定义一对多关联:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* 获取博客文章的评论。
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
切记,Eloquent 会自动判断 Comment
模型上正确的外键字段。按约定来说,Eloquent 会取用自身模型的「蛇形命名」后的名称,并在后方加上 _id
。所以,以此例来说,Eloquent 会假设 Comment 模型的外键是 post_id
。
一旦关联被定义,则可以通过 comments 属性来访问评论的集合。切记,因为 Eloquent 提供了「动态属性」,因此我们可以对关联函数进行访问,就像他们是在模型中定义的属性一样:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
定义相对的关联
现在我们已经能访问到所有文章的评论,让我们来接着定义一个通过评论访问上层文章的关联。若要定义相对于 hasMany
的关联,可在下层模型定义一个叫做 belongsTo
方法的关联函数:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 获取拥有此评论的文章。
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
3. 多对多
多对多关联要稍微比 hasOne
及 hasMany
关联复杂。如一个用户可能拥有多种身份,而一种身份能同时被多个用户拥有。举例来说,很多用户都拥有「管理者」的身份。要定义这种关联,需要使用三个数据表:users
、roles
和 role_user
。role_user 表命名是以相关联的两个模型数据表来依照字母顺序命名,并包含了 user_id 和 role_id 字段。
多对多关联通过编写一个在自身 Eloquent 类调用的 belongsToMany
的方法来定义。举个例子,让我们在 User 模型定义 roles 方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 属于该用户的身份。
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
定义相对的关联
要定义相对于多对多的关联,只需简单的放置另一个名为 belongsToMany
的方法到你关联的模型上。让我们接着以用户身份为例,在 Role 模型中定义 users 方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* 属于该身份的用户。
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
如你所见,此定义除了简单的参考 AppUser 模型外,与 User 的对应完全相同。因为我们重复使用了 belongsToMany
方法,当定义相对于多对多的关联时,所有常用的自定义数据表与键的选项都是可用的。
关联总结
1.一对一
$this->hasOne('App\Phone');
$this->belongsTo('App\User');
hasOne
关联相对应的 belongsTo
方法
2.一对多
$this->hasMany('App\Comment');
$this->belongsTo('App\Post');
hasMany
关联相对应的 belongsTo
方法
3.多对多
$this->belongsToMany('App\Role');
$this->belongsToMany('App\User');
belongsToMany
关联相对应的 belongsToMany
方法
4.查找关联
你可以查找 posts 关联并增加额外的条件至关联,像这样:
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
关联方法与动态属性
如果你不需要增加额外的条件至 Eloquent 的关联查找,则可以简单的像访问属性一样来访问关联。例如我们刚刚的 User 及 Post 模型示例,我们可以像这样来访问所有用户的文章:
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
预加载
当通过属性访问 Eloquent 关联时,该关联数据会被「延迟加载」。意味着该关联数据只有在你使用属性访问它时才会被加载。不过,Eloquent 可以在你查找上层模型时「预加载」关联数据。预加载避免了 N + 1 查找的问题。要说明 N + 1 查找的问题,可试想一个关联到 Author 的 Book 模型,如下所示:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* 获取编写该书的作者。
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
现在,让我们来获取所有书籍及其作者的数据:
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
上方的循环会运行一次查找并取回所有数据表上的书籍,接着每本书会运行一次查找作者的操作。因此,若存在着 25 本书,则循环就会执行 26 次查找:1 次是查找所有书籍,其它 25 次则是在查找每本书的作者。
很幸运地,我们可以使用预加载来将查找的操作减少至 2 次。可在查找时使用 with 方法来指定想要预加载的关联数据:
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
对于该操作则只会运行两次查找:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)