ellipse/handlers-adr

实现 ADR 模式的 Psr-15 请求处理器

0.2.0 2018-05-21 12:39 UTC

This package is auto-updated.

Last update: 2024-08-25 23:41:51 UTC


README

此包提供了一种实现 Psr-15 请求处理器,并遵循 Action-Domain-Responder 模式的包。

要求 php >= 7.0

安装 composer require ellipse/handlers-adr

运行测试 ./vendor/bin/kahlan

使用 ADR 模式的请求处理器

Action-Domain-Responder (ADR) 模式用于分离应用程序的领域和展示逻辑。可以概括为使用 Action 对象将一对 DomainResponder 对象粘合在一起,以从传入的请求生成响应。因此,可以将 Action 视为 Psr-15 请求处理器,此包的目标是提供一个可用于任何 Psr-15 分发系统的 ADR 实现。

Ellipse\Handlers\ActionRequestHandler 代表一个通用的 Action,实现了 Psr\Http\Server\RequestHandlerInterface。其第一个构造函数参数是一个实现了 Ellipse\ADR\DomainInterfaceDomain 对象,第二个参数是一个实现了 Ellipse\Handler\ResponderInterfaceResponder 对象。以下是当使用 Psr-7 请求调用 ActionRequestHandler 实例的 ->handle() 方法时发生的操作:

  • 从 Psr-7 请求中提取一个输入数组
  • 通过调用 Domain->payload() 方法并传入输入数组来生成一个负载
  • 通过调用 Responder->response() 方法并传入 Psr-7 请求和负载来生成一个 Psr-7 响应
  • 返回 Psr-7 响应

默认情况下,输入数组是通过合并请求属性、查询参数、解析的正文参数和上传的文件获得的。它们按此顺序合并,这意味着具有相同键的请求属性会被查询参数覆盖,而查询参数会被解析的正文参数覆盖,最终由上传的文件覆盖。可以通过将可调用对象作为 ActionRequestHandler 第三个构造函数参数传递来指定 Action 特定的请求解析逻辑。此请求解析可调用对象以请求作为参数执行,并必须返回一个数组。当返回的不是数组时,会抛出 Ellipse\Handlers\Exceptions\InputTypeException

DomainInterface 定义了一个 ->payload() 方法,该方法接受一个输入数组作为参数,并返回一个实现了 Ellipse\ADR\PayloadInterface 的实现。

PayloadInterface 定义了两个方法:->status() 返回负载状态作为字符串,->data() 返回负载数据作为数组。可以使用 Ellipse\ADR\Payload 类作为 PayloadInterface 的默认实现。它将状态字符串和数据数组作为构造函数参数。

最后,ResponseInterface 定义了一个 ->response() 方法,该方法接受一个请求和一个实现了 PayloadInterface 的实现作为参数,并返回一个响应。

<?php

namespace App\Domain;

use Ellipse\ADR\Payload;
use Ellipse\ADR\PayloadInterface;
use Ellipse\ADR\DomainInterface;

use App\SomeService;

class SomeDomain implements DomainInterface
{
    private $service;

    public function __construct(SomeService $service)
    {
        $this->service = $service;
    }

    public function payload(array $input): PayloadInterface
    {
        // perform domain logic...

        // This payload will be passed to the responder ->response() method.
        return new Payload('FOUND', ['k1' => $v1, 'k2' => $v2]);
    }
}
<?php

namespace App\Responder;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

use Ellipse\ADR\PayloadInterface;
use Ellipse\Handlers\ResponderInterface;

use App\ResponseFactory;

class SomeResponder implements ResponderInterface
{
    private $factory;

    public function __construct(ResponseFactory $factory)
    {
        $this->factory = $factory;
    }

    public function response(ServerRequestInterface $request, PayloadInterface $payload): ResponseInterface
    {
        // Different Psr-7 responses can be produced according to the given Psr-7 request and
        // the given payload.

        if ($payload->status() === 'FOUND') {

            return $this->factory->createFoundResponse('template', $payload->data());

        }

        return $this->factory->createNotFoundResponse();
    }
}
<?php

namespace App;

use App\Domain\SomeDomain;
use App\Responder\SomeResponder;

use Ellipse\Handlers\ActionRequestHandler;

