使用Hashids取代increment ID
一直以來該使用increment ID
或uuid
當作是資料庫主鍵一直是兩派爭論不休的議題。
increment ID
的優點相當的顯而易見,使用上簡易方便並具有佔用空間小及順序性等優點,個人覺得比較容易注意到的缺點大概就是id容易被外人進行猜測,雖然說這個部分應該是系統開發者應注意的事,但總覺得increment ID
這種顯示方式醜醜的(或應該說太漂亮?)
此外increment ID
還有一個缺點就是當你有資料轉移或合併的需求時,increment ID
會讓你很容易發生id重複的問題。
反觀uuid
他最大的特點就是你光從id上來看無法看出任何資訊(例如:順序),他就長得像這樣: 05177f3c-bdf7-4b96-927d-f6f636175a27
,通常他是128bit長的數字,並且用16進制表示。缺點是在很多情形下作為主鍵建立索引查詢效率低。
因此在我最近的一個專案中,我嘗試使用Hashids
當作是我的替代方案,我資料表的主鍵依然是使用increment ID
,但是在id暴露給外部時,他會經由Hashids
幫你算成像GlaHquq0
的結果,他是可以解密的,所以名字上雖然叫做Hashids
,但本質和hash一點關係也沒有。
Hashids
在各個程式語言都有實作library,根據官網上的實作原理,Hashids
是一套將數字轉換為Hex結果的演算法,並且支援數字陣列及salt,以下為部分Hashids
在javascript上的示意code。
function toHex(input) {
var hash = "",
alphabet = "0123456789abcdef",
alphabetLength = alphabet.length;
do {
hash = alphabet[input % alphabetLength] + hash;
input = parseInt(input / alphabetLength, 10);
} while (input);
return hash;
}
在Laravel中實作也非常簡單,直接安裝hashids的package
composer require vinkla/hashids
我先將encode和decode寫成helper function,在其他地方要用到時能更方便呼叫
<?php
if (! function_exists('hash_encode')) {
/**
* Get result of Hashids encode
*/
function hash_encode($value)
{
return \Hashids::encode($value);
}
}
if (! function_exists('hash_decode')) {
/**
* Get result of Hashids decode
*/
function hash_decode($value)
{
$result = \Hashids::decode($value);
if (is_array($result) && count($result) === 1) {
return $result[0];
}
return $result;
}
}
另外我將Hashids
寫成一個Trait便可以在需要用到的Eloquent Model中直接use
<?php
namespace Weding\CorePackage\Traits;
trait HashId
{
public function getIdAttribute($value)
{
return $this->attributes['id'] = hash_encode($value);
}
public function getId()
{
return hash_decode($this->id);
}
}
如此一來在Eloquent物件中id會自然被轉換成Hashids
的結果,並能透過$this->getId()
的方法來取得原先未演算過的increment ID