8. 使用Elequent創建表與表之間的關係

從零開始學Laravel目錄php

Eloquent提供了一個易於閱讀且很形象化的表和表之間的關係對應和調用,好比說,一條評論是屬於一個帖子的,一個帖子擁有不少的評論,一篇帖子和一個視頻頁同時擁有不少標籤,下面咱們來看看如何建立這些關係。html

咱們就以一篇帖子有不少的評論來舉列,帖子和評論是一對多的關係,咱們上一節已經創建了帖子的表posts, 下面咱們來創建評論表comments和評論的ModelComment, 在上一節咱們是經過下面兩條命令來創建migration文件和Model的laravel

// 創建帖子的migration文件
php artisan make:migration create_posts_table --create=posts

// 創建帖子Model
php  artisan make:model Post

咱們在創建評論表和模型的時候,用另外一種方法,咱們在創建Model的時候,只要加上-m參數,就能在創建Model的時候,同時生成migration文件了。(執行php artisan命令都是須要進入到項目的根目錄下執行的,之後我就不說這點了)sql

➜ php artisan make:model Comment -m                           
Model created successfully.
Created Migration: 2016_11_14_125930_create_comments_table

從上面咱們能夠看出,咱們建立模型的時候,laravel也幫咱們生成了表名爲模型名複數的migration文件,咱們打開這個migration文件,並更改up()函數以下:數據庫

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表的外鍵,在正式開發的時候,咱們須要作到外鍵約束,同時作到刪除的及聯操做,這裏咱們先不添加了。咱們將這個表執行到數據庫中數組

➜ php artisan migrate              
Migrated: 2016_11_14_125930_create_comments_table

如今在咱們的app目錄下,咱們已經有了Post.php和Comment.php兩個模型,下面咱們打開tinker緩存

➜ php artisan tinker 
Psy Shell v0.7.2 (PHP 7.0.12 — cli) by Justin Hileman
>>>

如下的代碼都在tinker中執行生成,咱們先來獲取第一條帖子數據:bash

>>> $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",
   }

咱們再來建立屬於帖子1的一條評論,咱們此次建立先手動的來維護外鍵(post_id):app

>>> $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",
       },
     ],
   }
>>>

下面咱們來經過Eloquent在各模型間創建表與表之間的對應關係,首先咱們先理一下,一個帖子會有不少評論,A post has many comments, 一個評論屬於一個帖子:a comment that belongs to a post,咱們打開Post.php,編寫一個comments()函數,意思是一個帖子有不少評論,因此注意這個comments()必定要寫成複數形式,寫代碼單詞的單複數對於易讀性來講很是的重要。編輯器

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

好的,咱們再進入到tinker中來測試下:
咱們先拿到第一個帖子:

>>> $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()->get(),也能夠這麼寫$post->comments;, 後面這種寫法,laravel文檔叫它動態屬性。

>>> $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",
       },
     ],
   }

用上面的方法拿出的數據實際上是Comment對象的一個集合(Collection),咱們能夠像操做數組同樣的操做這個集合,如:

>>> $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",
   }

固然,開發的時候不多像上面這麼作,由於Laravel給咱們提供了不少關於操做這個集合的方法,好比說,取集合中的第一個對象:

>>> $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",
    }

這裏有一個很是重要的地方,咱們來嘗試下面這條語句:

>>> $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();這兩條語句輸出的結果是同樣的,可是具體的操做卻不一樣,咱們假設帖子1有500條評論,那麼$post->comments->first();會先經過$post->comments從數據庫拿到這500條評論的數據放進集合,而後再從集合中獲取第一條數據。而$post->comments()->first();呢,當執行到$post->comments()時,它並無拿出這500條數據,這裏還處於一個查詢的階段,等到執行first()時,從數據庫只拿出一條數據,咱們應該使用哪一種寫法,你們應該就很明白了。曾有人說不要用ORM,太慢,可是不少慢的緣由不在於ORM, 而是不瞭解它,沒用好而已。

