【Laravel實戰】表格CRUD從零開始開發實戰

【Laravel實戰】表格CRUD從零開始開發實戰

資料庫表格的CRUD功能是對新手來說比較麻煩的一個功能,但剛進入開發團隊往往都是會被分類這類的工作,因為這些工作的內容是比較固定的

這個單元我們就為大家介紹你該怎麼樣去用 Laravel 快速的撰寫出部落格的CRUD功能,但著重以手寫的方式來實作而不透過Voyager套件來自動生成。假如你對於如何自動生成CRUD感到有興趣的話,歡迎參考以下這篇文章

部落格從零開始用Voyager實作攻略

前置準備

關於環境建置以及專案設定的部分,這篇文章也不會特別說明,將直接從建立完一個專案並完成基本設定的情況下開始進行這篇CRUD實作引導。如果你不熟悉專案建置的朋友,請先參考以下文章

喜歡用命令列而作業環境是Mac:教你如何在Mac電腦上建置Valet開發環境

其他人:15分鐘無痛搞定Win&Mac的Laravel開發環境建置

此次實作所使用的版本是 Laravel 8.x,如果使用的是 Laravel 7 及以前的版本,路由部分的撰寫可能會有些不同,不熟悉的朋友請參考以下這篇文章來進行學習

15分鐘搞懂Laravel的路由機制

術語說明

所謂的CRUD,指的就是表格的新增.刪除.修改.查詢等功能,取其單字的頭分別為 Create . Read . Update . Delete ,簡稱就是由此而來

所謂的MVC,指的就是將網頁程式架構區分為 Model(資料邏輯) . View(視圖) . Controller(控制器)。程式碼不應該全都寫在一個類別或檔案裡頭,而是根據其工作而移到 MVC 這三塊的對應檔案內。Model 和 View 不會單獨運作而是配合 Controller 來提供服務

文章表格(posts)

https://metaschool.asia/storage/articles/laravel/tutorial/table/posts.png

Laravel 的命名慣例為表格名稱為複數型且為小寫,因為表格會包含多筆資料,比如這裡的 posts。而 Eloquent Model 則為單數型且首字母大寫,因為模型物件代表了某一筆資料,比如這裡的 Post


實作引導

Step 1.建立表格

為了方便日後快速重建表格(單元測試時很常用),這裏我們生成模型時並順道生成 Migration 來定義資料庫表格並方便生成。開啟 Terminal 並輸入以下指令

php artisan make:model Post -m

這行將會生成一個 Post 模型,位於 App\Models\Post.php ,以及 Migration 檔案,位於 database\migrations\xxxx_xx_xx_xxxxxx_create_posts_table.php

開啟模型檔案 Post.php ,進行以下的編輯

//app\Models\Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'content', 'status'];

    public function getStatusAttribute($value)
    {
        if ($value == 'draft') {
            return '草稿';
        } else if ($value == 'published') {
            return '上架';
        } else {
            return '未知狀態';
        }
    }
}

加入一個 Accessor(其他語言又稱為 getter),目的是當從表格取出文章的狀態時,比如 draft ,能夠轉為中文 "草稿"。再加上 $fillable 屬性,讓這三個欄位能夠通過 Mass Assignment 的驗證

接著開啟 Migration 檔案來定義表格欄位,這裡可參考上方的部落格表格來對照

//database\migrations\xxxx_xx_xx_xxxxxx_create_posts_table.php

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id'); //主鍵
        $table->string('title',50);
        $table->text('content');
        $table->string('status',10)->default('draft');
        $table->timestamps(); //created_at . updated_at 這兩個時間戳記
    });
}

執行剛撰寫的 Migration 檔案來生成表格,也就是 migrate,開啟 Terminal ,輸入以下指令:

php artisan migrate

關於 Migration 相關的內容,如果你不是太熟悉,歡迎參考我所撰寫的官方文件導讀:

學習Laravel Migration,看這一篇就夠了

Step 2.建立假資料

有了表格就可以準備資料囉,這次我們將使用 Seed 技術 結合 Factory 來快速生成多筆資料

