Skip to content

Commit

Permalink
Merge pull request #30 from ebln/response-mgmt
Browse files Browse the repository at this point in the history
Unleash PSR-15 & symfony/psr-http-message-bridge
  • Loading branch information
ebln committed Oct 19, 2021
2 parents fda4d09 + 8bf4058 commit 5721cc4
Show file tree
Hide file tree
Showing 42 changed files with 1,128 additions and 457 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
/app/config/parameters.yml
/build/
/phpunit.xml
.php_cs*
.php-cs-fixer*
!.php_cs.dist
!.php-cs-fixer.dist
!.php-cs-fixer.dist.*
/var/*
!/var/cache
/var/cache/*
Expand Down
38 changes: 19 additions & 19 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,32 @@
->exclude('vendor')
->exclude('mock')
->exclude('DNC')
->exclude('tests')
->in(__DIR__);

$config = new PhpCsFixer\Config();
$config->setRiskyAllowed(true)
->setRules(
->setRules( // @see https://mlocati.github.io/php-cs-fixer-configurator/#version:3.2
[
'@PSR2' => true,
'@Symfony' => true,
'@PhpCsFixer' => true,
'@PhpCsFixer:risky' => true,
'@PHPUnit75Migration:risky' => true,
'@PHP73Migration' => true,
'@PHP71Migration:risky' => true,
'php_unit_dedicate_assert' => ['target' => '5.6'],
'array_syntax' => ['syntax' => 'short'],
'no_superfluous_phpdoc_tags' => true,
'native_function_invocation' => false,
'concat_space' => ['spacing' => 'one'],
'phpdoc_types_order' => ['null_adjustment' => 'always_first', 'sort_algorithm' => 'alpha'],
'single_line_comment_style' => [
'@PSR2' => true,
'@Symfony' => true,
'@PhpCsFixer' => true,
'@PhpCsFixer:risky' => true,
'@PHPUnit75Migration:risky' => true,
'@PHP73Migration' => true,
'@PHP71Migration:risky' => true,
'php_unit_dedicate_assert' => ['target' => '5.6'],
'array_syntax' => ['syntax' => 'short'],
'no_superfluous_phpdoc_tags' => true,
'native_function_invocation' => false,
'concat_space' => ['spacing' => 'one'],
'phpdoc_types_order' => ['null_adjustment' => 'always_first', 'sort_algorithm' => 'alpha'],
'single_line_comment_style' => [
'comment_types' => ['hash'],
],
'phpdoc_summary' => false,
'cast_spaces' => ['space' => 'none'],
'binary_operator_spaces' => ['default' => null, 'operators' => ['=' => 'align_single_space_minimal', '=>' => 'align_single_space_minimal']],
'phpdoc_summary' => false,
'cast_spaces' => ['space' => 'none'],
'binary_operator_spaces' => ['default' => null, 'operators' => ['=' => 'align_single_space_minimal', '=>' => 'align_single_space_minimal']],
'php_unit_test_class_requires_covers' => false,
]
)
->setFinder($finder);
Expand Down
35 changes: 26 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,39 @@ Intended to follow [«Keep a Changelog»](https://keepachangelog.com/en/)
## [Unreleased] (meant as staging area)

### Added

- Changelog
- composer-normalize
- Updated dev dependencies

### Fixed
- DX: makefile, dockerfile & xdebug
-

### TODO
- ResponseFactory
- ResponseTranscriptor

- Cookies: Write to response
- Cookies: Read from request
- Cookies: write and overwrite to request
- Refactor `Request::withUri` to be closer to PSR-7
- Refactor `BodyStreamHook::addBodyFromResponse` using WeakMap for a PHP >=8.0 (or with [polyfill](https://github.com/BenMorel/weakmap-polyfill))
- Remove `mock/guzzle-psr7` when `php-http/psr7-integration-tests` supports `GuzzleHttp\Psr7 ^2.0`
- Allow configurable StreamFactory instead of hardcoded used

----

## [1.4.0] - 2021-10-19

### Added

- PSR-17 `ResponseFactory` to enable PSR-15
- `ResponseTranscriptor` to transcribe PSR-7 responses directly to Symfony1 one's
- Dependency to PSR-17 i.e. `psr/http-factory`
- PSR-17 GuzzleStreamFactory and a DecoyHttpFactory to support `symfony/psr-http-message-bridge`
- Started a changelog
- composer-normalize

### Changed

- Updated dev dependencies
- Renamed "Utillity" to `brnc\Symfony1\Message\Utility`, technically [BREAKING] yet was never supposed to be used in user land

### Fixed

- DX: makefile, dockerfile & xdebug

----
No changelog before October 2021, the following was only casually reconstructed
Expand Down
75 changes: 68 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ Partial PSR-7 Adapters for Symfony 1.5
To enable the use of future-proof PSR-15 middlewares via partial PSR-7 adapters.

## Quickstart

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

## ServerRequest

Please mind the following PSR-7 violation which is enabled by default:

### No immutability by default

as this is just an adapter for `\sfWebRequest` which cannot easily be replaced with another instance.

This adapter – by default – also returns the very same instance when calling `with*()` methods.
For the same reason calls to methods which cannot act on and alter the underlying `\sfWebRequest`
This adapter – by default – also returns the very same instance when calling `with*()` methods. For the same reason calls to methods which cannot act on and alter the underlying `\sfWebRequest`
will throw an `\brnc\Symfony1\Message\Exception\LogicException`.

This default behaviour can be changed by creating the `Request` using
the `Request::OPTION_IMMUTABLE_VIOLATION` option set to `false`.
The `Request`-adapter will then always return new instances when `with*()`-methods are called and won't throw exceptions on calls which cannot transparently act on the `\sfWebRequest`- object.

This default behaviour can be changed by creating the `Request` using the `Request::OPTION_IMMUTABLE_VIOLATION` option set to `false`. The `Request`-adapter will then always return new instances when `with*()`-methods are called and won't throw exceptions on calls which cannot transparently act on the `\sfWebRequest`- object.

```php
use brnc\Symfony1\Message\Adapter\Request;
Expand All @@ -39,8 +39,8 @@ $serverRequestAdapter = Request::fromSfWebRequest(
```

## Response
Please mind the default to mutability!

Please mind the default to mutability!

```php
use brnc\Symfony1\Message\Adapter\Response;
Expand Down Expand Up @@ -68,3 +68,64 @@ $newInstance->preSend();
$sfWebResponse->send();

```

## Pass it down to a PSR-15 sub-stack

You may use the `ResponseFactory` implementing `\Psr\Http\Message\ResponseFactoryInterface` in order to "spawn" responses within your PSR-15 sub-stack.

```php
$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!
```

## Manually transcribe a PSR-7 Response to Symfony1

Assume you couldn't use other means, and you're confronted with an arbitrary PSR-7 response you can use the `ResponseTranscriptor` to copy the data from your PSR-7 response to your `\sfWebResponse`.

Currently the `ResponseTranscriptor` doesn't support cookies, and will fail fast and hard if it encounters some. You are free to implement your own Cookie-Handler implementing `CookieTranscriptorInterface` and pass it as an optional constructor argument

```php
// 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);
```

## Pass it down to http-foundation i.e. present-day Symfony

Combine this PSR7-Symfony1 Adapter and `symfony/psr-http-message-bridge` to connect your Symfony1 stack via PSR-7 to `symfony/http-foundation` objects and leverage using embedded (present-day) Symfony components.

```php
// 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!
```
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"require": {
"php": "^7.4 || ^8.0",
"guzzlehttp/psr7": "^1.7 || ^2.0",
"psr/http-factory": "^1.0",
"webmozart/assert": "^1.6"
},
"require-dev": {
Expand Down Expand Up @@ -62,7 +63,7 @@
"coverage": "XDEBUG_MODE=coverage phpunit --coverage-html coverage --coverage-text --colors=auto && chown -R 1000:1000 coverage",
"quality": [
"composer normalize --dry-run",
"psalm --find-unused-psalm-suppress",
"psalm --find-unused-psalm-suppress --show-info=true",
"phpstan analyse",
"phpmd src,tests ansi naming # cleancode,codesize,controversial,design,unusedcode",
"@style-check",
Expand Down
57 changes: 56 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion mock/guzzle-psr7/function_include.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

declare(strict_types=1);

/*
/**
* TODO remove this and the whole directory once php-http/psr7-integration-tests
* supports GuzzleHttp\Psr7 ^2
*
* Sadly needed as php-http/psr7-integration-tests @ 1.1.1
* relies on the deprecated GuzzleHttp\Psr7::stream_for instead of Utils::streamFor
*
* @internal
*/
// @codeCoverageIgnoreStart
if (!function_exists('GuzzleHttp\Psr7\stream_for')) {
require __DIR__ . '/stream_for.php';
}
5 changes: 4 additions & 1 deletion mock/guzzle-psr7/stream_for.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

