ebln/psr7-symfony1-adapter

Symfony 1.5 部分PSR-7适配器

v1.6.1 2024-06-07 14:24 UTC

README

为了通过部分PSR-7适配器启用未来兼容的PSR-15中间件。

快速入门

// not fully PSR-7 compliant lazy adapters
$serverRequestAdapter = \brnc\Symfony1\Message\Adapter\Request::fromSfWebRequest($sfWebRequest);
$responseAdapter      = \brnc\Symfony1\Message\Adapter\Response::fromSfWebResponse($sfWebResponse);

ServerRequest

请注意以下默认启用的PSR-7违规行为

默认情况下不保持不可变

因为这只是一个针对 \sfWebRequest 的适配器,无法轻松用另一个实例替换。

此适配器默认情况下在调用 with*() 方法时也会返回完全相同的实例。出于相同的原因,调用无法对底层 \sfWebRequest 对象进行操作的 with*() 方法将引发 \brnc\Symfony1\Message\Exception\LogicException

可以通过使用具有 Request::OPTION_IMMUTABLE_VIOLATION 选项设置为 falseRequest 来更改此默认行为。然后,当调用 with*()-方法时,Request-适配器将始终返回新的实例,并且不会在无法透明地操作 \sfWebRequest 对象的调用上抛出异常。

use brnc\Symfony1\Message\Adapter\Request;

$serverRequestAdapter = Request::fromSfWebRequest(
    $sfWebRequest,
    [
        // If set to true a stream on php://input is used instead of creating one over sfWebRequest::getContent() → defaults to false
        Request::OPTION_BODY_USE_STREAM     => false,
        // sfWebRequest-compatibility mode – set to false if you need PSR-7's immutability
        Request::OPTION_IMMUTABLE_VIOLATION => true, 
    ]
);

Response

请注意默认的可变性

use brnc\Symfony1\Message\Adapter\Response;

$responseAdapter = Response::fromSfWebResponse(
    $sfWebResponse,
    [Response::OPTION_IMMUTABLE_VIOLATION => false]
);
$newInstance     = $responseAdapter->withBody(
    \GuzzleHttp\Psr7\Utils::streamFor(
        '<html><head><title>Hello World!</title></head><body><h1>PSR-7 Adapters!</h1></body></html>'
    )
);
$newestInstance  = $newInstance->withBody(
    \GuzzleHttp\Psr7\Utils::streamFor(
        '<html><head><body><h1>dead end</h1></body></html>'
    )
);

// selects the content of $newInstance to be send instead of the most recent instance's one (i.e. $newestInstance)
$newInstance->preSend();
// N.b. The stream of $newestInstance is still held in memory until $responseAdapter and all copies got destroyed!
//      This might change in the future when this will be refactored to use WeakMap.

$sfWebResponse->send();

将其传递给PSR-15子栈

您可以使用实现 \Psr\Http\Message\ResponseFactoryInterfaceResponseFactory 来在您的PSR-15子栈中“产生”响应。

$request         = \brnc\Symfony1\Message\Adapter\Request::fromSfWebRequest($sfWebRequest);
$responseFactory = new \brnc\Symfony1\Message\Factory\ResponseFactory($sfWebResponse);
// (dependency) inject the ResponseFactory to your dispatcher, middlewares, and handlers
$entryPoint      = new YourPSR15Dispatcher($responseFactory);
// Dispatch your sub-stack via PSR-15
$response        = $entryPoint->handler($response);
// As $response will be linked to $sfWebResponse you don't need to do anything
// if you are in the context of a Symfony1 action. Only call $response->getSfWebResponse() in dire need!

手动将PSR-7响应转录到Symfony1

假设您无法使用其他方法,并且您面临一个任意的PSR-7响应,您可以使用 ResponseTranscriptor 将数据从您的PSR-7响应复制到您的 \sfWebResponse

ResponseTranscriptor 默认使用 NoCookieTranscriptor,这会在存在 Set-Cookie' 标头的情况下失败。将(现在的)Cookie合并到 \sfWebResponse 中不是一件简单的事情。然而,您可以自由实现自己的实现 CookieTranscriptorInterface 的Cookie处理器,并将其作为可选的构造函数参数传递。

// Given arbitrary PSR-7 response…
$psr7response = $psr7responseFactory();
// …use the ResponseTranscriptor in order to–
$transcriptor = new \brnc\Symfony1\Message\Transcriptor\ResponseTranscriptor();
// copy the response's contents.
//   The returned object will be the same as in the argument!
$sfWebResponse = $transcriptor->transcribe($psr7response, $sfWebResponse);

实现的 CookieTranscriptorInterface

已经实现了一些CookieTranscriptors;每个都有其特定的权衡。

CookieHeaderTranscriptor

将您的PSR-7响应中的 Set-Cookie 标头转录到Symfony1响应的Cookie管理中。这带来了所有遗留签名 setrawcookie() 的缺点。首先,它不支持 SameSite-属性,以及其他所有作为 extension-av 的内容,按照RFC 265的规定。

AbstractCookieDispatchTranscriptor

(抽象) CookieDispatchTranscriptor 使用反射并交换响应的事件调度器。它与原始的 sfWebResponse::sendHttpHeaders 实现非常紧密,特别是通过事件进行日志记录的机制。CookieDispatchersfWebResponse 和原始 sfEventDispatcher 之间插入自己,并在Symfony1发送其自己的Cookie之前触发PSR-7响应中的Cookie。您需要根据设置Cookie的来源实现 AbstractCookieDispatchTranscriptortranscribeCookies() 方法。例如,如果您使用第三方库。您的代码最终需要返回一个包含 CookieInterfaceCookieContainerInterface。已经有一个 HeaderCookie,它使用 header() 并期望一个已经制作好并且完整的 Set-Cookie-标头行。还有 SetCookieSetRawCookie,它们将使用具有新签名的相应方法——即三个参数,其中选项数组作为第三个参数。

将其传递给 http-foundation,即现在的Symfony

将此PSR7-Symfony1适配器与 symfony/psr-http-message-bridge 结合使用,通过PSR-7将您的Symfony1堆栈连接到 symfony/http-foundation 对象,并利用使用嵌入(现在的)Symfony组件。

// Use this chain to create a http-foundation request from a Symfony1's \sfWebRequest
$psrRequest            = \brnc\Symfony1\Message\Adapter\Request::fromSfWebRequest($sfWebRequest);
$httpFoundationFactory = \Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory();
$symfonyRequest        = $httpFoundationFactory->createRequest($psrRequest);

// Handle the request with some present day Symfony component
$symfonyResponse = $httpKernel->handle($symfonyRequest);

// Possibly ResponseFactory is best created in the Symfony1 context
$responseFactory = new \brnc\Symfony1\Message\Factory\ResponseFactory($sfWebResponse);

// Obtain other PSR17 factories,
//   while only ResponseFactory & StreamFactory will be used (as of today)
$streamFactory   = \brnc\Symfony1\Message\Factory\GuzzleStreamFactory();
$decoyFactory    = \brnc\Symfony1\Message\Factory\DecoyHttpFactory();
// Construct the PsrHttpFactory from symfony/psr-http-message-bridge and translate…
$psrHttpFactory  = Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory(
    $decoyFactory, $streamFactory, $decoyFactory, $responseFactory
);
$psrResponse     = $psrHttpFactory->createResponse($symfonyResponse);
// As $psrResponse will be linked to $sfWebResponse as it was created through the
// ResponseFactory you don't need to do anything if you exit via an Symfony1 action.
// Only call $psrResponse->getSfWebResponse() in dire need!