PHPでUUIDを生成する方法や使い方について

採用はこちら

目次

PHPのUUIDとは

UUIDとは「Universally Unique Identifier」の略で、データを識別するために利用される128ビットの識別子です。
日本語では「汎用一意識別子」などと呼ばれます。

UUIDは、データベースのレコードIDやユーザーID、注文ID、APIのリソースIDなど、さまざまな用途で利用されています。

一般的には、次のような形式で表現されます。

550e8400-e29b-41d4-a716-446655440000

16進数字32文字と4つのハイフンで構成され、文字列表現では36文字になります。
UUID自体は128ビットなので、バイナリデータとしては16バイトです。

現在のUUID仕様はRFC 9562で定義されており、従来広く参照されていたRFC 4122を置き換えています。

UUIDを使う目的

UUIDの大きな特徴は、中央の採番サーバーへ問い合わせなくても、それぞれのアプリケーションやサーバー側で識別子を生成しやすいことです。

例えば通常の連番IDでは、次のように番号が増えていきます。

1
2
3
4
5

一方、UUIDでは次のようになります。

8b715a81-170b-4a8a-9f0c-6a829c91bf0d

複数のサーバーやサービスがそれぞれIDを生成する分散システムでも利用しやすい点がメリットです。

ただし、UUIDは「絶対に重複しない値」ではありません。
仕様に従って適切に生成することで、重複する確率を極めて小さくできる識別子と考えるのが正確です。

UUIDには複数のバージョンがある

UUIDには、生成方法の異なる複数のバージョンがあります。

代表的なものは次のとおりです。

バージョン主な特徴用途
UUID v1時刻やノード情報を利用従来の時刻ベースUUID
UUID v3名前空間とMD5から生成同じ入力から同じUUIDを生成
UUID v4ランダム値を中心に生成一般的な識別子
UUID v5名前空間とSHA-1から生成同じ入力から同じUUIDを生成
UUID v6時刻順に並びやすい構造DBなど
UUID v7Unix時刻とランダム値を利用DB・分散システム
UUID v8アプリケーション独自用途カスタムUUID

PHPで一般的に利用する場合は、特にUUID v4とUUID v7を理解しておくとよいでしょう。

UUID v4とは

UUID v4は、ランダム値を中心に生成されるUUIDです。

128ビットのうち、versionに4ビット、variantに2ビットを利用するため、実質的には122ビットがランダム値として使われます。

一般的な識別子として広く利用しやすく、ユーザーID、商品ID、注文ID、ファイルIDなどに適しています。

UUID v7とは

UUID v7は、時系列性を考慮したUUIDです。

RFC 9562では、先頭48ビットにUnix Epochからのミリ秒単位のタイムスタンプを格納する形式が定義されています。

そのため、ランダムに生成されるUUID v4よりも、生成時刻順に並びやすいという特徴があります。

特にデータベースの主キーとしてUUIDを利用したい場合には、UUID v7が候補になることがあります。

ただし、UUID v7を利用すれば必ずデータベース性能が向上するわけではありません。
DBMS、インデックス構造、保存形式、データ量などによって効果は異なります。

PHPでUUID v4を生成する方法

PHPのコア機能には、すべての環境で利用できるuuid4()のような専用関数が標準で用意されているわけではありません。

そのため、UUIDを生成する場合は次のような方法が一般的です。

  • random_bytes()を利用して自分で生成する
  • ramsey/uuidを利用する
  • SymfonyのUidコンポーネントを利用する

random_bytes()でUUID v4を生成する

外部ライブラリを使わずにUUID v4を生成する場合は、PHPのrandom_bytes()を利用できます。

<?php

function uuid4(): string
{
    $data = random_bytes(16);

    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);

    return sprintf(
        '%s-%s-%s-%s-%s',
        bin2hex(substr($data, 0, 4)),
        bin2hex(substr($data, 4, 2)),
        bin2hex(substr($data, 6, 2)),
        bin2hex(substr($data, 8, 2)),
        bin2hex(substr($data, 10, 6))
    );
}

echo uuid4();

実行すると、次のようなUUIDが生成されます。

e2b88d3a-468c-4b19-91ad-976547bed454

毎回異なるUUIDが生成されます。

versionとvariantを設定する

先ほどのコードでは、単純に16バイトの乱数を生成するだけではなく、UUIDの仕様に合わせて特定のビットを設定しています。

$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);

この処理では、UUIDのversionを4に設定しています。