namespace GuzzleHttp\Psr7;

/*
/**
* TODO remove this and the whole directory once php-http/psr7-integration-tests
* supports GuzzleHttp\Psr7 ^2
*
* Sadly needed as php-http/psr7-integration-tests @ 1.1.1
* relies on the deprecated GuzzleHttp\Psr7::stream_for instead of Utils::streamFor
*
* @internal
* @codeCoverageIgnore
*/
function stream_for($resource = '', array $options = [])
{
Expand Down
2 changes: 2 additions & 0 deletions mock/sfEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/**
* Minimal mock of symfony's sfEvent to enable standalone testing
*
* @internal
*/
class sfEvent
{
Expand Down
2 changes: 2 additions & 0 deletions mock/sfEventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/**
* Minimal mock of symfony's sfEventDispatcher to enable standalone testing
*
* @internal
*/
class sfEventDispatcher
{
Expand Down
2 changes: 2 additions & 0 deletions mock/sfWebRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

/**
* Minimal mock of symfony's sfWebRequest to enable standalone testing
*
* @internal
*/
class sfWebRequest
{
Expand Down
2 changes: 2 additions & 0 deletions mock/sfWebResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

/**
* Minimal mock of symfony's sfWebResponse to enable standalone testing
*
* @internal
*/
class sfWebResponse
{
Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ parameters:
paths:
- src
- tests
excludePaths:
analyse:
- src/Factory/DecoyHttpFactory.php
ignoreErrors:
-
message: "#^Unsafe usage of new static\\(\\)\\.$#"
Expand Down
Loading

0 comments on commit 5721cc4

Please sign in to comment.