Eloquent提供了一個(gè)易于閱讀且很形象化的表和表之間的關(guān)系對(duì)應(yīng)和調(diào)用,比如說(shuō),一條評(píng)論是屬于一個(gè)帖子的,一個(gè)帖子擁有很多的評(píng)論,一篇帖子和一個(gè)視頻頁(yè)同時(shí)擁有很多標(biāo)簽,下面我們來(lái)看看如何創(chuàng)建這些關(guān)系。
我們就以一篇帖子有很多的評(píng)論來(lái)舉列,帖子和評(píng)論是一對(duì)多的關(guān)系,我們上一節(jié)已經(jīng)建立了帖子的表posts, 下面我們來(lái)建立評(píng)論表comments和評(píng)論的ModelComment, 在上一節(jié)我們是通過(guò)下面兩條命令來(lái)建立migration文件和Model的
// 建立帖子的migration文件
php artisan make:migration create_posts_table --create=posts
// 建立帖子Model
php artisan make:model Post
我們?cè)诮⒃u(píng)論表和模型的時(shí)候,用另一種方法,我們?cè)诮odel的時(shí)候,只要加上-m參數(shù),就能在建立Model的時(shí)候,同時(shí)生成migration文件了。(執(zhí)行php artisan命令都是需要進(jìn)入到項(xiàng)目的根目錄下執(zhí)行的,以后我就不說(shuō)這點(diǎn)了)
? php artisan make:model Comment -m
Model created successfully.
Created Migration: 2016_11_14_125930_create_comments_table
從上面我們可以看出,我們創(chuàng)建模型的時(shí)候,laravel也幫我們生成了表名為模型名復(fù)數(shù)的migration文件,我們打開(kāi)這個(gè)migration文件,并更改up()函數(shù)如下:
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('post_id')->unsigned()->index();
$table->text('content');
$table->timestamps();
});
}
上面的post_id是posts表的外鍵,在正式開(kāi)發(fā)的時(shí)候,我們需要做到外鍵約束,同時(shí)做到刪除的及聯(lián)操作,這里我們先不添加了。我們將這個(gè)表執(zhí)行到數(shù)據(jù)庫(kù)中
? php artisan migrate
Migrated: 2016_11_14_125930_create_comments_table
現(xiàn)在在我們的app目錄下,我們已經(jīng)有了Post.php和Comment.php兩個(gè)模型,下面我們打開(kāi)tinker
? php artisan tinker
Psy Shell v0.7.2 (PHP 7.0.12 — cli) by Justin Hileman
>>>
以下的代碼都在tinker中執(zhí)行生成,我們先來(lái)獲取第一條帖子數(shù)據(jù):
>>> $post = App\Post::first();
=> App\Post {#636
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
我們?cè)賮?lái)創(chuàng)建屬于帖子1的一條評(píng)論,我們這次創(chuàng)建先手動(dòng)的來(lái)維護(hù)外鍵(post_id):
>>> $comment = new App\Comment;
=> App\Comment {#625}
>>> $comment->content = 'Some comment for the post';
=> "Some comment for the post"
>>> $comment->post_id = 1;
=> 1
>>> $comment->save();
=> true
>>> App\Comment::all();
=> Illuminate\Database\Eloquent\Collection {#640
all: [
App\Comment {#641
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
},
],
}
>>>
下面我們來(lái)通過(guò)Eloquent在各模型間建立表與表之間的對(duì)應(yīng)關(guān)系,首先我們先理一下,一個(gè)帖子會(huì)有很多評(píng)論,A post has many comments, 一個(gè)評(píng)論屬于一個(gè)帖子:a comment that belongs to a post,我們打開(kāi)Post.php,編寫(xiě)一個(gè)comments()函數(shù),意思是一個(gè)帖子有很多評(píng)論,所以注意這個(gè)comments()一定要寫(xiě)成復(fù)數(shù)形式,寫(xiě)代碼單詞的單復(fù)數(shù)對(duì)于易讀性來(lái)說(shuō)非常的重要。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function comments()
{
return $this->hasMany('App\Comment');
}
}
好的,我們?cè)龠M(jìn)入到tinker中來(lái)測(cè)試下:
我們先拿到第一個(gè)帖子:
>>> $post = App\Post::first();
=> App\Post {#636
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
我們拿取這個(gè)帖子的所有評(píng)論,我們可以這么寫(xiě)$post->comments()->get(),也可以這么寫(xiě)$post->comments;, 后面這種寫(xiě)法,laravel文檔叫它動(dòng)態(tài)屬性。
>>> $post->comments;
=> Illuminate\Database\Eloquent\Collection {#633
all: [
App\Comment {#637
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
},
],
}
用上面的方法拿出的數(shù)據(jù)其實(shí)是Comment對(duì)象的一個(gè)集合(Collection),我們可以像操作數(shù)組一樣的操作這個(gè)集合,如:
>>> $post->comments[0];
=> App\Comment {#637
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
}
當(dāng)然,開(kāi)發(fā)的時(shí)候很少像上面這么做,因?yàn)長(zhǎng)aravel給我們提供了很多關(guān)于操作這個(gè)集合的方法,比如說(shuō),取集合中的第一個(gè)對(duì)象:
>>> $post->comments->first();
=> App\Comment {#637
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
}
這里有一個(gè)非常重要的地方,我們來(lái)嘗試下面這條語(yǔ)句:
>>> $post->comments()->first();
=> App\Comment {#651
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
}
我們看到$post->comments->first();和$post->comments()->first();這兩條語(yǔ)句輸出的結(jié)果是一樣的,但是具體的操作卻不同,我們假設(shè)帖子1有500條評(píng)論,那么$post->comments->first();會(huì)先通過(guò)$post->comments從數(shù)據(jù)庫(kù)拿到這500條評(píng)論的數(shù)據(jù)放進(jìn)集合,然后再?gòu)募现蝎@取第一條數(shù)據(jù)。而$post->comments()->first();呢,當(dāng)執(zhí)行到$post->comments()時(shí),它并沒(méi)有拿出這500條數(shù)據(jù),這里還處于一個(gè)查詢的階段,等到執(zhí)行first()時(shí),從數(shù)據(jù)庫(kù)只拿出一條數(shù)據(jù),我們應(yīng)該使用哪種寫(xiě)法,大家應(yīng)該就很明白了。曾有人說(shuō)不要用ORM,太慢,但是很多慢的原因不在于ORM, 而是不了解它,沒(méi)用好而已。
我們?cè)倏纯催@兩條語(yǔ)句執(zhí)行的原生SQL語(yǔ)句,我們?cè)?code>tinker中讓每次執(zhí)行語(yǔ)句的時(shí)候都打印出原生的SQL,可以這么做:
? php artisan tinker
Psy Shell v0.7.2 (PHP 7.0.12 — cli) by Justin Hileman
>>> DB::listen(function ($query) { var_dump($query->sql); });
=> null
我們?cè)賮?lái)拿第一個(gè)帖子:
>>> $post = App\Post::first();
string(29) "select * from "posts" limit 1"
=> App\Post {#637
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
>>>
拿帖子的所有評(píng)論,自己看下sql語(yǔ)句:
>>> $post->comments;
string(92) "select * from "comments" where "comments"."post_id" = ? and "comments"."post_id" is not null"
=> Illuminate\Database\Eloquent\Collection {#623
all: [
App\Comment {#638
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
},
我們?cè)賮?lái)執(zhí)行一次$post->comments;
>>> $post->comments;
=> Illuminate\Database\Eloquent\Collection {#623
all: [
App\Comment {#638
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
},
],
}
我們發(fā)現(xiàn)這次沒(méi)有出現(xiàn)SQL語(yǔ)句,那是因?yàn)閘aravel已經(jīng)緩存了這次查詢的結(jié)果,我們?cè)賮?lái)看下$post的結(jié)果,它也被緩存了,并且我們查詢的$post->comments的內(nèi)容也被插入到這個(gè)對(duì)象中。
>>> $post
=> App\Post {#637
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
comments: Illuminate\Database\Eloquent\Collection {#623
all: [
App\Comment {#638
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
},
],
},
}
如果我們刷新獲取下$post,在打印$post, 大家在看下結(jié)果:
>>> $post = $post->fresh();
string(44) "select * from "posts" where "id" = ? limit 1"
=> App\Post {#643
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
>>> $post
=> App\Post {#643
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
因?yàn)閘aravel會(huì)緩存查詢,所以大家在測(cè)試的時(shí)候一定要加上fresh()才能準(zhǔn)確,我們來(lái)看$post->comments->first(),執(zhí)行的時(shí)候要加上fresh(),這非常的重要,千萬(wàn)不要做了錯(cuò)誤的測(cè)試誤導(dǎo)了你。
>>> $post->fresh()->comments->first();
string(44) "select * from "posts" where "id" = ? limit 1"
string(92) "select * from "comments" where "comments"."post_id" = ? and "comments"."post_id" is not null"
=> App\Comment {#630
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
}
看上面的第二條SQL語(yǔ)句,它是查詢出數(shù)據(jù)庫(kù)的所有的評(píng)論。
我們?cè)趤?lái)看$post->comments()->first()這條語(yǔ)句:
>>> $post->fresh()->comments()->first();
string(44) "select * from "posts" where "id" = ? limit 1"
string(100) "select * from "comments" where "comments"."post_id" = ? and "comments"."post_id" is not null limit 1"
=> App\Comment {#644
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
}
我們看第2條SQL語(yǔ)句,用這種寫(xiě)法,只會(huì)從數(shù)據(jù)庫(kù)拿出1條記錄,這里我說(shuō)這么多,是因?yàn)槲铱匆?jiàn)很多人在濫用動(dòng)態(tài)屬性,所以我們一定要注意這點(diǎn)。
好了,我們現(xiàn)在看看在Comment模型中如何寫(xiě)對(duì)應(yīng)的關(guān)系呢?拿出之前我們寫(xiě)的英文句子:a comment that belongs to a post, 我們打開(kāi)Comment.php,
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
// 注意這里post應(yīng)該是單數(shù)形式
public function post()
{
return $this->belongsTo('App\Post');
// 如果你使用的是PhpStrom編輯器,你也可以按下面這么寫(xiě),這樣點(diǎn)擊可以跳轉(zhuǎn)到對(duì)應(yīng)的類(lèi)文件中
// return $this->belongsTo(Post::class);
}
}
我們重新打開(kāi)tinker, 當(dāng)你修改了代碼后,要重新打開(kāi)tinker再測(cè)試,否則tinker執(zhí)行的還是修改前的代碼:
>>> $comment = App\Comment::first();
=> App\Comment {#636
id: "1",
post_id: "1",
content: "Some comment for the post",
created_at: "2016-11-15 01:07:53",
updated_at: "2016-11-15 01:07:53",
}
>>> $comment->post;
=> App\Post {#637
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
>>>
下面我們來(lái)看下,我們創(chuàng)建一條評(píng)論的時(shí)候,如何讓Eloquent的關(guān)聯(lián)關(guān)系給我們自動(dòng)維護(hù)外鍵:
我們先創(chuàng)建一個(gè)$comment對(duì)象,設(shè)置它的內(nèi)容:
>>> $comment = new App\Comment;
=> App\Comment {#622}
>>> $comment->content = 'Here is another comment.';
=> "Here is another comment."
>>>
現(xiàn)在我們不用手動(dòng)去設(shè)置post_id,我們直接找到評(píng)論需要屬于的post,比如,還是打算讓這條評(píng)論屬于第一個(gè)帖子:
>>> $post = App\Post::first();
=> App\Post {#639
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
下面我們只需要通過(guò)Post的Comments()關(guān)聯(lián)去存儲(chǔ)屬于它的評(píng)論即可,會(huì)自動(dòng)設(shè)置$post對(duì)象的ID到對(duì)應(yīng)的評(píng)論的post_id
>>> $post->comments()->save($comment);
=> App\Comment {#622
content: "Here is another comment.",
post_id: 1,
updated_at: "2016-11-15 02:38:01",
created_at: "2016-11-15 02:38:01",
id: 2,
}
現(xiàn)在通過(guò)$post->comments;查看下,發(fā)現(xiàn)已經(jīng)存在兩條評(píng)論了。
好了,上面的代碼都是在tinker中測(cè)試的,我們現(xiàn)在進(jìn)入了PostsController中,修改下show()函數(shù):
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
然后建立show.blade.php視圖層,輸入以下代碼:
@extends('layout')
@section('content')
<h1>{{ $post->title }}</h1>
<ul>
@foreach ($post->comments as $comment)
<li>{{ $comment->content }}</li>
@endforeach
</ul>
@stop
好,我們?cè)L問(wèn)下: http://localhost:8000/posts/1

我們剛才是用save()方法來(lái)存儲(chǔ)一條評(píng)論,現(xiàn)在我們來(lái)試試使用create()方法來(lái)創(chuàng)建呢! 還是打開(kāi)tinker

嗯, 出現(xiàn)了匹配異常錯(cuò)位,這是Laravel對(duì)使用create()和update()這兩個(gè)函數(shù)做的保護(hù)機(jī)制,我們知道create()和update()可以批量的設(shè)置表字段,如果不做一些保護(hù)錯(cuò)位的話,可能會(huì)被人通過(guò)設(shè)置某些字段的值來(lái)串改你的數(shù)據(jù),所以在Laravel中,你允許批量創(chuàng)建和修改的字段,你都要自己在模型中明確指定,我們打開(kāi)Comment.php, 為Comment模型添加$fillbale屬性:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
// 允許使用create()和update()批量創(chuàng)建和更新的字段
protected $fillable = ['content'];
public function post()
{
return $this->belongsTo(Post::class);
}
}
下面重新啟動(dòng)tinker,在執(zhí)行一次,就能成功創(chuàng)建了
Psy Shell v0.7.2 (PHP 7.0.12 — cli) by Justin Hileman
>>> $post = App\Post::first();
=> App\Post {#636
id: "1",
title: "My New Post Title",
content: "new post content",
created_at: "2016-11-14 07:22:32",
updated_at: "2016-11-14 07:22:32",
}
>>> $post->comments()->create(['content' => 'Yet another comment about this post']);
=> App\Comment {#640
content: "Yet another comment about this post",
post_id: 1,
updated_at: "2016-11-15 03:08:25",
created_at: "2016-11-15 03:08:25",
id: 3,
}
>>>
我們?cè)谶M(jìn)入到posts/index.balde.php中,我們給帖子都加上鏈接:
@extends('layout')
@section('content')
<h1>所有的帖子</h1>
@foreach ($posts as $post)
<h2><a href="posts/{{ $post->id }}">{{ $post->title }}</a></h2>
<p>{{ $post->content }}</p>
@endforeach
@stop
加鏈接有很多方法,也有人會(huì)寫(xiě)成一個(gè)函數(shù),如<a href="{{ $post->path() }}">, 然后在Post模型層寫(xiě)一個(gè)path()函數(shù)
public function path()
{
return '/posts/' . $this->id;
}
不過(guò)把這個(gè)寫(xiě)成函數(shù)我認(rèn)為也沒(méi)多大必要。好了,本節(jié)到這里結(jié)束。
Eloquent關(guān)聯(lián)模型的用法大家應(yīng)該知道了,但是除了一對(duì)多關(guān)系,還有一對(duì)一,多對(duì)多,多態(tài)一對(duì)多等,要用的時(shí)候,可以去查這篇文檔