hejunjie/promotion-engine
Composer 安装命令:
composer require hejunjie/promotion-engine
包简介
一个 灵活可扩展 的 PHP 促销策略引擎,让购物车的各种复杂促销逻辑(满减、打折、阶梯优惠、会员折扣…)实现更优雅、可维护 | A flexible and scalable PHP promotional strategy engine that enables various complex promotional logics (money off, discounts, tiered offers, member discounts, etc.) in shopping carts to be implemented more elegantly and maintainably.
README 文档
README
English | 简体中文
A flexible and extensible PHP promotion strategy engine that abstracts complex shopping cart promotion logic — money-off, discounts, tiered offers, member discounts — out of if-else spaghetti and into composable rule classes.
This project has been parsed by Zread. If you need a quick overview of the project, you can click here to view it:Understand this project
Features
- 🧩 Rules as classes:Each promotion strategy is an independent rule class — clear, testable, and reusable
- 🔀 Three calculation modes:Independent, sequential (stacked), and lock modes for different business scenarios
- 🏷️ Tag filtering:Rules match items by product tags, making category-specific promotions effortless
- 📊 Priority control:Rules execute in priority order, giving you full control over discount stacking
- 🔌 Easy to extend:Add custom rules by extending
AbstractPromotionRule— no changes to the engine core - 🪶 Zero dependencies:Requires only PHP >= 7.4, no third-party libraries
Requirements
- PHP >= 7.4 (also supports PHP 8.x)
- Composer
Installation
composer require hejunjie/promotion-engine
Quick Start
use Hejunjie\PromotionEngine\Models\Cart; use Hejunjie\PromotionEngine\Models\User; use Hejunjie\PromotionEngine\PromotionEngine; use Hejunjie\PromotionEngine\Rules; // Create a VIP user $user = new User(true); // Create a shopping cart $cart = new Cart(); $cart->addItem('Chips', 30, 2, ['snacks']); $cart->addItem('T-shirt', 100, 1, ['clothes']); $cart->addItem('Mouse', 200, 1, ['electronics']); // Initialize the engine and set calculation mode $engine = new PromotionEngine(); $engine->setMode('independent'); // Register rules $engine->addRule(new Rules\FullReductionRule(300, 30)); // ¥30 off when total ≥ ¥300 $engine->addRule(new Rules\VipDiscountRule(0.95)); // VIP 5% off // Calculate $result = $engine->calculate($cart, $user); echo "Original: ¥{$result['original']}\n"; // Original: ¥360 echo "Discount: -¥{$result['discount']}\n"; // Discount: -¥48 echo "Final: ¥{$result['final']}\n"; // Final: ¥312 print_r($result['details']); // Discount detail array
Calculation Modes
The engine supports three calculation modes, switchable via setMode():
| Mode | Identifier | Behavior |
|---|---|---|
| Independent | independent |
Each rule calculates its discount against the original price independently, then all discounts are summed |
| Sequential | sequential |
Rules execute in order. Each rule's discount is distributed back into item prices, so subsequent rules calculate against already-discounted prices (stacked discounts) |
| Lock | lock |
Each item can be discounted by at most one rule. Discounted items are locked and skipped by subsequent rules |
// Independent: each rule works against original prices $engine->setMode('independent'); // Sequential: discount-on-discount $engine->setMode('sequential'); // Lock: each item receives only one discount $engine->setMode('lock');
Note
In independent mode, rule order does not affect the final discount. In sequential and lock modes, rules execute in priority order.
Built-in Rules
All rules accept two optional trailing parameters for tag filtering and priority:
// Only applies to items tagged 'snacks', priority 10 (lower = higher priority) new Rules\FullReductionRule(100, 20, ['snacks'], 10);
| Rule | Description | Example |
|---|---|---|
FullReductionRule |
Spend ¥X, get ¥Y off | new FullReductionRule(100, 20) |
FullDiscountRule |
Spend ¥X, get Z% off | new FullDiscountRule(200, 0.9) |
FullQuantityReductionRule |
Buy X items, get ¥Y off | new FullQuantityReductionRule(3, 20) |
FullQuantityDiscountRule |
Buy X items, get Z% off | new FullQuantityDiscountRule(5, 0.9) |
TieredDiscountRule |
Tiered discount (highest eligible tier) | new TieredDiscountRule([100 => 0.9, 200 => 0.8, 500 => 0.7]) |
TieredReductionRule |
Tiered money-off (highest eligible tier) | new TieredReductionRule([100 => 10, 200 => 30, 500 => 80]) |
NthItemDiscountRule |
Nth item at Z% off | new NthItemDiscountRule(3, 0.5) |
NthItemReductionRule |
Nth item at special price ¥X | new NthItemReductionRule(3, 9.9) |
VipDiscountRule |
VIP members get Z% off | new VipDiscountRule(0.9) |
VipReductionRule |
VIP members get ¥X off | new VipReductionRule(5) |
Advanced Usage
Tag Filtering
Use product tags to restrict rules to specific categories:
$cart = new Cart(); $cart->addItem('Cola', 5, 10, ['drinks']); $cart->addItem('Keyboard', 300, 1, ['electronics']); // Only drinks: 20% off when buying 3 or more $engine->addRule(new Rules\FullQuantityDiscountRule(3, 0.8, ['drinks'])); // Only electronics: ¥50 off when spending ¥200 $engine->addRule(new Rules\FullReductionRule(200, 50, ['electronics']));
Custom Rules
Extend AbstractPromotionRule and implement the apply() method:
use Hejunjie\PromotionEngine\Models\Cart; use Hejunjie\PromotionEngine\Models\User; use Hejunjie\PromotionEngine\PromotionResult; use Hejunjie\PromotionEngine\Rules\AbstractPromotionRule; class BirthdayDiscountRule extends AbstractPromotionRule { public function apply(Cart $cart, User $user, array $eligibleIndexes = []): PromotionResult { $items = $this->filterEligibleItems($cart, $eligibleIndexes); $total = $cart->calculateItemsTotal($items); if ($total > 0) { $discount = round($total * 0.1, 2); return new PromotionResult($discount, 'Birthday 10% off'); } return new PromotionResult(0, 'Birthday discount not triggered'); } } // Use the custom rule $engine->addRule(new BirthdayDiscountRule(['all'], 1));
Priority Control
Lower numbers have higher priority (default is 1). Priority determines execution order in multi-rule scenarios:
// Money-off runs first (priority 5), then VIP discount (priority 10) $engine->addRule(new Rules\FullReductionRule(300, 30, [], 5)); $engine->addRule(new Rules\VipDiscountRule(0.9, [], 10));
Directory Structure
src/
├── PromotionEngine.php # Engine entry point, rule management and calculation dispatch
├── PromotionResult.php # Rule result value object (discount amount + description)
├── Calculators/
│ ├── IndependentCalculator.php # Independent mode
│ ├── SequentialCalculator.php # Sequential (stacked) mode
│ └── LockCalculator.php # Lock mode
├── Contracts/
│ ├── PromotionCalculatorInterface.php # Calculator interface
│ └── PromotionRuleInterface.php # Rule interface
├── Models/
│ ├── Cart.php # Shopping cart model
│ └── User.php # User model
└── Rules/
├── AbstractPromotionRule.php # Abstract rule base class
├── FullReductionRule.php # Spend X, get Y off
├── FullDiscountRule.php # Spend X, get Z% off
├── FullQuantityReductionRule.php # Buy X items, get Y off
├── FullQuantityDiscountRule.php # Buy X items, get Z% off
├── TieredDiscountRule.php # Tiered discount
├── TieredReductionRule.php # Tiered money-off
├── NthItemDiscountRule.php # Nth item discount
├── NthItemReductionRule.php # Nth item special price
├── VipDiscountRule.php # VIP discount
└── VipReductionRule.php # VIP money-off
Contributing
PRs are welcome! Areas that especially need help:
- New promotion rules (coupons, buy-one-get-one, flash sales, etc.)
- Unit tests
- Performance improvements
Fork the repository, submit a Pull Request, or open an Issue to discuss ideas.
hejunjie/promotion-engine 适用场景与选型建议
hejunjie/promotion-engine 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 5 次下载、GitHub Stars 达 18, 最近一次更新时间为 2025 年 07 月 29 日, 在 PHP 生态内属于活跃度较高的组件。
我们在过去多个企业项目中使用过 hejunjie/promotion-engine 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 hejunjie/promotion-engine 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
统计信息
- 总下载量: 5
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 18
- 点击次数: 31
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-07-29