A+ A A-

【Laravel】以 slug 取代 id,讓網址更好閱讀

以別名取代 id,讓網址更容易閱讀

  以下是某個網站的網址:

https://www.example.com/blog/3

  從網址可以大致知道該頁面會是網站部落格的第 3 篇文章或是 id 為 3 的文章,在未瀏覽之前其實不曉得頁面的內容。那麼如果網址換成以下格式:

https://www.example.com/blog/why-i-make-blog

  這樣就能從網址知道該頁面應該是敘述為何製作部落格的文章,是不是差別很大?

  我的 side project 之一是建置部落格系統,對「以別名而非 id 方式顯示文章網址」這件事特別注重,不過就本文撰寫的當下,實做上述功能的 Laravel 中文教學文章很少,會不會是因為實做這個功能實在太簡單?

  我需要這個功能,然後搜尋網路文章尋找解答,透過一段時間的嘗試之後完成實做,所以覺得有必要把這個功能的實做流程紀錄下來,給自己以及其他有需要的人做個參考。

專案基本資料

  • Laravel 版本: 8.x
  • 運作平台:macbook air 2020(intel),macOS 11.6,Nginx + PHP v7.4 + MySQL 5.7 以 valet 運行。

準備

  在資料表規劃時我設定名為「slug」的文字欄位存放內容物件(文章、分類、標籤等)的別名,以存放文章的資料表來說,migration 中的 up 方法會是以下內容:

public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('category_id')->constrained();
            $table->string('title');
            $table->string('slug')->unique();
            $table->string('cover_image');
            $table->string('introtext');
            $table->text('content');
            $table->foreignId('user_id')->constrained();
            $table->integer('sort')->default(0);
            $table->enum('status',['pending', 'published', 'unpublished']);
            $table->enum('featured',['yes','no']);
            $table->timestamps();
        });
    }

  如果尚未建立別名欄位則可以參考以下指令建立 mirgation:

 php artisan make:migration add_slug_column_to_posts_table --table=posts

  up 與 down 方法參考如下:

public function up()
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('slug')->nullable();
    });
}

public function down()
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('slug');
    });
}

  執行 php artisan migrate 完成資料欄位加入。

下載套件

  在終端機視窗下載套件

composer require cviebrock/eloquent-sluggable

在 Model 加入 slug 敘述

  在需要使用別名(slug)的 Model 引用套件,以文章相關的 Model「Post」來說會是這樣子:

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;
use Illuminate\\Database\\Eloquent\\Model;
use TCG\\Voyager\\Traits\\Translatable;
use Cviebrock\\EloquentSluggable\\Sluggable;

class Post extends Model
{
    use HasFactory, Translatable ,Sluggable;

    protected $table = 'posts';
    protected $fillable = [
        'category_id',
        'title',
        'slug',
        'cover_image',
        'introtext',
        'content',
        'user_id',
        'sort',
        'status',
        'featured',
    ];

    public function sluggable()
    {
        return [
            'slug' => [
                'source' => 'slug'
            ]
        ];
    }

    public function getRouteKeyName()
    {
        return 'slug';
    }

  透過 use Cviebrock\\EloquentSluggable\\Sluggable; 加入 Sluggable,然後就可以在 Post 類別中使用了。我新增 sluggable() 方法,以資料表「slug」欄位作為 slug 來源,並建立 getRouteKeyName() 方法要求路由改成回傳 slug。

  到此為止就可以利用 slug 欄位內容作為網址的一部分。

Controller、Blade 頁面與 web.php

  Model 部分完成後就可以到 Contoller 撰寫顯示內容的方法,例如處理 blog 單篇文章方法如下:

public function renderBlogPage($slug){
    $post = Post::where('slug', $slug)->first();
    return view('blog_page', compact('post'));
}

  在顯示文章內容的 Blade template(blog_page.blade.php)中以 Eloquent 語法使用資料庫資源,以帶自身連結的標題為例:

<h2 class="entry-title">
    <a href="{{ $post->slug }}">{{ $post->title }}</a>
</h2>

  管理網頁路由的 web.php 的語法參考:

Route::get('/blog/{slug}', 'App\\Http\\Controllers\\SiteController@renderBlogPage');

參考資料