oro/chain-processor

此包已被废弃,不再维护。作者建议使用 oro/platform 包。

ORO ChainProcessor 组件

dev-master / 1.0.x-dev 2018-05-08 17:26 UTC

This package is auto-updated.

Last update: 2021-04-19 20:24:31 UTC


README

目录

概述

Oro ChainProcessor 组件实现了增强的 责任链模式 设计模式,允许高效地处理请求,无需硬编码处理器之间的关系和优先级,或请求到处理器的映射。

以下是对原始设计模式的增强,以使请求处理配置更加灵活:

  • 处理器可以被分组到某些逻辑组中。
  • 可以使用处理器的 priority 属性和组的属性配置处理器的执行顺序。
  • 在请求处理过程中可以跳过处理器组的执行。
  • 可以执行处理器组的一部分,而不是在链中注册的所有处理器的全部。
  • 可以根据每个处理器的属性过滤为具体请求执行的处理器的列表。
  • 可以很容易地添加新的规则来检查处理器是否应该被执行。

Data flow diagram

入门

让我们想象一下,你需要一组对象的不同的文本表示形式,例如实体。一个直接的解决方案可能使用 __toString() 魔术方法或自定义的 toString($parameters) 方法。在一些简单的情况下,这种方法可能是一个好的方法。但是,如果你想要允许外部代码更改对象的现有表示或添加新的表示类型,这种方法似乎不是一个好的选择。

Oro ChainProcessor 组件可以帮助解决这个任务。假设你的应用程序是使用 Symfony 框架构建的,但 Oro ChainProcessor 组件可以很容易地适应其他框架。

  • 注册编译器传递
<?php

namespace Acme\Bundle\TextRepresentationBundle;

use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

use Oro\Component\ChainProcessor\DependencyInjection\CleanUpProcessorsCompilerPass;
use Oro\Component\ChainProcessor\DependencyInjection\LoadAndBuildProcessorsCompilerPass;

class AcmeTextRepresentationBundle extends Bundle
{
    /**
     * {@inheritdoc}
     */
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(
            new LoadAndBuildProcessorsCompilerPass(
                'text_representation.processor_bag_config_provider',
                'text_representation.processor'
            )
        );
        $container->addCompilerPass(
            new CleanUpProcessorsCompilerPass(
                'text_representation.simple_processor_factory',
                'text_representation.processor'
            ),
            PassConfig::TYPE_BEFORE_REMOVING
        );
    }
}
  • 创建主处理器,该处理器将执行文本表示处理器
<?php

namespace Acme\Bundle\TextRepresentationBundle;

use Oro\Component\ChainProcessor\ActionProcessor;
use Oro\Component\ChainProcessor\ContextInterface;

class TextRepresentationProcessor extends ActionProcessor
{
    /**
     * {@inheritdoc}
     */
    public function process(ContextInterface $context)
    {
        $context->setAction('get_text_representation');

        $this->executeProcessors($context);

        if (!$context->hasResult()) {
            throw new \RuntimeException(
                sprintf('Cannot convert "%s" to string.', get_class($context->get('object')))
            );
        }
    }
}
  • 创建一个类,它将是文本表示功能的入口点
<?php

namespace Acme\Bundle\TextRepresentationBundle;

use Oro\Component\ChainProcessor\Context;

class ObjectToStringConverter
{
    /** @var TextRepresentationProcessor */
    protected $processor;

    /**
     * @param TextRepresentationProcessor $processor
     */
    public function __construct(TextRepresentationProcessor $processor)
    {
        $this->processor = $processor;
    }

    /**
     * @param object      $object
     * @param string|null $representationType
     *
     * @return string
     */
    public function convertObjectToString($object, $representationType = null)
    {
        $context = $this->processor->createContext();
        $context->set('object', $object);
        $context->set('class', get_class($object));
        if (null !== $representationType) {
            $context->set('representationType', $representationType);
        }
        $this->processor->process($context);

        return $context->getResult();
    }
}
  • 在 DI 容器中注册服务
services:
    text_representation.object_to_string_converter:
        class: Acme\Bundle\TextRepresentationBundle\ObjectToStringConverter
        arguments:
            - '@text_representation.processor'

    text_representation.processor:
        class: Acme\Bundle\TextRepresentationBundle\TextRepresentationProcessor
        public: false
        arguments:
            - '@text_representation.processor_bag'

    text_representation.processor_bag:
        class: Oro\Component\ChainProcessor\ProcessorBag
        public: false
        arguments:
            - '@text_representation.processor_bag_config_provider'
            - '@text_representation.processor_factory'
            - '%kernel.debug%'

    text_representation.processor_bag_config_provider:
        class: Oro\Component\ChainProcessor\ProcessorBagConfigProvider
        public: false
        arguments:
            - # groups
                'get_text_representation': ['prepare_data', 'format']

    text_representation.processor_factory:
        class: Oro\Component\ChainProcessor\ChainProcessorFactory
        public: false
        calls:
            - [addFactory, ['@text_representation.simple_processor_factory', 10]]
            - [addFactory, ['@text_representation.di_processor_factory']]

    text_representation.simple_processor_factory:
        class: Oro\Component\ChainProcessor\SimpleProcessorFactory
        public: false

    text_representation.di_processor_factory:
        class: Oro\Component\ChainProcessor\DependencyInjection\ProcessorFactory
        public: false
        arguments:
            - '@service_container'
  • 实现一个简单的处理器,该处理器将对象标识符注册到 prepare_data