また、次の処理ではvariantを設定しています。

$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);

単純にrandom_bytes(16)を16進数へ変換するだけでは、UUID v4の仕様に準拠した値にはならない点に注意しましょう。

ramsey/uuidでUUIDを生成する方法

実際のPHPアプリケーションでは、自分でUUID生成処理を実装するよりも、専用ライブラリを利用する方法が一般的です。

PHPで代表的なUUIDライブラリの一つがramsey/uuidです。

ramsey/uuidをインストールする

Composerを利用して次のようにインストールします。

composer require ramsey/uuid

Composerのオートローダーを利用する場合は、必要に応じて次のように読み込みます。

require __DIR__ . '/vendor/autoload.php';

UUID v4を生成する

ramsey/uuidでは、次のように簡単にUUID v4を生成できます。

<?php

require __DIR__ . '/vendor/autoload.php';

use Ramsey\Uuid\Uuid;

$uuid = Uuid::uuid4();

echo $uuid->toString();

出力例は次のようになります。

bfe17e54-dbd2-46bd-beac-b3855e986511

Uuid::uuid4()はUUIDオブジェクトを返します。

文字列として明示的に取得する場合は、次のようにtoString()を利用します。

$uuidString = $uuid->toString();

ramsey/uuidでUUID v7を生成する方法

UUID v7を利用したい場合は、対応バージョンのramsey/uuiduuid7()を利用できます。

UUID v7を生成する

<?php

require __DIR__ . '/vendor/autoload.php';

use Ramsey\Uuid\Uuid;

$uuid = Uuid::uuid7();

echo $uuid->toString();

UUID v7は次のような形式になります。

0198b7b3-7a6c-7d21-a743-b2bc62c8487e

UUID v7では、3つ目のブロックの先頭が7になります。

xxxxxxxx-xxxx-7xxx-xxxx-xxxxxxxxxxxx

UUID v4がランダム性を重視した識別子なのに対し、UUID v7は生成時刻順に並びやすい識別子です。

SymfonyでUUIDを生成する方法

Symfonyを使用している場合は、symfony/uidコンポーネントを利用できます。

symfony/uidをインストールする

Composerで次のようにインストールします。

composer require symfony/uid

UUID v4を生成する

<?php

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Uid\Uuid;

$uuid = Uuid::v4();

echo $uuid;

UUID v7を生成する

UUID v7の場合は次のようにします。

<?php

use Symfony\Component\Uid\Uuid;

$uuid = Uuid::v7();

echo $uuid;

Symfony UidはUUID v1、v3、v4、v5、v6、v7、v8などを扱えます。

Symfonyプロジェクトですでに関連コンポーネントを利用している場合は、symfony/uidを採用すると構成を統一しやすいでしょう。

UUID v4とUUID v7はどう使い分けるか

UUIDを利用するときに迷いやすいのが、v4とv7のどちらを選ぶかです。

一般的な用途ならUUID v4

UUID v4はランダム値を中心に生成されるため、生成時刻に依存しない識別子が欲しい場合に向いています。

例えば、次のような用途があります。

  • ユーザーID
  • 商品ID
  • ファイルID
  • APIリソースID
  • 一般的なオブジェクトID

特別な要件がなく、単純にランダムな一意識別子が必要なのであれば、UUID v4は分かりやすい選択肢です。

時系列性を重視するならUUID v7

UUID v7は、時刻情報を先頭側に持つため、生成された順序に近い形で並びやすくなります。

そのため、次のような用途で候補になります。

  • データベースの主キー
  • 注文ID
  • イベントID
  • ログID
  • 大量にINSERTするテーブル
  • 分散システム

ただし、UUID v7だから必ずUUID v4より高性能になるわけではありません。
実際のデータベース設計に合わせて検討する必要があります。

UUIDをデータベースの主キーにする方法

UUIDはAUTO_INCREMENTの代わりにデータベースの主キーとして利用することもできます。

PHP側でUUIDを生成してINSERTする

例えばramsey/uuidを利用する場合は次のようにできます。

<?php

use Ramsey\Uuid\Uuid;

$uuid = Uuid::uuid7()->toString();

$stmt = $pdo->prepare(
    'INSERT INTO users (id, name) VALUES (:id, :name)'
);

$stmt->execute([
    ':id' => $uuid,
    ':name' => 'Taro',
]);

この方法では、データベースへINSERTする前にIDを生成できます。