首先,建立文章工廠 PostFactory.php,開啟 Terminal ,輸入以下指令

php artisan make:factory PostFactory

開啟 PostFactory.php 來設定欄位內容

//database\factories\PostFactory.php

<?php

namespace Database\Factories;

use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition()
    {
        return [
            'title' => $this->faker->name,
            'content' => $this->faker->realText(50),
            'status' => $this->faker->randomElement(array ('published','draft'))
        ];
    }
}

下一步來編輯 DatabaseSeeder.php ,在裡面使用剛新增的 PostFactory 來建立資料

//database\seeders\DatabaseSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        \App\Models\Post::truncate();
        \App\Models\Post::factory(10)->create();
    }
}

最後執行 db:seed 來建立假資料到表格中,開啟 Terminal,輸入以下指令:

php artisan db:seed

Step 3.建立路由

接下來我們編輯 CRUD 功能所需要使用的路由規則,幸運的是 Laravel 因為為我們準備好生成對應路由的方法,也就是 resource() ,這個方法將能夠一次生成 CRUD 相關總共 7 個路由規則

開啟 routes\web.php ,加入以下路由規則:

//routes\web.php

Route::resource('posts', 'App\Http\Controllers\PostController');

Step 4.建立控制器

寫完路由我們需要立即接著生成控制器,否則在進行任何的 Artisan 指令時都會因為找不到 PostController 而報錯

在這裏我們需要生成一個名為 PostController.php 的控制器,裡頭需要有 7 個方法,分別為 index() . show() . create() . store() . edit() . update() . destroy()

同樣的,Laravel 也為我們準備好了快速生成這個控制器的機制,而不需要一行行自己打。開啟 Terminal ,輸入以下指令

php artisan make:controller PostController --resource

建立完成你將會在 app/Http/Controllers 找到 PostController.php ,並且因為先前有加入選項

—resource 而找到 7 個方法

  • 如果你想知道先前的路由設定有無成功,這時候你可以輸入 php artisan route:list 來看到目前所有的路由規則

Step 5.建立視圖

接著這個環節,我們要製作前台顯示所需要的畫面,透過 Laravel 內建的 Blade 引擎。為了避免檔案太多顯得凌亂,建議將每個表格的CRUD視圖檔案放在各自的資料夾內,比如這裏的部落格相關視圖,我們將放在名為 posts 的資料夾,完整路徑為 resources/views/posts

以下是視圖檔案的所有內容,為求聚焦重點,並未寫入任何樣式,你可以根據自己需要來加入樣式設定或標籤

//resources\views\posts\index.blade.php

<a href="{{ route('posts.create') }}" class="btn btn-primary">新增</a>

@foreach($posts as $post)
        <h3><a href="{{ url('/posts/'.$post->id)}}">{{ $post->title }}</h3> 
        <span> {{ $post->status }} </span><br>
        <span> {{ $post->updated_at->diffForHumans() }} </span>
        <hr>
@endforeach

i這一段程式碼主要是用 foreach 標籤來呈現出多筆的文章資料,重點包含有:

  • url() 用來生成完整的網址,參數為URI路徑
  • $post→status 取狀態參數時會先呼叫先前寫在 Post.php 裡的 getStatusAttribute(),得到中文狀態名
  • deffForHumans() 是 Carbon 類別的功能,可以顯示口語化的時間呈現,比如 3 天前
//resources\views\posts\show.blade.php

<h1>{{ $post->title }}</h1> 
<span> {{ $post->status }} </span><br>
<span> {{ $post->created_at->diffForHumans() }} </span><br>
<span> {{ $post->updated_at->diffForHumans() }} </span><br>
<p>{!! $post->content !!}</p>

<a href="{{ route('posts.edit', $post->id) }}" class="btn btn-primary">編輯</a>
//resources\views\posts\_form.blade.php

<label for="title">標題</label>
<input type="text" name="title" value="@isset($post) {{ $post->title }} @endisset">

<label for="content">內容</label>
<textarea name="content" rows="4" cols="50">
@isset($post) {{ $post->content }} @endisset
</textarea>

