bvtterfly/model-state-machine

此包已被放弃,不再维护。未建议替代包。

Laravel 模型状态机

0.3.0 2023-03-05 21:00 UTC

README

🚨 此包已被放弃 🚨

我不再使用 Laravel,无法证明维护此包所需的时间。这就是我选择放弃它的原因。您可以随意复制我的代码并维护自己的副本。

Laravel 模型状态机

Latest Version on Packagist run-tests Check & fix styling Total Downloads

此包为 Laravel Eloquent 模型属性创建状态机提供支持。

要求

  • PHP 8.1 或更高版本
  • Laravel 9.x 或更高版本

安装

您可以通过 composer 安装此包。

composer require bvtterfly/model-state-machine

用法

例如,我们有一个博客系统,我们的博客文章有三个状态:草稿、待审和已发布。当我们编写文章时,它处于草稿状态。每当我们的博客文章写完时,我们将其安排在将来发布,这会改变文章的状态为待审。一旦文章发布,状态将变为已发布。

表示博客文章状态状态的简单枚举如下

use Bvtterfly\ModelStateMachine\Attributes\AllowTransitionTo;
use Bvtterfly\ModelStateMachine\Attributes\InitialState;

enum PostState: string
{
    #[InitialState]
    #[AllowTransitionTo(self::PENDING)]
    case DRAFT = 'draft';

    #[AllowTransitionTo(self::PUBLISHED)]
    case PENDING = 'pending';
    
    case PUBLISHED = 'published';
    
}

以下是博客文章模型的示例

class Post extends Model
{
    use HasStateMachine;

    protected $casts = [
      'status' => PostState::class  
    ];
    
    
    public function getStateMachineFields(): array
    {
        return [
            'status'
        ];
    }
    
}

一个模型可以有您想要的任意多个状态机字段,您需要使用 getStateMachineFields 方法将它们添加到列表中。

由于状态机从字符串支持的枚举中加载状态配置,您需要在模型中将状态机字段转换为相关联的状态枚举。

现在,您可以得到您的状态机

$stateMachine = $post->getStateMachine('status')

获取所有状态

您可以使用 getAllStates 方法,它返回所有可用状态的集合

$stateMachine->getAllStates();

获取所有允许的转换

您可以使用 getStateTransitions 方法,它返回当前/初始状态的可用转换的集合

$stateMachine->getStateTransitions();

如果状态字段为 null 并且状态配置没有初始状态(未知状态的字段),它将抛出异常。

如果您想获取某个状态的可用的转换,您可以将它传递给该方法

$stateMachine->getStateTransitions(PostState::PENDING);
// or $stateMachine->getStateTransitions('pending');

使用转换

要使用转换,请在状态字段上调用 transitionTo 方法,如下所示

$stateMachine->transitionTo(PostState::PUBLISHED);
// or $stateMachine->transitionTo('published');

您可以将数组作为 transitionTo 方法的第二个参数传递,以便在您的操作和转换中需要额外的数据。

状态动作

您可以为状态更改到状态时运行的动作添加动作。在上面的示例中,也许我们想在文章发布时发送推文并发送电子邮件给订阅者。

我们可以使用 #[Actions] 属性来完成此操作。以下是我们的 PostState 的样子

enum PostState: string
{
    #[InitialState]
    #[AllowTransitionTo(self::PENDING)]
    case DRAFT = 'draft';

    #[AllowTransitionTo(self::PUBLISHED)]
    case PENDING = 'pending';

    #[Actions(SendTweetAction::class, SendEmailToSubscribers::class)]
    case PUBLISHED = 'published';

}

动作是实现了 Bvtterfly\ModelStateMachine\Contracts\StateMachineAction 的类

class SendTweetAction implements StateMachineAction
{

    public function handle(Model $model, array $additionalData): void
    {
        // send tweet...
    }
}

您的动作也可以在构造函数上类型提示它们需要的任何依赖项。所有动作都是通过 Laravel 服务容器解析的,因此依赖项将自动注入。

转换动作

除了状态动作之外,也许您只想在特定的状态转换到另一个状态时运行动作。

您可以将动作数组作为 #[AllowTransitionTo] 的第二个参数传递。

在上面的示例中,如果我们想在文章状态更改为待审时向管理员发送通知,我们的 PostState 将看起来像这样

enum PostState: string
{
    #[InitialState]
    #[AllowTransitionTo(self::PENDING, [SendNotificationToAdmin::class])]
    case DRAFT = 'draft';

    #[AllowTransitionTo(self::PUBLISHED)]
    case PENDING = 'pending';

    #[Actions(SendTweetAction::class, SendEmailToSubscribers::class)]
    case PUBLISHED = 'published';
}

转换动作在状态动作之前运行

带验证的动作

在使用过渡时,您可以作为第二个参数传递额外的数据,并且这些数据将传递给所有操作。因此,在运行操作之前验证这些数据是必要的。

验证器是实现了 Bvtterfly\ModelStateMachine\Contracts\StateMachineValidation 接口的操作。

在上面的例子中,我们希望在帖子状态更改为待处理时向管理员发送通知

$stateMachine->transitionTo(PostState::PENDING, [
    'message' => '...'
]);

以下是我们的 SendNotificationToAdmin 操作的示例

class SendNotificationToAdmin implements StateMachineAction, StateMachineValidation
{

        public function validate(Model $model, array $additionalData): void
    {
        $validator = validator($additionalData, [
            'message' => 'required',
        ]);
        
        if ($validator->fails()) {
            // throw exception
        }
    }

    public function handle(Model $model, array $additionalData): void
    {
        // send notification...
    }
}

自定义过渡类

本包包含一个默认的过渡类,该类在运行状态和过渡操作后保存新状态。如果您需要做的不仅仅是改变到新状态,您可以使用过渡类。

自定义过渡是实现了 Bvtterfly\ModelStateMachine\Contracts\StateTransition 接口的类。

例如,我们希望在帖子模型中存储更改帖子状态为 pending 状态的用户 user_id

class DraftToPending implements StateTransition
{
    public function commitTransition(
            BackedEnum|string $newState,
            Model $model,
            string $field,
            array $additionalData
        ): void {
            $model->{$field} = $newState;
            $model->causer = $additionalData['user_id']
            $model->save();
        }
}

您可以将此类作为第三个参数传递给 #[AllowTransitionTo]

然后,我们的 PostState 将看起来像这样

enum PostState: string
{
    #[InitialState]
    #[AllowTransitionTo(self::PENDING, [SendNotificationToAdmin::class], DraftToPending::class)]
    case DRAFT = 'draft';

    #[AllowTransitionTo(self::PUBLISHED)]
    case PENDING = 'pending';

    #[Actions(SendTweetAction::class, SendEmailToSubscribers::class)]
    case PUBLISHED = 'published';
}

测试

composer test

变更日志

请参阅 变更日志 了解最近更改的详细信息。

安全漏洞

请查看我们如何报告安全漏洞的 安全策略

鸣谢

许可证

MIT许可证(MIT)。请参阅 许可证文件 了解更多信息。