UUIDを主キーにするメリット

UUIDを主キーにすると、アプリケーション側であらかじめIDを生成できます。

さらに、複数のサーバーやサービスからデータを作成する場合でも、中央のAUTO_INCREMENTに依存しにくくなります。

例えば次のような構成です。

WebサーバーA
WebサーバーB
WebサーバーC

各サーバーでそれぞれUUIDを生成できるため、分散システムとの相性がよい点がメリットです。

UUIDを主キーにするデメリット

UUIDは整数型のIDよりもサイズが大きくなります。

例えば文字列形式のUUIDを保存する場合は、一般的に次のようになります。

CHAR(36)

一方、BIGINTは8バイトです。

UUIDは128ビット、つまり16バイトのため、保存方法によっては主キーやインデックスのサイズが大きくなります。

大量のデータを扱う場合は、ストレージやインデックス性能への影響も考慮する必要があります。

UUIDをBINARY(16)で保存する方法

UUIDは128ビットなので、データベースでは16バイトのバイナリデータとして保存できます。

文字列からバイナリへ変換する

例えば次のUUIDがあるとします。

550e8400-e29b-41d4-a716-446655440000

ハイフンを削除すると次のようになります。

550e8400e29b41d4a716446655440000

PHPでは次のように16バイトへ変換できます。

$binary = hex2bin(
    str_replace('-', '', $uuid)
);

これをデータベースの、

BINARY(16)

などへ保存できます。

ライブラリの変換機能を利用する

実務では、UUIDライブラリがバイナリ変換機能を提供している場合は、それを利用する方が分かりやすいでしょう。

また、PostgreSQLのようにUUID専用のデータ型を提供しているDBMSもあります。

そのため、必ずCHAR(36)BINARY(16)を使用しなければならないわけではありません。
利用しているDBMSに適した保存形式を選択することが重要です。

UUIDの形式をチェックする方法

外部から受け取ったUUIDをそのままデータベース検索などに利用するのではなく、形式が正しいか確認すると安全です。

ramsey/uuidでバリデーションする

ramsey/uuidでは、次のようにUUIDの形式を確認できます。

<?php

use Ramsey\Uuid\Uuid;

$value = '550e8400-e29b-41d4-a716-446655440000';

if (Uuid::isValid($value)) {
    echo '有効なUUIDです';
} else {
    echo '無効なUUIDです';
}

APIやURLパラメータとしてUUIDを受け取る場合などに利用できます。

例えば、

/users/550e8400-e29b-41d4-a716-446655440000

のようなURLを使用している場合、検索処理の前にUUID形式を確認できます。

UUIDをAPIで利用する方法

UUIDはWeb APIのリソースIDとしても利用しやすい識別子です。

REST APIで利用する例

ユーザーなら次のようにできます。

GET /api/users/0198b7b3-7a6c-7d21-a743-b2bc62c8487e

商品なら次のようになります。

GET /api/products/0198b7b3-81b5-751d-9337-404b883f5694

注文なら次のようになります。

GET /api/orders/0198b7b3-8661-799e-9eaf-3293f74f8c4c

UUIDは複数のサービスで独立して生成できるため、マイクロサービスなどでも利用しやすい特徴があります。

UUIDをURLに利用するメリット

連番IDの場合は、URLが次のようになることがあります。

/users/1
/users/2
/users/3

この形式では、IDが連番であることが分かりやすくなります。

UUIDを利用すると、次のようになります。

/users/550e8400-e29b-41d4-a716-446655440000

単純な連番より次のIDを推測しにくくなる点はメリットです。

UUIDは認証や認可の代わりにはならない

ただし、UUIDが推測しにくいからといってアクセス制御を省略してはいけません。

UUIDを知っている
=
アクセス権限がある

という設計にはしないようにしましょう。

ユーザーが対象データを閲覧・更新できるかどうかは、UUIDとは別に認証・認可処理で確認する必要があります。

uniqid()をUUIDとして使ってよいのか

PHPにはuniqid()という関数があります。

$id = uniqid();

echo $id;

しかし、uniqid()が返す値はUUIDではありません。

uniqid()とUUIDは別物

例えばuniqid()では次のような値が生成されます。

66bc7fa77352f

UUIDのように見える識別子を簡単に作れますが、RFC 9562で定義されているUUID v4やUUID v7とは別物です。

そのため、UUIDが必要な場所で、

uniqid();

を代用するのは避けた方がよいでしょう。

同様に、