<label for="status">狀態</label>
<select name="status">
    <option>請選擇狀態</option>
    <option value="draft">草稿</option>
    <option value="published">上架</option>
</select>

<input type="submit" class="btn btn-primary" value="送出">
//resources\views\posts\create.blade.php

<form action="{{ url('posts') }}" method="POST">
    @csrf
    @include('posts._form')
</form>

需要注意 form 如果沒有特別指定方法,預設將會是 GET 而非 POST

關於跨站攻擊防護(CSRF) 的相關知識如果不是太了解,請參考以下這篇文章

15分鐘搞懂LARAVEL的CSRF防護機制

//resources\views\posts\edit.blade.php

<div align="center">
    <form action="{{ route('posts.destroy', $post->id) }}" method="POST">
        @csrf
        @method('DELETE')
         <button class="btn btn-danger">刪除</button>
     </form>
 </div>

<form action="{{ url('posts/'.$post->id) }}" method="POST">
        @method('PUT')
        @csrf
        @include('posts._form')
</form>

route() 幫助函式可用來根據所傳入的路由名稱,比如這例的 "posts.destroy" 來生成對應的完整網址

HTML表單並不支持 PUT . PATCH 以及 DELETE ,因此需要在 method 先設為 POST ,然後再透過 @method() 來指定方法

Step 6.編輯控制器

我們已經有了 CRUD 顯示所用的視圖,接下來就要使用控制器來實踐 MVC 架構。以下是 PostController.php 的完整內容

//app\Http\Controllers\PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $posts = Post::get(); //取得表格的所有資料
        return view('posts.index', compact('posts'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('posts.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //建立 posts 表格內的一筆新資料
        $post = Post::create($request->only(['title', 'content', 'status']));

        if ($post) {
            return redirect()->route('posts.show', $post->id);
        } else {
            return back();
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $post = Post::findOrFail($id); //取得主鍵為 $id 的資料
        return view('posts.show', compact('post'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $post = Post::findOrFail($id);
        return view('posts.edit', compact('post'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $post = Post::findOrFail($id);
        //更新指定資料
        $post->update($request->only(['title', 'content', 'status']));
        return redirect()->route('posts.show', $id);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $post = Post::findOrFail($id);
        //刪除指定資料
        $post->delete();

        return redirect()->route('posts.index');
    }
}

$request 物件可取得前端所傳回來的輸入項內容。all()可以取得所有輸入項內容,而 only()則只取得指定輸入項的內容,較為安全

Step 7.測試功能是否正常

當一切準備完畢,就可以開啟瀏覽器,轉到專案網址,通常是http://localhost/blog/public/post 來訪問文章總覽頁

後話

終於我們完成了所有的 CRUD 功能了,但其實中間跳過了很多環節,這些地方並非不重要,只是這個單元我們只聚焦在 CRUD 功能上。比如你可能會發現文章的狀態在編輯頁面並不會正確的呈現當前狀態,再比如過程中我們也沒有針對表單的內容進行驗證

關於讓下拉選單能夠正確呈現資料的值有好幾種做法,我最為推薦的就是使用 LaravelCollective 這個套件來實作下拉選單,這個套件能把產生下拉選單以及設定預設值變得極為簡單,你可以參考下面這個網頁

HTML v6.x

至於表單驗證的部分,又是一個不小的課題,我希望之後能用一個單元好好的來說明,就不先帶大家越級打怪了。你只要知道 Laravel 已經幫你把驗證功能都做好了,你不用再造這個輪子,只要會裝就可以囉

希望這個單元對於想學習如何自己寫出 CRUD 的夥伴有所幫助,下課囉!


分享這篇文章:

關聯文章:

訂閱電子報,索取 Laravel 學習手冊

價值超過 3000 元,包含常用 Laravel 語法與指令!

Laravel 百萬年薪特訓營

從最基礎的 PHP 語法開始,包含所有你該知道的網頁基礎知識,連同 Laravel 從零開始一直到實戰,最後還將告訴你如何找好工作,讓你及早擁有百萬年薪