Skip to content

Commit 9f1ea79

Browse files
committed
Add PSR-15 middleware
1 parent ee52d26 commit 9f1ea79

File tree

10 files changed

+382
-14
lines changed

10 files changed

+382
-14
lines changed

README.md

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use Selective\Validation\ValidationResult;
2828
// ...
2929

3030
// Get all POST values
31-
$data = $request->getParsedBody();
31+
$data = (array)$request->getParsedBody();
3232

3333
$validation = new ValidationResult();
3434

@@ -52,23 +52,49 @@ if ($validation->isFailed()) {
5252
}
5353
```
5454

55-
### PSR-7 Middleware
55+
### Middleware
5656

57-
This validation middleware catches the `ValidationException` exception and converts it into a nice JSON response:
57+
The `ValidationExceptionMiddleware` PSR-15 middleware catches all exceptions and converts it into a nice JSON response.
58+
59+
#### Slim 4 integration
60+
61+
```php
62+
<?php
63+
64+
use Selective\Validation\Middleware\ValidationExceptionMiddleware;
65+
use Slim\Factory\AppFactory;
66+
67+
require_once __DIR__ . '/../vendor/autoload.php';
68+
69+
$app = AppFactory::create();
70+
71+
$app->add(ValidationExceptionMiddleware::class); // <--- add middleware
72+
73+
// ...
74+
75+
$app->run();
76+
```
77+
78+
#### Usage
5879

5980
```php
60-
use Psr\Http\Message\ResponseInterface as Response;
61-
use Psr\Http\Message\ServerRequestInterface as Request;
6281
use Selective\Validation\ValidationException;
82+
use Selective\Validation\ValidationResult;
6383

