iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 1
0
Modern Web

Laravel 8: For Beginners系列 第 9

短網址服務(Part2)

前言

昨天建構的服務還有些缺點:

  • 沒有驗證 URL 是否合法
  • 如果傳送了一個不存在的 ID,會產生錯誤
  • 容易被以遍歷的方式取得所有的短網址
    • 因為序列為 1、2、3 …,只要有心就能拿到整個系統的所有短網址
    • 舉例來說,PCHome 合作的穩達商貿運籌就存在這樣的問題
      • 拿現有宅配單號往前或往後就可以看到目前的配送狀態

重構

驗證 URL 是否合法

查詢官方文件,確定應該用哪些 validation rule。

<?php

// app/Http/Controller/ShorterController.php

// ...

public function __invoke(Request $request)
{
	$request->validate(['url' => 'required|url']);

	// ...
}
  • 使用 redirect()->to($url) 時,若 $url 不是一個合法的 URL(非以 http 開頭的)會被視為路徑。

確定用戶指定的 ID 存在

先看一下原本的程式碼

<?php

// app/Http/Controller/RedirectController.php

public function __invoke(int $id): RedirectResponse
{
	$url = Url::find($id)->url;
	
	return redirect()->to($url);
}

如果今天用戶傳進來的 $id = 1000000 ,而在資料庫中並沒有這個值。

  • Url::find($id) 會回傳 null
  • null->url 會直接產生錯誤

此時,我們有幾種方法可以避免發生這種狀況

<?php

// app/Http/Controller/RedirectController.php

public function __invoke(int $id): RedirectResponse
{
	$url = Url::findOrFail($id)->url;

	return redirect()->to($url);
}

Url::findOrFail() 找不到指定的 ID 時,會直接丟出 Illuminate\Database\Eloquent\ModelNotFoundException ,此時Laravel 會直接回應 404。

路由模型綁定:更簡潔的以 ID 取得 Model 方式

首先修改 Route,將 id 改為 url

<?php

// routes/web.php

Route::get('/{url}', 'RedirectController');

接著修改 Controller

<?php

// app/Http/Controllers/RedirectController.php

use App/Url;

class RedirectController extends Controller
{
	public function __invoke(Url $url)
	{
		return $url->url;
	}
}

得益於 Illuminate\Routing\Middleware\SubstituteBindings 這個中介層,它可以自動將路由參數轉為指定的 Model,而且當 Model 不存在時直接回應 404。

避免遍歷取得所有短網址

目前的短網址型式是 [http://127.0.0.1/{id}](http://127.0.0.1/{id}) ,很容易被遍歷 ID 以取得系統上所有的短網址。

我們需要有一個方法將 ID 轉為難以被逆推的字串,此時建議可以使用 Hashids 這個演算法,在 Laravel 中有 vinkla/laravel-hashids 這個套件。

$ composer require vinkla/hashids
  • 註:使用任何套件,請務必要閱讀相關文件

Controller

<?php

// app/Http/Controllers/ShorterController.php

// ...
use Vinkla\Hashids\Facades\Hashids;

public function __invoke(Request $request)
{
	$request->validate(['url' => 'required|url']);

	$url = Url::create(['url' => $request->url]);

	return Hashids::encode($url->id);
}
<?php

// app/Http/Controllers/RedirectController.php

// ...
use Vinkla\Hashids\Facades\Hashids;

public function __invoke(string $hashedIds)
{r
	$url = Url::findOrFail(Hashids::decode($hashedIds)[0] ?? null);

	return $url->url;
}

上面我有提到可以利用路由模型綁定的功能,那加入 Hashids 之後是否仍然可以自動綁定呢?

可以的,只要在 Eloquent ORM 上改寫 resolveRouteBinding() 就可以自定義解析邏輯。

<?php

// app/Url.php

// ...

class Url extends Model
{
	// ...

	public function resolveRouteBinding($value, $field = null)
	{
		return $this->find(Hashids::decode($value)[0] ?? null);
	}
}

上一篇
短網址服務(Part1)
下一篇
短網址服務(Part3)
系列文
Laravel 8: For Beginners14

尚未有邦友留言

立即登入留言