TL;DR: ユーザーが入力するURLスラッグが、Webアプリケーションのルートや予約語と衝突しないようバリデーションするCakePHP 5.xプラグインを公開しました。

elstc/cakephp-slug-guard: CakePHP plugin for URL-safe slug validation and reserved-word collision prevention


SlugGuard - CakePHP Plugin

どういうプラグイン?

Webアプリケーションで、ユーザーにプロフィールページのURLスラッグを自由に設定させたいことってありますよね。例えば example.com/nojimage のような感じで。

でも、ユーザーが adminloginapi のようなスラッグを登録してしまうと、システムのルートと衝突して大変なことになります。

SlugGuard は、こういったスラッグの衝突を防ぐための CakePHP 5.x プラグインです。以下の3つのバリデーションルールを提供します。

  • SlugValidator - スラッグのフォーマットチェック(小文字英数字とハイフンのみ、先頭末尾は英数字)
  • IsNotReservedSlug - 約710個の予約語(admin, login, api, settings 等)との衝突チェック
  • IsNotRouteConflict - CakePHPに登録されたルートとの動的な衝突チェック

インストール

composer require elstc/cakephp-slug-guard

bin/cake plugin load Elastic/SlugGuard

プラグインをロードしたら、マイグレーションを実行して予約語テーブルを作成します。

bin/cake migrations migrate --plugin Elastic/SlugGuard
bin/cake slug_guard sync

sync コマンドで、組み込みの約710個の予約語がデータベースにインポートされます。admin, dashboard, login, api から facebook, twitter, github のようなSNS名まで、よくある予約語が一通り入っています。

使い方

予約語・ルート衝突チェック(Application Rule)

テーブルクラスの buildRulesIsNotReservedSlugIsNotRouteConflict を追加します。

use Elastic\SlugGuard\Model\Rule\IsNotReservedSlug;
use Elastic\SlugGuard\Model\Rule\IsNotRouteConflict;

// Table クラス内
public function buildRules(RulesChecker $rules): RulesChecker
{
    $rules->add(new IsNotReservedSlug('slug'), 'reservedSlug', [
        'errorField' => 'slug',
        'message' => 'このスラッグは予約されています',
    ]);
    $rules->add(new IsNotRouteConflict('slug'), 'routeConflict', [
        'errorField' => 'slug',
        'message' => 'このスラッグはルートと衝突します',
    ]);

    return $rules;
}

コンストラクタの引数にチェック対象のフィールド名を渡します。slug 以外のフィールド(例えば username)にも使えます。

スラッグフォーマットチェック(Validator)

SlugValidator は Validator の provider として使います。小文字英数字とハイフンのみ許可し、長さの制約もかけられます。

use Elastic\SlugGuard\Validation\SlugValidator;

// Table クラス内
public function validationDefault(Validator $validator): Validator
{
    $validator->setProvider('slugValidator', SlugValidator::class);
    $validator->add('slug', 'validSlug', [
        'rule' => ['isValid'],
        'provider' => 'slugValidator',
        'message' => '小文字英数字とハイフンのみ使用できます',
    ]);

    return $validator;
}

デフォルトは4〜24文字ですが、長さを変更することもできます。

// 最小3文字、最大32文字に変更
$validator->add('slug', 'validSlug', [
    'rule' => ['isValid', 3, 32],
    'provider' => 'slugValidator',
]);

予約語の管理

CLIコマンドで予約語の追加・削除・一覧表示ができます。

# 予約語の一覧表示
bin/cake slug_guard list

# 検索
bin/cake slug_guard list --search admin

# 予約語の追加
bin/cake slug_guard add my-reserved-word

# 予約語の削除
bin/cake slug_guard remove my-reserved-word

# ファイルからインポート(1行1スラッグ、# でコメント)
bin/cake slug_guard import path/to/custom-slugs.txt

# アプリケーションのルートから自動抽出して予約語に追加
bin/cake slug_guard routes | bin/cake slug_guard add

# 設定ファイルとDBの同期(dry-run対応)
bin/cake slug_guard sync --dry-run

設計のポイント

ルートとの動的衝突チェック

IsNotRouteConflict は、CakePHPに登録されたルートの第1セグメントを動的に取得して衝突チェックを行います。ルーティング設定を変更しても、自動的に反映されるので管理が楽です。

予約語のDB管理

予約語は reserved_slugs テーブルで管理しています。スラッグ文字列をそのまま主キーにしているので、存在チェックが高速です。CLIコマンドでの追加・削除に加えて、ファイルからのインポートやstdin入力にも対応しているので、運用時の柔軟性があります。


余談

機構自体はだいぶ昔から持っていて、実際のプロジェクトで何度も必要になっていたのですが、なかなかプラグインとして切り出せないでいました。 AIエージェントのおかげで、プラグイン化のためのコード整理やドキュメント作成が一気にできて、ようやく公開できる形になりました。

今回のプラグインで言えば、CLIコマンドの実装はほぼおまかせです。

というわけで、ユーザーにスラッグを自由入力させるアプリケーションを作るとき、予約語チェックは地味だけど忘れると面倒なことになる部分です。SlugGuard を入れておけば、フォーマットチェック・予約語チェック・ルート衝突チェックをまとめて面倒見てくれるので、安心してスラッグ入力機能を実装できます。

CakePHP 5.x + PHP 8.2 以上で使えますので、ぜひ試してみてください。

参考リンク