<?php

namespace Acme\Bundle\TextRepresentationBundle\Processor;

use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;

class GetObjectId implements ProcessorInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContextInterface $context)
    {
        if ($context->has('objectId')) {
            // object id is already retrieved
            return;
        }
        $object = $context->get('object');
        if (!method_exists($object, 'getId')) {
            throw new \RuntimeException(sprintf('Expected "%s::getId()"', get_class($object)));
        }
        $context->set('objectId', $object->getId());
    }
}
    text_representation.processor.get_object_id:
        class: Acme\Bundle\TextRepresentationBundle\Processor\GetObjectId
        tags:
             - { name: text_representation.processor, action: get_text_representation, group: prepare_data, priority: -10 }
  • 实现一个简单的处理器,该处理器将对象表示为 class name - object id 并将其注册到 format
<?php

namespace Acme\Bundle\TextRepresentationBundle\Processor;

use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;

class FormatClassNameIdPair implements ProcessorInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContextInterface $context)
    {
        if ($context->hasResult()) {
            // already formatted
            return;
        }
        $context->setResult(sprintf('%s - %s', $context->get('class'), $context->get('objectId')));
    }
}
    text_representation.processor.format_class_name_id_pair:
        class: Acme\Bundle\TextRepresentationBundle\Processor\FormatClassNameIdPair
        tags:
             - { name: text_representation.processor, action: get_text_representation, group: format, priority: -10 }

现在我们可以为几乎所有的实体获取测试表示

    $converter = $this->getContainer()->get('text_representation.object_to_string_converter');

    $entity = new User();
    $entity->setId(123);

    echo $converter->convertObjectToString($entity);

但是,如果某些包中的实体没有 getId(),但有 getCode() 来获取标识字段值怎么办?在这种情况下,可以在这个包中创建一个特殊的处理器,并将其与通过此实体过滤的 DI 容器注册

<?php

namespace Acme\Bundle\AnotherBundle\Processor;

use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;

class GetObjectIdForTestEntity implements ProcessorInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContextInterface $context)
    {
        if ($context->has('objectId')) {
            return;
        }
        $object = $context->get('object');
        $context->set('objectId', $object->getCode());
    }
}
services:
    text_representation.processor.get_object_id_for_test_entity:
        class: Acme\Bundle\AnotherBundle\Processor\GetObjectIdForTestEntity
        tags:
             - { name: text_representation.processor, action: get_text_representation, group: prepare_data, class: Acme\Bundle\AnotherBundle\Entity\TestEntity }

接下来,让我们想象一下,某些包需要将文本表示从 class name - object id 更改为 [class name - object id]。这可以通过添加一个处理器来实现,该处理器将在构建上一个表示的处理器的处理器之后执行

<?php

namespace Acme\Bundle\AnotherBundle\Processor;

use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;

class DecorateClassNameIdPair implements ProcessorInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContextInterface $context)
    {
        $context->setResult(sprintf('[%s]', $context->getResult()));
    }
}
    text_representation.processor.decorate_class_name_id_pair:
        class: Acme\Bundle\AnotherBundle\Processor\DecorateClassNameIdPair
        tags:
             - { name: text_representation.processor, action: get_text_representation, group: format, priority: -20 }

这是一个示例,说明如何使用Oro ChainProcessor组件。下一节提供了一组关键类,这些类可能有助于您调查此组件。您还可以在Oro Platform中找到其高级用法。

上下文

上下文是一种抽象设计,用于隔离处理器在执行时的环境实现。实际上,上下文只是输入和输出数据的关键值存储。

操作

操作是一组操作,用于在上下文中执行以获得某些输入数据的输出数据。

处理器类型

有三种处理器类型

  • 通用处理器 - 这些处理器在所有操作中执行。如果您需要在开始或结束时执行一些常见操作,它们可能很有用。
  • 未分组处理器 - 与上述内容相同,但仅在指定操作的范围内。
  • 分组处理器 - 这是使用最广泛的处理器。它们在指定操作的范围内定义,并且逻辑上分组。

处理器优先级

每个处理器和每个处理器组都可以有一个用于指定处理器执行顺序的priority属性。优先级越高,处理器执行得越早。默认优先级是0。

处理器的执行顺序如下

  • 初始通用处理器
  • 初始未分组处理器
  • 分组处理器
  • 最终未分组处理器
  • 最终通用处理器

以下表格显示了优先级属性值的限制。

处理器类型 处理器优先级 分组优先级
初始通用处理器 大于或等于0
初始未分组处理器 大于或等于0
分组处理器 从-255到255 从-255到255
最终未分组处理器 小于0
最终通用处理器 小于0

适用检查器

适用检查器用于筛选当前执行上下文中要执行的处理器的处理器。您想要使用的所有适用检查器都应该在处理器包中注册,并且必须实现ApplicableCheckerInterface

以下是一个列表,其中包含默认注册在处理器包中的现有适用检查器

  • GroupRangeApplicableChecker - 它允许仅执行指定范围内的组中的处理器。要设置范围,您可以使用上下文的setFirstGroupsetLastGroup方法。
  • SkipGroupApplicableChecker - 它允许跳过包含在某个组中的处理器。要管理跳过的组,您可以使用上下文的skipGroupundoGroupSkipping方法。
  • MatchApplicableChecker - 它允许根据上下文中存储的数据过滤处理器。

关键类

以下是关键类列表