64-
// Validation middleware
65-
$app->add(function (Request $request, Response $response, $next) {
66-
try{
67-
return $next($request, $response);
68-
} catch (ValidationException $exception) {
69-
return $response->withStatus(422)->withJson(['error' => $exception->getValidation()->toArray()]);
70-
}
71-
});
84+
$validation = new ValidationResult();
85+
86+
// Validate username
87+
if (empty($data->username)) {
88+
$validation->addError('username', 'Input required');
89+
}
90+
91+
// Check validation result
92+
if ($validation->isFailed()) {
93+
$validation->setMessage('Please check your input');
94+
95+
// Trigger the validation middleware
96+
throw new ValidationException($validation);
97+
}
7298
```
7399

74100
## License

composer.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
],
99
"license": "MIT",
1010
"require": {
11-
"php": ">=7.1"
11+
"php": ">=7.1",
12+
"psr/http-factory": "^1.0",
13+
"psr/http-server-middleware": "^1.0"
1214
},
1315
"require-dev": {
16+
"fig/http-message-util": "^1.1",
1417
"overtrue/phplint": "^1.1",
1518
"phpstan/phpstan-shim": "^0.11",
1619
"phpunit/phpunit": "^7.0",
20+
"relay/relay": "^2.0",
21+
"slim/psr7": "^0.6.0",
1722
"squizlabs/php_codesniffer": "^3.4"
1823
},
1924
"scripts": {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Selective\Validation\Encoder;
4+
5+
use UnexpectedValueException;
6+
7+
/**
8+
* Encoder interface.
9+
*/
10+
interface EncoderInterface
11+
{
12+
/**
13+
* Encode the given data to string.
14+
*
15+
* @param mixed $data The data
16+
*
17+
* @throws UnexpectedValueException
18+
*
19+
* @return string The encoded string
20+
*/
21+
public function encode($data): string;
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Selective\Validation\Encoder;
4+
5+
use UnexpectedValueException;
6+
7+
/**
8+
* Encoder interface.
9+
*/
10+
final class JsonEncoder implements EncoderInterface
11+
{
12+
/**
13+
* Encode the given data to string.
14+
*
15+
* @param mixed $data The data
16+
*
17+
* @throws UnexpectedValueException
18+
*
19+
* @return string The encoded string
20+
*/
21+
public function encode($data): string
22+
{
23+
$result = json_encode($data);
24+
25+
if ($result === false) {
26+
throw new UnexpectedValueException(
27+
sprintf(
28+
'JSON encoding failed. Code: %s. Error: %s.',
29+
json_last_error(),
30+
json_last_error_msg()
31+
)
32+
);
33+
}
34+
35+
return $result;
36+
}
37+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace Selective\Validation\Middleware;
4+
5+
use Psr\Http\Message\ResponseFactoryInterface;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use Psr\Http\Server\MiddlewareInterface;
9+
use Psr\Http\Server\RequestHandlerInterface;
10+
use Selective\Validation\Encoder\EncoderInterface;
11+
use Selective\Validation\ValidationException;
12+
13+
/**
14+
* A JSON validation exception middleware.
15+
*/
16+
final class ValidationExceptionMiddleware implements MiddlewareInterface
17+
{
18+
/**
19+
* @var ResponseFactoryInterface
20+
*/
21+
private $responseFactory;
22+
23+
/**
24+
* @var EncoderInterface
25+
*/
26+
private $encoder;
27+
28+
/**
29+
* Constructor.
30+
*
31+
* @param ResponseFactoryInterface $responseFactory The response factory
32+
* @param EncoderInterface $encoder The encoder
33+
*/
34+
public function __construct(
35+
ResponseFactoryInterface $responseFactory,
36+
EncoderInterface $encoder
37+
) {
38+
$this->responseFactory = $responseFactory;
39+
$this->encoder = $encoder;
40+
}
41+
42+
/**
43+
* Invoke middleware.
44+
*
45+
* @param ServerRequestInterface $request The request
46+
* @param RequestHandlerInterface $handler The handler
47+
*
48+
* @return ResponseInterface The response
49+
*/
50+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
51+
{
52+
try {
53+
return $handler->handle($request);
54+
} catch (ValidationException $exception) {
55+
$response = $this->responseFactory->createResponse()->withStatus(422);
56+
57+
$response->getBody()->write($this->encoder->encode([
58+
'error' => $exception->getValidation()->toArray(),
59+
]));
60+
61+
return $response;
62+
}
63+
}
64+
}

tests/Encoder/JsonEncoderTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Selective\Validation\Test\Encoder;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Selective\Validation\Encoder\JsonEncoder;
7+
use UnexpectedValueException;
8+
9+
/**
10+
* Tests.
11+
*
12+
* @coversDefaultClass \Selective\Validation\Encoder\JsonEncoder
13+
*/
14+
class JsonEncoderTest extends TestCase
15+
{
16+
/**
17+
* Test.
18+
*
19+
* @return void
20+
*/
21+
public function testEncode()
22+
{
23+
$encoder = new JsonEncoder();
24+
$actual = $encoder->encode(['key' => 'value']);
25+
26+
static::assertSame('{"key":"value"}', $actual);
27+
}
28+
29+
/**
30+
* Test.
31+
*
32+
* @return void
33+
*/
34+
public function testInvalidEncoding(): void
35+
{
36+
$this->expectException(UnexpectedValueException::class);
37+
$this->expectExceptionMessage('JSON encoding failed. Code: 5. Error: Malformed UTF-8 characters, possibly incorrectly encoded.');
38+
39+
$encoder = new JsonEncoder();
40+
$encoder->encode(['key' => "\x00\x81"]);
41+
}
42+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Selective\Validation\Test\Middleware;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Psr\Http\Message\ServerRequestInterface;
7+
use Psr\Http\Server\MiddlewareInterface;
8+
use Psr\Http\Server\RequestHandlerInterface;
9+
use Selective\Validation\ValidationException;
10+
use Selective\Validation\ValidationResult;
11+
12+
/**
13+
* Middleware.
14+
*/
15+
final class ErrorMiddleware implements MiddlewareInterface
16+
{
17+
/**
18+
* Invoke middleware.
19+
*
20+
* @param ServerRequestInterface $request The request
21+
* @param RequestHandlerInterface $handler The handler
22+
*
23+
* @throws ValidationException
24+
*
25+
* @return ResponseInterface The response
26+
*/
27+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
28+
{
29+
$validation = new ValidationResult();
30+
31+
$validation->addError('username', 'Input required');
32+
$validation->setMessage('Please check your input');
33+
34+
throw new ValidationException($validation);
35+
}
36+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Selective\Validation\Test\Middleware;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Psr\Http\Message\ServerRequestInterface;
7+
use Relay\Relay;
8+
use Slim\Psr7\Factory\ServerRequestFactory;
9+
10+
/**
11+
* Test.
12+
*/
13+
trait MiddlewareTestTrait
14+
{
15+
/**
16+
* Run middleware stack.
17+
*
18+
* @param array $queue The queue
19+
*
20+
* @return ResponseInterface The response
21+
*/
22+
protected function runQueue(array $queue): ResponseInterface
23+
{
24+
$queue[] = new ResponseFactoryMiddleware();
25+
26+
$request = $this->createRequest();
27+
$relay = new Relay($queue);
28+
29+
return $relay->handle($request);
30+
}
31+
32+
/**
33+
* Factory.
34+
*
35+
* @return ServerRequestInterface
36+
*/
37+
protected function createRequest(): ServerRequestInterface
38+
{
39+
return (new ServerRequestFactory())->createServerRequest('GET', '/');
40+
}
41+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Selective\Validation\Test\Middleware;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Psr\Http\Message\ServerRequestInterface;
7+
use Psr\Http\Server\MiddlewareInterface;
8+
use Psr\Http\Server\RequestHandlerInterface;
9+
use Slim\Psr7\Factory\ResponseFactory;
10+
11+
/**
12+
* Middleware.
13+
*/
14+
final class ResponseFactoryMiddleware implements MiddlewareInterface
15+
{
16+
/**
17+
* Invoke middleware.
18+
*
19+
* @param ServerRequestInterface $request The request
20+
* @param RequestHandlerInterface $handler The handler
21+
*
22+
* @return ResponseInterface The response
23+
*/
24+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
25+
{
26+
return (new ResponseFactory())->createResponse();
27+
}
28+
}

0 commit comments

Comments
 (0)