8. 使用Elequent建立表與表之間的關(guān)系 - 從零開(kāi)始學(xué)Laravel

從零學(xué)Laravel目錄列表

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

表的關(guān)聯(lián)關(guān)系

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

fillable

嗯, 出現(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í)候,可以去查這篇文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容