md5(time());

などを独自UUIDとして使用することもおすすめできません。

UUIDが必要であれば、標準仕様に準拠した実装を利用します。

UUIDは重複する可能性があるのか

UUIDは非常に大きな識別子空間を利用しているため、適切に生成すれば衝突する確率は極めて小さくなります。

ただし、理論上は重複する可能性がゼロではありません。

データベースでは一意制約を設定する

UUIDをデータベースの識別子として利用する場合は、UUIDだから重複しないと決めつけるのではなく、データベース側でも制約を設定しましょう。

例えば主キーなら、

PRIMARY KEY

を設定します。

主キー以外で一意性を保証したい場合は、

UNIQUE

制約を利用できます。

これにより、万が一重複する値が発生した場合でも、データベース側で検出できます。

PHPでUUIDを利用するときの注意点

PHPでUUIDを扱う場合は、単に生成できればよいというわけではありません。

独自実装を増やしすぎない

UUID v4はrandom_bytes()を使えば自分で実装できますが、UUIDの解析や変換、複数バージョンへの対応まで独自に実装すると保守が複雑になります。

一般的なWebアプリケーションでは、

ramsey/uuid

や、

symfony/uid

などの実績のあるライブラリを利用する方が安全です。

保存形式を設計段階で決める

UUIDをデータベースへ保存する場合は、

CHAR(36)
BINARY(16)
UUID専用型

など複数の選択肢があります。

どの形式が適しているかはDBMSやアプリケーションの規模によって異なります。

小規模なシステムで可読性を重視するなら文字列形式でも十分な場合があります。

大量データを扱う場合は、インデックスサイズなども考慮してバイナリ形式やUUID専用型を検討するとよいでしょう。

UUID v4とv7を目的に応じて選ぶ

単純なランダムIDが必要ならUUID v4が使いやすいでしょう。

一方、データベースで時系列性やインデックスの局所性を重視する場合はUUID v7が候補になります。

ただし、UUID v7が常に最適とは限らないため、システム要件に合わせて判断することが重要です。

PHPでUUIDを生成するおすすめの方法

PHPでUUIDを生成する方法はいくつかありますが、用途によって適した方法が異なります。

外部ライブラリを使わないならrandom_bytes()

小規模な処理でUUID v4だけが必要なのであれば、random_bytes()を使った実装でも対応できます。

ただし、versionとvariantのビット設定を正しく行う必要があります。

一般的なPHP開発ならramsey/uuid

フレームワークに依存せずUUIDを扱いたい場合は、ramsey/uuidが有力な選択肢です。

例えばUUID v4なら次のように生成できます。

use Ramsey\Uuid\Uuid;

$uuid = Uuid::uuid4()->toString();

UUID v7なら次のようになります。

use Ramsey\Uuid\Uuid;

$uuid = Uuid::uuid7()->toString();

生成、検証、解析などをまとめて扱えるため、実務でも使いやすい方法です。

Symfonyならsymfony/uid

Symfonyを利用しているのであれば、symfony/uidを利用するとプロジェクト内の依存関係を統一しやすくなります。

use Symfony\Component\Uid\Uuid;

$uuid = Uuid::v7();

UUIDだけでなくULIDなども扱えるため、Symfony環境では使いやすい選択肢です。

PHPのUUIDについてのまとめ

PHPでUUIDを生成する場合は、random_bytes()を利用してUUID v4を自分で実装する方法や、ramsey/uuidsymfony/uidといったライブラリを利用する方法があります。

単純なランダムIDが必要であればUUID v4が分かりやすく、時系列性を持たせてデータベースなどで利用したい場合はUUID v7が有力な候補です。

また、UUIDをデータベースへ保存するときは、CHAR(36)BINARY(16)、DBMSが提供するUUID専用型などから適切な方式を選ぶ必要があります。

UUIDは分散環境でIDを生成しやすく、APIやデータベースの識別子として非常に便利ですが、認証や認可の代わりになるものではありません。

さらに、uniqid()md5(time())などはUUIDではないため、UUIDが必要な場面ではRFC 9562に準拠した生成方法を利用することが重要です。

PHPで本格的にUUIDを扱うのであれば、一般的なPHPアプリケーションではramsey/uuid、Symfony環境ではsymfony/uidを利用し、用途に応じてUUID v4またはUUID v7を選択する方法が分かりやすいでしょう。

以上、PHPでUUIDを生成する方法や使い方についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次