
はじめに
この記事は、BASEテックブログ夏のブログリレー9日目の記事です。
こんにちは、BASE株式会社で PHPer をしている @meihei です。
フィーチャートグルはご存知で既に使っている、という方も多いと思います。BASEでもフィーチャートグルの仕組みを利用しています。
一方で、私が進めていたプロジェクトでは、既存の仕組みだけでは少し扱いづらい場面がありました。そこで、用途に応じて必要最低限のフィーチャートグルを追加していきました。
この記事では、フィーチャートグルを具体的にもっとEasyに取り入れていった事例を紹介します。
背景
フィーチャートグルとは
フィーチャートグルは、コードを変更・デプロイすることなくシステムの振る舞いを切り替える手法です。新しい処理をコードとしては本番に入れておきつつ、実際に有効化するタイミングは別で制御できるので、「デプロイ」と「リリース」を分離できます。
このフィーチャートグル自体の話は多くの場所で語られているものなので、この記事では深入りしません。
この記事で扱うのはもっと素朴なものです。「特定のリクエストを旧経路と新経路のどちらに通すかを判断できる」ことさえできれば、フィーチャートグルとして十分に機能します。
PHPで書くとこれだけです。
if ($featureEnabled) {
// 新しい処理
} else {
// 今までの処理
}
「今までの処理」はリリースが終わったら消すので、消す対象をコメントでマークしておくと、後から機械的に探せて便利です。
// @delete start feature-a if (!$featureEnabled) { // 今までの処理 return; } // @delete end feature-a // 新しい処理
リリースが済んだら @delete start feature-a 〜 @delete end feature-a のブロックをまるごと消して、新しい処理だけを残します。
問題は「トグルの値」をどう決めるか
BASE にある既存のフィーチャートグルは、ショップ単位 (特定のショップだけ、または全ショップ) の有効・無効の設定からトグルの値を決めています。
管理画面から操作でき、まず社内のテスト用ショップや一部のショップで有効にして様子を見て、問題なければ全ショップに広げる、という段階的なリリースに使われています。

一方で、リリースの手前には「本番で自分のリクエストだけ新経路を試したい」「ショップページ以外のページでも検証したい」といった、ショップ単位とは別の切り替えが欲しくなる場面があります。
もしちゃんと作るなら、既存の仕組みを拡張するか、OpenFeature のようなライブラリを入れて、汎用的な仕組みから入るところです。
今回はそうせず、Easyに、特定ケースにしか効かない汎用性のない最小のものを作りましょう、というお話です。
事例1:Query Parameter で特定リクエストだけ切り替える
- 対象: 社内向け管理画面
- 値の出どころ: クエリパラメータ
- 既存の仕組みで足りない理由: 社内向け管理画面にはショップという単位がない

この画面にアクセスできるのは社内の人だけです。 公開前の機能が外に見える心配はなく、URL が利用者の履歴に残っても問題ありません。 なるべく簡単に切り替えられることだけを考えればよいので、クエリパラメータで判断することにしました。
function isFeatureAEnabled($request): bool
{
return ($request->getQueryParams()['feature_a'] ?? '') === '1';
}
URL の末尾に ?feature_a=1 を付けたリクエストだけが新経路を通り、それ以外は今まで通りです。DB もデプロイも要らず、状態も持ちません。
「このリンクを開いてください」と URL を渡すだけで、開発者以外の人にも確認を依頼できます。

画像は、チーム共有会で機能をオンにした状態にアクセスできるよう共有したときのものです。
クエリパラメータなので、ボタンで切り替えられるようにしました。
事例2:HTTP Header で特定の訪問者だけ切り替える
- 対象: ショップページと BASE API
- 値の出どころ: カスタムヘッダー
- 既存の仕組みで足りない理由: ショップ単位でしか切り替えられず、訪問者単位でオンオフできない

