alterway/component-workflow

此包最新版本(v1.0.0)没有可用的许可证信息。

Alter Way大量使用的工作流组件

v1.0.0 2014-02-18 13:47 UTC

This package is not auto-updated.

Last update: 2024-09-24 07:22:08 UTC


README

此组件提供了一个PHP库编写的工作流引擎。

此库将工作流程视为一个简单的有向图:顶点代表节点,边代表转换。

节点

节点代表生命周期中的某个点。`Node` 类实现了这个概念。节点在整个工作流中通过唯一名称引用。约束是 `NodeMap` 类的责任。

转换

转换是节点之间的链接。《Transition》类实现了这个概念。在创建时,转换被赋予一个实现《SpecificationInterface》的规范对象。规范用作业务规则来决定在工作流中向何处推进。

令牌

令牌是用于初始化特定节点的工作流程的简单字符串。思想是将令牌视为放置在节点中心的物品。当工作流引擎启动时,令牌会从节点移动到节点。

事件

事件是每次令牌到达节点时创建的对象。《Event》类实现了这个概念。此类扩展了来自 Symfony EventDispatcher 组件的《Event》类。您可以为实现任何业务行为编写监听器或订阅者。

用法

假设您正在编写一个 PHP 博客引擎,并希望实现以下工作流程

  • 一篇文章最初作为草稿存在
  • 准备好后,文章会被发布
  • 如果引起争议,文章会被删除
  • 如果太旧,文章会被存档

首先,您需要为每个业务规则编写实现《SpecificationInterface》的类

namespace BlogEngine\Domain\Specification;

use Alterway\Component\Workflow\ContextInterface;
use Alterway\Component\Workflow\SpecificationInterface;

class DraftableArticleSpecification implements SpecificationInterface
{
    public function isSatisfiedBy(ContextInterface $context)
    {
        // an article can always be drafted
        return true;
    }
}

class PublishableArticleSpecification implements SpecificationInterface
{
    public function isSatisfiedBy(ContextInterface $context)
    {
        // an article needs two reviews to be published
        return 1 < count($context->get('article')->getReviews());
    }
}

class DeletableArticleSpecification implements SpecificationInterface
{
    public function isSatisfiedBy(ContextInterface $context)
    {
        // an article can always be deleted if requested
        return 'delete' === $context->get('action');
    }
}

class ArchivableArticleSpecification implements SpecificationInterface
{
    public function isSatisfiedBy(ContextInterface $context)
    {
        // an article needs to be one month old to be archived
        $publishedAtPlusOneMonth = clone $context->get('publishedAt');
        $publishedAtPlusOneMonth->modify('+1 month');

        return 'archive' === $context->get('action') && $publishedAtPlusOneMonth < $context->get('now');
    }
}

然后,您可以使用《Builder》类和规范来描述工作流程

namespace BlogEngine\Domain\Service;

use Alterway\Component\Workflow\Builder;
use Alterway\Component\Workflow\ContextInterface;
use BlogEngine\Domain\Event\ArticleSubscriber;
use BlogEngine\Domain\Specification\DraftableArticleSpecification;
use BlogEngine\Domain\Specification\PublishableArticleSpecification;
use BlogEngine\Domain\Specification\DeletableArticleSpecification;
use BlogEngine\Domain\Specification\ArchivableArticleSpecification;
use BlogEngine\Util\Context;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class ArticleService
{
    private $workflow;

    public function __construct(EventDispatcherInterface $eventDispatcher)
    {
        $this->workflow = (new Builder($eventDispatcher))
            ->open('article.draft', new DraftableArticleSpecification())
            ->link('article.draft', 'article.published', new PublishableArticleSpecification())
            ->link('article.published', 'article.deleted', new DeletableArticleSpecification())
            ->link('article.published', 'article.archived', new ArchivableArticleSpecification())
            ->getWorkflow();

        $eventDispatcher->addSubscriber(new ArticleSubscriber());
    }

    public function create(Article $article)
    {
        $this->advance($article, new Context());
    }

    public function publish(Article $article)
    {
        $context = new Context();
        $context->set('article', $article);

        $this->advance($article, $context);
    }

    public function delete(Article $article)
    {
        $context = new Context();
        $context->set('action', 'delete');

        $this->advance($article, $context);
    }

    public function archive(Article $article)
    {
        $context = new Context();
        $context->set('action', 'archive');
        $context->set('publishedAt', $article->getPublishedAt());
        $context->set('now', new \DateTime());

        $this->advance($article, $context);
    }

    private function advance($article, ContextInterface $context)
    {
        try {
            $this->workflow->initialize($article->getToken())->next($context);
        } catch (\LogicException $e) {
            // the workflow reports a problem
        }
    }
}

最后,您必须监听工作流程派发的事件来附加业务行为

namespace BlogEngine\Domain\Event;

use Alterway\Component\Workflow\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ArticleSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(
            'article.draft' => array('onDraft', 0),
            'article.published' => array('onPublished', 0),
            'article.deleted' => array('onDeleted', 0),
            'article.archived' => array('onArchived', 0),
        );
    }

    public function onDraft(Event $event) { /* ... */ }

    public function onPublished(Event $event) { /* ... */ }

    public function onDeleted(Event $event) { /* ... */ }

    public function onArchived(Event $event) { /* ... */ }
}

贡献

非常感谢,加上糖,提供了 phpspec 规范,并在贡献代码时应为绿色。

参考

理论

PHP

许可

有关详细信息,请参阅捆绑的 LICENSE 文件。

赞助商