複雑な条件分岐に悩んだら自作ルールエンジンを作ってみた

プログラミングしていてこんな経験ありませんか?
機能自体は単純なのに、判定条件だけがどんどん増えていく。
ユーザーの状態、設定項目、商品属性、会員レベル...
無数の if/else が絡み合い、コードが混沌としていく。
一箇所修正するだけで、他の部分への影響が心配になる。

私も同じ状況に直面しました。最初は我慢していましたが、ついに決断しました:シンプルなルールエンジンを自作して、これらの複合判定を専門に処理しようと。

こうして生まれたのが:hejunjie/simple-rule-engine

このルールエンジンで何ができる?

要約すると:
軽量で使いやすい PHP ルールエンジン。複数条件の組み合わせ、動的ルール実行をサポートし、ビジネスルール判定やデータ検証などのシーンに適しています。

プロジェクトのこうした場面で活躍します:

  • 複雑なビジネスロジックの多条件判定(例:ユーザーがキャンペーン条件を満たしているか)
  • データ保存前のルール検証
  • カスタムロジックの設定化・構造化
  • if 地獄から抜け出して整理整頓したい場合 😅

なぜ作ったのか?

実際のビジネスでは、多くの判定ロジックを「複数のフィールド + いくつかのルール + 条件の組み合わせ」として表現できます。

従来はこのように書いていました:

if (
    $profile['status'] === 'active' &&
    $profile['age'] >= 18 &&
    in_array($profile['permission'], ['admin', 'editor'])
) {
    // 処理...
}

今ではこう書けます:

// ルール定義
$criteria = [
    new Criterion('age', '>=', 18, '年齢は18歳以上必要'),
    new Criterion('status', '==', 'active', 'ステータスはactive必須'),
    new Criterion('permission', 'in', ['admin', 'editor'], '権限が必要'),
];

// 評価実行
$result = RuleEngine::assess($criteria, $profile, 'AND'); // true/falseを返す

// 詳細評価情報(各ルールの実行状況取得)
$report = RuleEngine::assessWithDetails($criteria, $profile);
/*
返却例:
[
    ['description' => '年齢は18歳以上必要', 'passed' => true],
    ['description' => 'ステータスはactive必須', 'passed' => true],
    ['description' => '権限が必要', 'passed' => true]
]
*/

すっきりしませんか?さらに、ルールをデータベースに保存すれば「ビジネス判定の設定化」も実現できます。

プロジェクトの特徴

  • 軽量でシンプル:依存関係なし、フレームワーク非依存、数行で使える
  • ファクトリ登録機能:独自のオペレーターを登録して拡張可能
  • 標準オペレーター搭載:一般的なオペレーターを多数サポート(記事末尾のリスト参照)
  • 組み合わせ可能:AND/OR関係の組み合わせをサポート、複数ルールセットの拡張が容易

導入方法

composer require hejunjie/simple-rule-engine

️ サンプルコード

use Hejunjie\SimpleRuleEngine\Criterion;
use Hejunjie\SimpleRuleEngine\RuleEngine;

// ルールセット定義
$criteria = [
    new Criterion('age', '>=', 18, '年齢は18歳以上必要'),
    new Criterion('status', '==', 'active', 'ステータスはactive必須'),
    new Criterion('permission', 'in', ['admin', 'editor'], '権限が必要'),
];

$target = ['age' => 20, 'nation' => 'Japan'];

// 全ルールのパス判定
if (RuleEngine::assess($criteria, $target, 'AND')) {
    echo 'ルールを満たしています';
}

// 各ルールの詳細結果
foreach (RuleEngine::assessWithDetails($criteria, $target) as $item) {
    echo $item['description'] . ':' . ($item['passed'] ? '✅ 合格' : '❌ 不合格') . PHP_EOL;
}

カスタムオペレーター

独自の判定ロジックを実装し、任意のオペレーターを定義してルールに組み込めます。

OperatorInterface を実装し、OperatorFactory 経由で登録するだけ:

use Hejunjie\SimpleRuleEngine\Interface\OperatorInterface;
use Hejunjie\SimpleRuleEngine\OperatorFactory;

class CustomOperator implements OperatorInterface
{
    /**
     * 評価メソッド
     *
     * @param mixed $inputValue 入力データ
     * @param mixed $compareValue 比較データ
     *
     * @return bool
     */
    public function evaluate(mixed $inputValue, mixed $compareValue): bool
    {
        // TODO: 判定ロジックを実装
    }

    /**
     * オペレーター名
     *
     * @return string
     */
    public function name(): string
    {
        return 'custom';
    }
}

// カスタムオペレーター custom を登録
$factory = OperatorFactory::getInstance();
$factory->register(new CustomOperator());

// ルール定義で custom を使用可能
$criteria = [
    new Criterion('field', 'custom', 'value', 'カスタムルール説明'),
    // ...
    // ...
];

// RuleEngine::assess($criteria, $target, 'AND')
// RuleEngine::assessWithDetails($criteria, $target)

標準オペレーター一覧

オペレーター説明補足​==​等しいなし​!=​等しくないなし​>​より大きいなし​>=​以上なし​<​より小さいなし​<=​以下なし​in​配列に含まれる配列:[値1,値2,...]​not_in​配列に含まれない配列:[値1,値2,...]​contains​文字列を含むなし​not_contains​文字列を含まないなし​start_swith​指定文字列で始まるなし​end_swith​指定文字列で終わるなし​between​範囲内配列:[最小値,最大値]​not_between​範囲外配列:[最小値,最大値]​before_date​指定日付より前通常の日付形式、タイムスタンプも可​after_date​指定日付より後通常の日付形式、タイムスタンプも可​date_equal​日付が等しい通常の日付形式、タイムスタンプも可

まとめ

このルールエンジンは、高度な技術課題を解決するものではありません。ただよりエレガントな解決策を提供するだけです。
もし同様の if/else の悩みに直面しているなら、この小さなツールが少しでも役立つことを願っています。

Star、Issue、PR をお待ちしています。一緒に改善していきましょう 🙌
役立つと思っていただけたら、いいねいただけると更新の励みになります~

タグ: PHP ルールエンジン ビジネスロジック 条件分岐 データ検証

5月16日 02:06 投稿