my-com / laravel-conditional-actions
Requires
- ext-json: *
- illuminate/config: ^6.0
- illuminate/database: ^6.0
- illuminate/http: ^6.0
- illuminate/support: ^6.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.14
- mockery/mockery: ^1.2
- orchestra/testbench: ^4.0
- phpmd/phpmd: ^2.6
- phpro/grumphp: ^0.15.0
- sebastian/phpcpd: ^4.1
This package is auto-updated.
Last update: 2024-09-14 01:04:33 UTC
README
此包允许通过API配置业务逻辑,而无需更改您的代码。当您不知道特定条件,因为它们由您的经理/用户等动态定义时,这非常有用。
如何使用
代码库提供预定义的条件、操作、目标以及API,以便将它们组合到业务逻辑中以供最终用户使用。对象
Target
- 为条件和操作提供所有必要的数据;State
- 键值对。操作应在应用时更新状态;Condition
- 条件具有check
方法,它返回成功或不成功(布尔值);Action
- 操作具有apply
方法,它更改State
或执行任何其他操作,并返回更改后的State
;
生命周期
Target
创建一个State
对象;Target
获取所有相关活动的Condition
,按优先级排序并运行每个条件的检查;- 对于成功的
Condition
,Condition
获取所有相关的操作并将它们应用到State
上; Action
返回更改后的State
,它用于下一个条件或操作;- 检查所有
Condition
后,Target
将新的State
传递给applyState
方法。您可以根据需要使用其状态。
入门
例如,您有一个玩具店。您的营销部门针对某些玩具进行促销。如果用户在过去购买过玩具或今天是他的生日,"芭比娃娃" 应该有10%的折扣。促销从2019/05/01 00:00开始,到2019/05/01 23:59结束。
您应该创建
条件
- 用户过去购买过玩具(《HasPaidToysCondition》)
- 今天是他的生日(《TodayIsBirthdayCondition》)
操作
- "芭比娃娃" 应该有10%的折扣(《DiscountAction》)
对于时间限制(促销从2019/05/01 00:00开始,到2019/05/01 23:59结束),您可以使用字段 starts_at
和 ends_at
。
两个条件都应该成功。您可以使用包中的 AllOfCondition
条件。
营销部门可以用来进行促销,而无需更改您的代码。
促销的最终方案
■ AllOfCondition (condition)
│ # fields: ['id' => 1, 'starts_at' => '2019-05-01 00:00:00', 'ends_at' => '2019-05-01 23:59:59']
│ ║
│ ╚═» ░ DiscountAction (action)
│ # fields: ['parameters' => ['discount' => 10]]
│
├─── ■ TodayIsBirthdayCondition (condition)
│ # fields: ['parent_id' => 1]
│
└─── ■ HasPaidToysCondition (condition)
# fields: ['parent_id' => 1, 'parameters' => ['toy_id' => 5]]
让我们进入实施!
安装包
composer require my-com/laravel-conditional-actions
Laravel
对于版本 < 5.5
将包服务提供者添加到 config/app.php
return [ // ... 'providers' => [ // ... ConditionalActions\ConditionalActionsServiceProvider::class, ], // ...
对于 laravel >= 5.5
Laravel 5.5 使用包自动发现,因此无需手动添加 ServiceProvider
Lumen
在 app.php
中注册服务提供者和配置
$app->configure('conditional-actions'); $app->register(ConditionalActions\ConditionalActionsServiceProvider::class);
添加迁移
php artisan ca:tables
php artisan migrate
php artisan vendor:publish --provider="ConditionalActions\ConditionalActionsServiceProvider"
命令选项
Description: Create a migration for the conditional actions database tables Usage: ca:tables [options] Options: --migrations-path[=MIGRATIONS-PATH] Path to migrations directory (relative to framework base path) [default: "database/migrations"]
实现 Target
Target 是一个对象,它为条件和操作提供所有必要的数据。它也可以是一个 eloquent 模型。
由于 Toy
是条件操作的实体,它应该使用 EloquentTarget
特性(特性具有关系和一些获取模型条件的方法)
class ToysPriceTarget implements TargetContract { use RunsConditionalActions; /** @var Toy */ public $toy; /** @var User */ public $user; public $finalPrice; public function __construct(Toy $toy, User $user) { $this->toy = $toy; $this->user = $user; } /** * Gets state from target. * * @return StateContract */ public function getInitialState(): StateContract { return $this->newState([ 'price' => $this->toy->price, ]); } /** * Sets the state to the target. * * @param StateContract $state */ public function applyState(StateContract $state): void { $this->finalPrice = $state->getAttribute('price'); } /** * Gets root target conditions. * * @return iterable|ConditionContract[] */ public function getRootConditions(): iterable { return $this->toy->getRootConditions(); } /** * Gets children target conditions. * * @param int $parentId * * @return iterable|ConditionContract[] */ public function getChildrenConditions(int $parentId): iterable { return $this->toy->getChildrenConditions($parentId); } }
实现条件
每个条件都应该实现 ConditionalActions\Contracts\ConditionContract
接口。该包有一个基类 ConditionalActions\Entities\Conditions\BaseCondition
,其中包含所有接口方法,除了 check
方法。
class HasPaidToysCondition extends BaseCondition { /** @var ToysService */ private $toysService; // You can use dependency injection in constructor public function __construct(ToysService $toysService) { $this->toysService = $toysService; } /** * Runs condition check. * * @param TargetContract $target * @param StateContract $state * * @return bool */ public function check(TargetContract $target, StateContract $state): bool { $toyId = $this->parameters['toy_id'] ?? null; if (!($target instanceof ToysPriceTarget) || is_null($toyId)) { return false; } return $this->toysService->hasPaidToy($target->user, $toyId); } }
class TodayIsBirthdayCondition extends BaseCondition { /** @var ToysService */ private $toysService; // You can use dependency injection in constructor public function __construct(ToysService $toysService) { $this->toysService = $toysService; } /** * Runs condition check. * * @param TargetContract $target * @param StateContract $state * * @return bool */ public function check(TargetContract $target, StateContract $state): bool { if (!($target instanceof ToysPriceTarget)) { return false; } return Carbon::now()->isSameDay($target->user->birthday); } }
实现操作
每个条件都应该实现 ConditionalActions\Contracts\ActionContract
接口。该包有一个基类 ConditionalActions\Entities\Actions\BaseAction
,其中包含所有接口方法,除了 apply
方法。
class DiscountAction extends BaseAction { /** * Applies action to the state and returns a new state. * * @param StateContract $state * * @return StateContract */ public function apply(StateContract $state): StateContract { $discount = $this->parameters['discount'] ?? 0; $currentPrice = $state->getAttribute('price'); $state->setAttribute('price', $currentPrice - $currentPrice / 100 * $discount); return $state; } }
将条件添加到配置 config/conditional-actions.php
return [ 'conditions' => [ 'AllOfCondition' => ConditionalActions\Entities\Conditions\AllOfCondition::class, 'OneOfCondition' => ConditionalActions\Entities\Conditions\OneOfCondition::class, 'TrueCondition' => ConditionalActions\Entities\Conditions\TrueCondition::class, 'CurrentTimeCondition' => App\ConditionalActions\Conditions\CurrentTimeCondition::class, 'HasPaidToysCondition' => App\ConditionalActions\Conditions\HasPaidToysCondition::class, 'TodayIsBirthdayCondition' => App\ConditionalActions\Conditions\TodayIsBirthdayCondition::class, ], 'actions' => [ 'UpdateStateAttributeAction' => ConditionalActions\Entities\Actions\UpdateStateAttributeAction::class, 'DiscountAction' => App\ConditionalActions\Actions\DiscountAction::class, ], 'use_logger' => env('APP_DEBUG', false), ];
为 Toy
模型实现 API 以添加条件和操作
您可以使用 eloquent 模型或任何其他对象将业务逻辑放入外部存储。
该包为条件和操作提供基本的 CRUD。您可以选择启用它
use ConditionalActions\ConditionalActions; use Illuminate\Support\ServiceProvider; class RouteServiceProvider extends ServiceProvider { // ... public function register() { ConditionalActions::routes(); } }
或者您可以实现自己的API。示例:
# This example is not an API. You can create API as you needed. /** @var Toy $toy */ $toy = Toy::find(10); /** @var Condition $allOf */ $allOf = $toy->conditions()->create([ 'name' => 'AllOfCondition', 'starts_at' => '2019-05-01 00:00:00', 'ends_at' => '2019-05-01 23:59:59', ]); $allOf->actions()->create([ 'name' => 'DiscountAction', 'parameters' => ['discount' => 10], ]); $todayIsBirthday = $allOf->childrenConditions()->make([ 'name' => 'TodayIsBirthdayCondition', ]); $hasPaidToy = $allOf->childrenConditions()->make([ 'name' => 'HasPaidToysCondition', 'parameters' => ['toy_id' => 5], ]); $toy->conditions()->saveMany([$allOf, $hasPaidToy, $todayIsBirthday]);
运行条件操作
$toy = Toy::find(10); // Create a target instance $target = new ToysPriceTarget(Auth::user(), $toy); /* * Run conditional actions. * This method will iterate over all its conditions stored in database and apply actions related to succeed conditions */ $newState = $target->runConditionalActions(); dump($newState->getAttribute('price'));
备注:
该包包括条件和操作
- 条件
AllOfCondition
- 当所有子条件都成功时成功。所有子操作都将包含在父条件AllOfCondition
中; - 条件
OneOfCondition
- 当任何子条件成功时成功。第一个成功条件的所有子操作将包含在父条件OneOfCondition
中; - 条件
TrueCondition
- 总是成功; - 操作
UpdateStateAttributeAction
- 更新状态中的属性值。
条件和操作都有字段
priority
- 执行优先级;- 可空
starts_at
和ends_at
- 在特定时间段启用条件或操作; parameters
- 条件或操作的参数;is_inverted
- 决定条件结果是否应该被反转。