Skip to content

Commit

Permalink
Functional Psr15 bridge (#1)
Browse files Browse the repository at this point in the history
Operative bridge to allow execute any psr-15 middleware transparently in laravel
  • Loading branch information
joskfg authored Jan 28, 2019
1 parent e3acda0 commit 2c25440
Show file tree
Hide file tree
Showing 15 changed files with 629 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/vendor/
composer.lock
.php_cs.cache
39 changes: 39 additions & 0 deletions .php_cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
;
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'concat_space' => ['spacing' => 'one'],
'new_with_braces' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_phpdoc' => true,
'no_empty_comment' => true,
'no_leading_import_slash' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unused_imports' => true,
'ordered_imports' => ['importsOrder' => null, 'sortAlgorithm' => 'alpha'],
'phpdoc_add_missing_param_annotation' => ['only_untyped' => true],
'phpdoc_align' => true,
'phpdoc_no_empty_return' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_separation' => true,
'phpdoc_to_comment' => true,
'phpdoc_types' => true,
'psr0' => false,
'psr4' => true,
'return_type_declaration' => ['space_before' => 'none'],
'single_blank_line_before_namespace' => true,
'single_quote' => true,
'space_after_semicolon' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => true,
'trim_array_spaces' => true,
'whitespace_after_comma_in_array' => true,
])
->setFinder($finder)
;
17 changes: 17 additions & 0 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
filter:
paths: [src/*]
excluded_paths: [tests/*]
checks:
php:
code_rating: true
tools:
external_code_coverage:
timeout: 600
runs: 2
php_code_coverage: false
php_loc:
enabled: true
excluded_dirs: [tests, vendor]
php_cpd:
enabled: true
excluded_dirs: [tests, vendor]
32 changes: 32 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
language: php

sudo: false

matrix:
include:
- php: 7.1
env: COLLECT_COVERAGE=true VALIDATE_CODING_STYLE=true
- php: 7.2
env: COLLECT_COVERAGE=true VALIDATE_CODING_STYLE=true
- php: master
env: COLLECT_COVERAGE=true VALIDATE_CODING_STYLE=false
allow_failures:
- php: master
fast_finish: true

cache:
directories:
- $HOME/.composer/cache

before_install:
- travis_retry composer self-update

install:
- travis_retry composer update --no-interaction --prefer-source

script:
- composer phpunit

after_script:
- if [ "$COLLECT_COVERAGE" == "true" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover build/clover.xml; fi
- if [ "$VALIDATE_CODING_STYLE" == "true" ]; then composer phpcs; fi
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Laravel PSR-15 Middleware Bridge
=====

[![Latest Version](https://img.shields.io/github/release/softonic/laravel-psr15-bridge.svg?style=flat-square)](https://github.com/softonic/laravel-psr15-bridge/releases)
[![Software License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE.md)
[![Build Status](https://img.shields.io/travis/softonic/laravel-psr15-bridge/master.svg?style=flat-square)](https://travis-ci.org/softonic/laravel-psr15-bridge)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/softonic/laravel-psr15-bridge.svg?style=flat-square)](https://scrutinizer-ci.com/g/softonic/laravel-psr15-bridge/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/softonic/laravel-psr15-bridge.svg?style=flat-square)](https://scrutinizer-ci.com/g/softonic/laravel-psr15-bridge)
[![Total Downloads](https://img.shields.io/packagist/dt/softonic/laravel-psr15-bridge.svg?style=flat-square)](https://packagist.org/packages/softonic/laravel-psr15-bridge)

This package provides a Laravel middleware bridge for [PSR-15](https://www.php-fig.org/psr/psr-15/) inspired in [jshannon63/laravel-psr15-middleware](https://github.com/jshannon63/laravel-psr15-middleware).

Installation
-------

To install, use composer:

```
composer require softonic/laravel-psr15-bridge
```

You are ready to use it!

Usage
-------

The bridge adapter receive a PSR-15 middleware via injection, so the bridge is transparent for Laravel and you can use
it as any other middleware.


Example based on [OpenApi Validation Middleware](https://github.com/hkarlstrom/openapi-validation-middleware):

Wrapping [OpenApi Validation Middleware](https://github.com/hkarlstrom/openapi-validation-middleware) within the bridge.
```php
// app/Providers/AppServiceProvider.php

use HKarlstrom\Middleware\OpenApiValidation

/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(OpenApiValidation::class, function () {
$validator = new \HKarlstrom\Middleware\OpenApiValidation('schema.json');

return Psr15MiddlewareAdapter::adapt($validator);
}
}
```

Now you can use it anywhere or for example generate an alias.

```php
// app/Http/Kernel.php

protected $routeMiddleware = [
...
'openapi-validation' => OpenApiValidation::class,
];
```

Check [laravel middleware](https://laravel.com/docs/5.7/middleware) for more information.

How it works
------------

In the next diagram you can see the request and response flow.

![psr-15 bridge flow](doc/bridge_flow.png)

As you can see, when you execute `Psr15MiddlewareAdapter::adapt($validator);`, you are adding an envelop to the psr-15
middleware that converts the request and response transparently for the middleware and the laravel itself.


Testing
-------

`softonic/laravel-psr15-bridge` has a [PHPUnit](https://phpunit.de) test suite and a coding style compliance test suite using [PHP CS Fixer](http://cs.sensiolabs.org/).

To run the tests, run the following command from the project folder.

``` bash
$ docker-compose run test
```

To run interactively using [PsySH](http://psysh.org/):
``` bash
$ docker-compose run psysh
```

License
-------

The Apache 2.0 license. Please see [LICENSE](LICENSE) for more information.

[PSR-2]: http://www.php-fig.org/psr/psr-2/
[PSR-4]: http://www.php-fig.org/psr/psr-4/
2 changes: 2 additions & 0 deletions build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"issues": "https://github.com/softonic//issues"
},
"require": {
"php": ">=7.0"
"php": ">=7.1",
"illuminate/http": "^5.6",
"zendframework/zend-diactoros": "^1.7",
"psr/http-server-middleware": "^1.0",
"symfony/psr-http-message-bridge": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0",
Expand All @@ -32,4 +36,3 @@
"fix-cs": "php-cs-fixer fix -v --diff --allow-risky=yes;"
}
}

Binary file added doc/bridge_flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3.2'

services:
test:
volumes:
- ./:/app
image: ricc/composer-prestissimo:latest
command: composer run test

fixcs:
volumes:
- ./:/app
image: ricc/composer-prestissimo:latest
command: composer run fix-cs

psysh:
volumes:
- ./:/app
image: ricc/psysh:latest
33 changes: 33 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">

<testsuites>
<testsuite name="Laravel PSR-15 Middleware Bridge">
<directory>tests</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory suffix=".php">src</directory>
</whitelist>
</filter>

<logging>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage" charset="UTF-8" yui="true" highlight="true"/>
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/clover.xml"/>
</logging>

</phpunit>
90 changes: 90 additions & 0 deletions src/NextHandlerAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Softonic\Laravel\Middleware\Psr15Bridge;

use Closure;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Component\HttpFoundation\Request;

class NextHandlerAdapter implements RequestHandlerInterface
{
/**
* @var Request
*/
private $foundationRequest;

/**
* @var Closure
*/
private $next;

/**
* @var DiactorosFactory
*/
private $diactorosFactory;

/**
* @var HttpFoundationFactory
*/
private $httpFoundationFactory;

public function __construct(
HttpFoundationFactory $httpFoundationFactory,
DiactorosFactory $diactorosFactory,
Request $foundationRequest,
Closure $next
) {
$this->diactorosFactory = $diactorosFactory;
$this->foundationRequest = $foundationRequest;
$this->next = $next;
$this->httpFoundationFactory = $httpFoundationFactory;
}

/**
* Intercept communication between the PSR-15 middleware and the next middleware/controller.
*
* To allow the next execution we need to restore the request to a HttpFoundationRequest
* and wait for a response that must be adapter to PSR-7 response to allow the
* PSR-15 middleware process it.
*
* @return ResponseInterface
*/
public function handle(ServerRequestInterface $psr7Request): ResponseInterface
{
$request = $this->convertRequest($psr7Request, $this->foundationRequest);
$response = ($this->next)($request);

return $this->getPsr7Response($response);
}

private function convertRequest(ServerRequestInterface $psr7Request, $originalRequest): Request
{
$foundation_request = $this->httpFoundationFactory->createRequest($psr7Request);

$originalRequest->query = clone $foundation_request->query;
$originalRequest->request = clone $foundation_request->request;
$originalRequest->attributes = clone $foundation_request->attributes;
$originalRequest->cookies = clone $foundation_request->cookies;
$originalRequest->files = clone $foundation_request->files;
$originalRequest->server = clone $foundation_request->server;
$originalRequest->headers = clone $foundation_request->headers;

return $originalRequest;
}

/**
* @param $response
*
* @return ResponseInterface|DiactorosFactory|\Zend\Diactoros\Response
*/
protected function getPsr7Response($response)
{
return $this->diactorosFactory->createResponse($response);
}
}

;
24 changes: 24 additions & 0 deletions src/NextHandlerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Softonic\Laravel\Middleware\Psr15Bridge;

use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Component\HttpFoundation\Request;

class NextHandlerFactory
{
public function getHandler(
HttpFoundationFactory $httpFoundationFactory,
DiactorosFactory $diactorosFactory,
Request $request,
\Closure $next
) {
return new NextHandlerAdapter(
$httpFoundationFactory,
$diactorosFactory,
$request,
$next
);
}
}
Loading

0 comments on commit 2c25440

Please sign in to comment.