// Create an action request handler using SomeDomain and SomeResponder.
$domain = new SomeSomain(new SomeService);
$responder = new SomeResponder(new ResponseFactory);

$handler = new ActionRequestHandler($domain, $responder);

// A specific request parsing callable can be specified.
$handler = new ActionRequestHandler($domain, $responder, function ($request) {

    $attributes = $request->getAttributes();

    return [
        'key' => explode(' ', $attributes['key']),
    ];

});

// Action request handler instances work like any Psr-15 request handler.
$response = $handler->handle($request);

与 Psr-11 容器一起使用

在实际应用中,通常从容器中检索 DomainResponder 实例。

此包提供了实现 DomainInterfaceResponderInterface 的代理,这些代理 Psr-11 容器条目。

Ellipse\Handlers\ContainerDomain 类接受一个容器和一个 Domain 对象的容器 ID 作为构造函数参数。当调用其 ->payload() 方法时,将从容器中检索 Domain 对象,并返回其 ->payload() 方法产生的负载。如果容器条目不是 DomainInterface 的实现,将抛出 Ellipse\Handlers\Exceptions\ContainedDomainTypeException 异常。

同样地,Ellipse\Handlers\ContainerResponder 类接受一个容器和一个 Responder 对象的容器 ID 作为构造函数参数。容器条目的 ->response() 方法被代理,如果它不是 ResponderInterface 的实现,将抛出 Ellipse\Handlers\Exceptions\ContainedResponderTypeException 异常。

最后,可以使用 Ellipse\Handlers\ContainerRequestParser 类通过容器和可调用的容器 ID 作为构造函数参数检索请求解析的可调用对象。如果容器条目不是一个可调用对象,将抛出 Ellipse\Handlers\Exceptions\ContainedRequestParserTypeException 异常。

<?php

namespace App;

use SomePsr11Container;

use App\Domain\SomeDomain;
use App\Responder\SomeResponder;

use Ellipse\Handlers\ContainerDomain;
use Ellipse\Handlers\ContainerResponder;
use Ellipse\Handlers\ContainerRequestParser;
use Ellipse\Handlers\ActionRequestHandler;

// Register SomeDomain, SomeResponder and a request parser into a Psr-11 container.
$container = new SomePsr11Container;

$container->set(SomeDomain::class, function ($container) {

    $service = $container->get(SomeService::class);

    return new SomeDomain($service);

});

$container->set(SomeResponder::class, function ($container) {

    $factory = $container->get(ResponseFactory::class);

    return new SomeResponder($factory);

});

$container->set('adr.parser', function () {

    return function ($request) {

        $attributes = $request->getAttributes();

        return [
            'key' => explode(' ', $attributes['key']),
        ];

    };

});

// Create an action using domain, responder and request parser proxying container entries.
$domain = new ContainerDomain($container, SomeDomain::class);
$responder = new ContainerResponder($container, SomeResponder::class);
$parser = new ContainerRequestParser($container, 'adr.parser');

$handler = new ActionRequestHandler($domain, $responder, $parser);

// Actual domain, responder and request parser are retrieved from the container when the request is handled
// by the action.
$response = $handler->handle($request);

当然,可以使用来自 ellipse/container-reflection 包的 Ellipse\Container\ReflectionContainer 类自动装配 DomainResponder 类。

<?php

namespace App;

use SomePsr11Container;

use App\Domain\SomeDomain;
use App\Responder\SomeResponder;

use Ellipse\ADR\DomainInterface;
use Ellipse\Handlers\ResponderInterface;
use Ellipse\Handlers\ActionRequestHandler;
use Ellipse\Container\ReflectionContainer;

// Get some Psr-11 container.
$container = new SomePsr11Container;

// Decorate the container with a reflection container.
// Specify the domain and responder implementations can be auto wired.
$reflection = new ReflectionContainer($container, [
    DomainInterface::class,
    ResponderInterface::class,
]);

// Create an action using domain and responder proxying container entries.
$domain = new ContainerDomain($reflection, SomeDomain::class);
$responder = new ContainerResponder($reflection, SomeResponder::class);

$handler = new ActionRequestHandler($domain, $responder);

// Instances of SomeDomain and SomeResponder are built using auto wiring.
$response = $handler->handle($request);