咱們再看看這兩條語句執行的原生SQL語句,咱們在tinker中讓每次執行語句的時候都打印出原生的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

咱們再來拿第一個帖子:

>>> $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",
   }
>>>

拿帖子的全部評論,本身看下sql語句:

>>> $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",
       },

咱們再來執行一次$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",
       },
     ],
   }

咱們發現此次沒有出現SQL語句,那是由於laravel已經緩存了此次查詢的結果,咱們再來看下$post的結果,它也被緩存了,而且咱們查詢的$post->comments的內容也被插入到這個對象中。

>>> $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, 你們在看下結果:

>>> $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",
   }

由於laravel會緩存查詢,因此你們在測試的時候必定要加上fresh()才能準確,咱們來看$post->comments->first(),執行的時候要加上fresh(),這很是的重要,千萬不要作了錯誤的測試誤導了你。

>>> $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語句,它是查詢出數據庫的全部的評論。

咱們在來看$post->comments()->first()這條語句:

>>> $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語句,用這種寫法,只會從數據庫拿出1條記錄,這裏我說這麼多,是由於我看見不少人在濫用動態屬性,因此咱們必定要注意這點。

好了,咱們如今看看在Comment模型中如何寫對應的關係呢?拿出以前咱們寫的英文句子:a comment that belongs to a post, 咱們打開Comment.php,

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    // 注意這裏post應該是單數形式
    public function post()
    {
       return $this->belongsTo('App\Post');
        
        // 若是你使用的是PhpStrom編輯器,你也能夠按下面這麼寫,這樣點擊能夠跳轉到對應的類文件中
        // return $this->belongsTo(Post::class);
    }
}

咱們從新打開tinker, 當你修改了代碼後,要從新打開tinker再測試,不然tinker執行的仍是修改前的代碼:

>>> $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",
   }
>>>

下面咱們來看下,咱們建立一條評論的時候,如何讓Eloquent的關聯關係給咱們自動維護外鍵:
咱們先建立一個$comment對象,設置它的內容:

>>> $comment = new App\Comment;
=> App\Comment {#622}
>>> $comment->content = 'Here is another comment.';
=> "Here is another comment."
>>>

如今咱們不用手動去設置post_id,咱們直接找到評論須要屬於的post,好比,仍是打算讓這條評論屬於第一個帖子:

>>> $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",
   }

下面咱們只須要經過Post的Comments()關聯去存儲屬於它的評論便可,會自動設置$post對象的ID到對應的評論的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,
   }

如今經過$post->comments;查看下,發現已經存在兩條評論了。

好了,上面的代碼都是在tinker中測試的,咱們如今進入了PostsController中,修改下show()函數:

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

好,咱們訪問下: http://localhost:8000/posts/1
表的關聯關係

咱們剛纔是用save()方法來存儲一條評論,如今咱們來試試使用create()方法來建立呢! 仍是打開tinker

fillable

嗯, 出現了匹配異常錯位,這是Laravel對使用create()update()這兩個函數作的保護機制,咱們知道create()update()能夠批量的設置表字段,若是不作一些保護錯位的話,可能會被人經過設置某些字段的值來串改你的數據,因此在Laravel中,你容許批量建立和修改的字段,你都要本身在模型中明確指定,咱們打開Comment.php, 爲Comment模型添加$fillbale屬性:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    // 容許使用create()和update()批量建立和更新的字段

    protected $fillable = ['content'];
    
    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}

下面從新啓動tinker,在執行一次,就能成功建立了

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,
   }
>>>

咱們在進入到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

加連接有不少方法,也有人會寫成一個函數,如<a href="{{ $post->path() }}">, 而後在Post模型層寫一個path()函數

public function path()
{
    return '/posts/' . $this->id;
}

不過把這個寫成函數我認爲也沒多大必要。好了,本節到這裏結束。

Eloquent關聯模型的用法你們應該知道了,可是除了一對多關係,還有一對一,多對多,多態一對多等,要用的時候,能夠去查這篇文檔

相關文章
相關標籤/搜索