ショップページと BASE API は、ショップの訪問者や外部のアプリが使う、社外に公開された経路です。
既存のショップ単位のトグルで切り替えると、そのショップを訪れる全員が一斉に新経路になります。しかし今回のユースケースでは、リリース前の確認として「特定のリクエストだけ」を新経路 (または旧経路) に通したいので、ショップ単位とは別に訪問者単位の切り替えが必要でした。
事例1で紹介したクエリパラメータの方式では、不本意に URL に乗ってしまうリスクがあり、クエリパラメータを受け取らない API へのリクエストでは使えません。そこで、クエリパラメータを使わずカスタムヘッダーで判断することにしました。
function isFeatureAEnabled($request): bool
{
return $request->getHeaderLine('X-Feature-A') === '1';
}
ショップページなどの多くの場所で判定が必要な場合は、Middleware を使うと便利です。
final class FeatureA
{
private static bool $enabled = false;
public static function enable(): void
{
self::$enabled = true;
}
public static function isEnabled(): bool
{
return self::$enabled;
}
}
class FeatureAMiddleware
{
public function process($request, $handler)
{
if (isFeatureAEnabled($request)) {
FeatureA::enable();
}
return $handler->handle($request);
}
}
Middleware がリクエストの冒頭でヘッダーを判定して FeatureA をオンにするので、新旧の分岐を書く場所では、リクエストを引き回さずに FeatureA::isEnabled() で判定できます。
QAなどで動作確認する人は、ヘッダーを書き換えるブラウザ拡張機能を使ったりして新経路に入ります。他の訪問者は今まで通りです。
事例3:Cookie + IP Address で社内アクセスだけ切り替える
- 対象: ショップページ
- 値の出どころ: Cookie と IP アドレス
- 既存の仕組みで足りない理由: 事例2と同様の要件だが、普段のブラウザ操作だけで使えて、時間が経てば自然に元へ戻る形で新経路に切り替えたい

社内の人に新経路を見てもらう機会が急遽発生し、突貫で用意しました。
見てもらう相手はヘッダーの書き換えに慣れた人ばかりではないので、事例2の方式は使えません。
そこで、ブラウザに Cookie を持たせて、普段の操作のまま新経路を使い続けられるようにしました。
Cookie には有効期限があるので、期限が切れれば自然に元の状態へ戻り、戻し忘れの心配もありません。
ただし Cookie だけでは外部の人も同じことができてしまうので、社内 (オフィスや VPN) の IP アドレスからのアクセスであることも条件に加えました。
function isFeatureAEnabled($request): bool
{
// 社内 IP かどうかの判定。中身はプロキシや LB の構成に依存するので省略
return isInternalAccess($request)
&& ($request->getCookieParams()['feature_a'] ?? '') === '1';
}
Cookie は、ブラウザ操作だけで「feature-a を有効にする」ことができる形でセットします。
このアプローチは、私が過去に書いた記事と同じものです。
事例4:Shop ID を使ってカナリアリリースする
- 対象: ショップページ、ショップ向けの管理画面
- 値の出どころ: ショップと1対1に対応する整数IDの剰余と、割合の設定値 (canary)
- 既存の仕組みで足りない理由: ショップ単位のトグルは対象を手で選ぶ必要があり、「全体のn%」という広げ方ができない

最初は1%、問題がなければ10%、50%、100%と段階的に上げていく、いわゆるカナリアリリースもEasyに実装できます。
要件は「既存のトグルと同じショップ単位のまま、全ショップのうちn%だけを新経路に切り替えたい」というものです。
そこで、ショップと1対1に対応する整数のIDを100で割った余りを、canary として設定した割合と比較します。
function isFeatureAEnabled(int $numericShopId): bool
{
// $numericShopId はショップと1対1に対応する整数のID
$canary = (int) Config::get('feature_a_canary');
return $numericShopId % 100 < $canary;
}
判定はショップごとに決まっているので、canary を上げれば対象が広がっていきます。
割合の変更にはデプロイが必要であり、厳密には「デプロイ」と「リリース」を分離できていませんが、触るのは Config の値だけで、戻すときも値を戻すだけです。今回はこれで十分と割り切りました。
おわりに
この記事では、クエリパラメータやヘッダー、Cookie と IP アドレス、Shop ID といった手元にあるものだけで、トグルの値をEasyに決めていった事例を紹介しました。
最終的には、カナリアリリースやカスタムヘッダーなど複数の判定を1つの関数にまとめ、組み合わせて使っています。
リリースが終わったら、判定関数ごと @delete マークの旧経路と一緒に消します。
この記事で紹介したフィーチャートグルにはビジネスロジックが含まれず、決められた場所で決められた用途でしか使われません。
だからこそ、なるべく質素に、単純に、Easyに書く方が良いと思っています。
フィーチャートグルというと専用の基盤を思い浮かべがちですが、もっとEasyに始めることができます。ぜひ参考にしてください!
またこうしたプロダクト開発で使える、簡単だけど強力なTipsに興味のある方は、ぜひ採用情報もご覧ください!
明日は、Takeuchiさんの記事です。お楽しみに!