iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 4
0
Modern Web

RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄系列 第 4

Day 04. DB 三劍客 Migration, Model 和 Resource

雖然系統中沒有資料也可以正常運作,不過個人還是習慣先準備好資料,再一層層往上建起來,若跟大家習慣不同還請多包涵

在 Laravel 中,資料庫相關的類別主要有下列三項:

  • Migration: 操作資料表的行為紀錄檔
  • Model: table entity
  • Resource: 針對 model object 轉換成 php array

生成檔案(指令)

  1. 在 cmd 輸入指令生成 model 和對應 schema
php artisan make:model <Model 名稱> -m
  • 指令中的 -m 帶表順帶生成 migration 檔案
  • model: 位於 app\,筆者習慣生成一個 Models 資料夾存放,若有移動請修改檔案內的 namespace
  • migration: 位於 database\migrations\
  1. 在 cmd 輸入指令生成與 model 對應的 resource
php artisan make:resource <Model 名稱>
  • 位於 app\Http\Resources\

生成檔案(範例)

Post:

php artisan make:model Post -m
php artisan make:resource Post

Migration

下面是我們所產出的 post migration,其中 class name 的語意明確,告訴我們這個 class 就是用來產生 posts 這張表。

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}
  • 重點整理如下:
    01. function up(): 當執行指令 php artisan migrate 時會對資料表進行的操作。
    02. function down(): 當使用 Laravel Facades "Schema" 刪除資料表時候執行
    03. Laravel 資料表欄位習慣以 snake_case 方式命名
    04. schema 內各種欄位類型與設定可以參考官方列表,這裡舉例幾個常用的:

    // 加入 `created_at` 和 `updated_at` 兩個欄位,當資料新增或修改時,會自動帶入時間值
    $table->timestamps();
    
    // 加入 `deleted_at` 作為 soft deletes 欄位
    $table->softDeletes();
    
    // 最後呼叫 nullable() 欄位設為空值
    $table->string('欄位名稱')->nullable();
    
    // 最後呼叫 unique() 指定單一欄位為唯一
    $table->string('欄位名稱')->unique();
    
    // 複合欄位指定為唯一
    $table->unique([ '欄位名稱 01', '欄位名稱 02', ... ]);
    
    // 指定為 foreign key
    // 1. 先建立欄位
    $table->integer('欄位名稱')->unsigned();
    // 2. 指定 foreign key 資料
    $table->foreign('欄位名稱')->references('對應欄位')->on('對應 table');
    
    1. 設計完 schema 之後在 cmd 輸入下列指令進行對資料庫操作(建立資料表)
    php artisan migrate
    
  • posts table 範例設計

Schema::create('posts', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->integer('user_id')->unsigned();
    $table->string('title');
    $table->text('content');
    $table->dateTime('published_at');
    $table->timestamps();
    $table->softDeletes();
    $table->foreign('user_id')->references('id')->on('users');
});

在使用 migration 的經驗上,初始的建立以及各種欄位設定,code-first 的作法個人覺得滿好用的,但在修改欄位後要更新 schema 的部分,一開始會因為習慣的問題感覺沒有很好用。舉例來說,假設 posts 已經建立並存在資料,這個時候假設我們要刪除 published_at 欄位,所需要進行的步驟為:

  1. 新建立一個 migration 類別 (drop_publish_at_to_users 為 migration 類別名稱,可自行命名)
php artisan make:migration drop_publish_at_to_users
  1. 寫入對 table 的操作
// ....
public function up()
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('published_at');
    });
}
// ...
  1. 執行 migrate
php artisan migrate

從上面的流程可以知道,migration 其實是一個針對 db 操作的一個 code-first 的紀錄檔,他會轉為一串 SQL 語法,這大概也是為甚麼每個 migration 檔案名稱都有時間戳記,只是像我這種資質駑鈍的新手村菜鳥,每一次都要有一個檔案紀錄,一開始也真的滿不習慣的,但用久了就認命了 XD。

Eloquent Model

預設產出的 Model 檔如下,在沒有任何設定上,即可直接使用、存取資料表資料。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    //
}

官網內有更詳細的介紹各種客製的資料,包括 資料表名稱 ($table)主鍵 ($primaryKey), 是否使用建立/更新日期 ($timestamps)連線 ($connection) ...等,這邊就不贅述。

客製方法

上述基本設定之外,可以在 model 中客製一些方法,讓我們更容易使用。例如在之前 post migration 中我們定義了 published_at 欄位,在某些情況下我們會需要知道該篇 post 當下是否已經上架,這時候我們會在業務邏輯的部分寫如下面的判斷:

$today = Carbon::today();
$isPublished = $today->gte($post->published_at);
if ($isPublished) {
    // ...

假若這樣的判斷是很常用在各種業務邏輯當中,我們就可以將此邏輯寫入 model 當中:

class Post extends Model
{
    public function isPublished() {
        $today = Carbon::today();
        return $today->gte($this->published_at);
    }

實際運用的時候就可以簡化如下,也更方便維護

if ($post->isPublished) {
    // ...

關聯

寫資料庫哪有不複雜的關聯呢! 預設情況下,model 的物件,並不會將關聯的 model 資料一起帶出來。以我們目前的 Post 物件來說,只有會有 user_id 的資料,但並不會把該 user 的資料一起帶出來。因此若有需求,可以在 model 裡面加上關聯的 function:

public function user() {
    return $this->belongsTo('App\Models\User');
}

實際運用上就相對更方便

// $post->user 的結果會是 User model 的實例
$userNameOfPost = $post->user->name;

關聯重點整理:

  • 建立關聯 function 的時候,預設上會需要有以 function name + _id 所組成的 foreign key,例如 function user() 預設要有 user_id foreign key。若有特殊鍵值,需要額外設定。
  • 關聯 function 內需要提供對應的 model ('App\Models\User') 作為參數。
  • 使用上,是以呼叫 property 的方式,而不是呼叫 function 帶出資料。
  • 更多不同的關聯建立請參考官網

API Resource

在 Laravel 官方文件中,Resource 是為了 API 將 model 資料輸出時轉換為 php array 時候用。

預設產出的 resource 類別會有個 toArray 方法讓我們進行改寫:

class Post extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

Resource 會帶有 model 本身的資料,因此我們改寫的時可以依照 API 需求自行調整要輸出的結果 (尤其是將 snake_case 轉換為 camel case 很好用),例如 Post Resource 我們改寫如下:

public function toArray($request)
{
    return [
        "id" => $this->id,
        "title" => $this->title,
        "content" => $this->content,
        "updatedAt" => $this->updated_at,
        "author" => this->user,
    ];

在回傳結果前,我們就可以利用 Resource 類別將 model 資料轉換:

// ...
use App\Http\Resources\Post as PostResource;
// ...

public function getPost() {
    // ...
    return new UserResource($post);

以上就是資料庫相關的三個主要類別,雖然有點多,但是頭過身就過~ 明天在進入 Repository 和 Service 之前,先來看看 Laravel 的 injection (依賴注入) 吧!


上一篇
Day 03. Laravel 專案開箱
下一篇
Day 05. 一不小心就會扯遠的依賴注入 (DI)
系列文
RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄31

尚未有邦友留言

立即登入留言