Skip to content

Commit 402cf9f

Browse files
authored
Improve readme + Tests 100% + MSI 100% + Psalm level 1 + PHP 8.0 static testing
1 parent 1f40d15 commit 402cf9f

21 files changed

+460
-73
lines changed

.github/workflows/static.yml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717

1818
php:
1919
- "7.4"
20+
- "8.0"
2021

2122
steps:
2223
- name: Checkout

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# _____ Change Log
1+
# Yii Middleware Dispatcher Change Log
22

33
## 1.0.0 under development
44

README.md

+29-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<p align="center">
22
<a href="https://github.com/yiisoft" target="_blank">
3-
<img src="https://github.com/yiisoft.png" height="100px">
3+
<img src="https://yiisoft.github.io/docs/images/yii_logo.svg" height="100px">
44
</a>
55
<h1 align="center">Yii Middleware Dispatcher</h1>
66
<br>
@@ -17,54 +17,62 @@
1717

1818
The package is a [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware dispatcher.
1919

20+
## Requirements
21+
22+
- PHP 7.4 or higher.
23+
2024
## Installation
2125

2226
The package could be installed with composer:
2327

24-
```
25-
composer install yiisoft/middleware-dispatcher
28+
```shell
29+
composer require yiisoft/middleware-dispatcher --prefer-dist
2630
```
2731

2832
## General usage
2933

30-
## Unit testing
34+
## Testing
35+
36+
### Unit testing
3137

3238
The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
3339

34-
```php
40+
```shell
3541
./vendor/bin/phpunit
3642
```
3743

38-
## Mutation testing
44+
### Mutation testing
3945

40-
The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it:
46+
The package tests are checked with [Infection](https://infection.github.io/) mutation framework with
47+
[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it:
4148

42-
```php
43-
./vendor/bin/infection
49+
```shell
50+
./vendor/bin/roave-infection-static-analysis-plugin
4451
```
4552

46-
## Static analysis
53+
### Static analysis
4754

4855
The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:
4956

50-
```php
57+
```shell
5158
./vendor/bin/psalm
5259
```
53-
### Support the project
60+
61+
## License
62+
63+
The Yii Middleware Dispatcher is free software. It is released under the terms of the BSD License.
64+
Please see [`LICENSE`](./LICENSE.md) for more information.
65+
66+
Maintained by [Yii Software](https://www.yiiframework.com/).
67+
68+
## Support the project
5469

5570
[![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft)
5671

57-
### Follow updates
72+
## Follow updates
5873

5974
[![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/)
6075
[![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework)
61-
[![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3ru)
76+
[![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en)
6277
[![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk)
6378
[![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack)
64-
65-
## License
66-
67-
The Yii Middleware Dispatcher is free software. It is released under the terms of the BSD License.
68-
Please see [`LICENSE`](./LICENSE.md) for more information.
69-
70-
Maintained by [Yii Software](https://www.yiiframework.com/).

composer.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "yiisoft/middleware-dispatcher",
33
"type": "library",
4-
"description": "Middleware dispatcher",
4+
"description": "PSR-15 middleware dispatcher",
55
"keywords": [
66
"middleware",
77
"dispatcher"
@@ -30,7 +30,8 @@
3030
"phpunit/phpunit": "^9.5",
3131
"roave/infection-static-analysis-plugin": "^1.6",
3232
"spatie/phpunit-watcher": "^1.23",
33-
"vimeo/psalm": "^4.3"
33+
"vimeo/psalm": "^4.3",
34+
"yiisoft/test-support": "^1.1"
3435
},
3536
"autoload": {
3637
"psr-4": {

phpunit.xml.dist

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</php>
1717

1818
<testsuites>
19-
<testsuite name="Yii _____ tests">
19+
<testsuite name="Yii Middleware Dispatcher tests">
2020
<directory>./tests</directory>
2121
</testsuite>
2222
</testsuites>

psalm.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?xml version="1.0"?>
22
<psalm
3-
errorLevel="6"
4-
resolveFromConfigFile="true"
3+
errorLevel="1"
54
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
65
xmlns="https://getpsalm.org/schema/config"
76
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"

src/InvalidMiddlewareDefinitionException.php

+16-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public function __construct($middlewareDefinition)
2727
parent::__construct($message);
2828
}
2929

30+
/**
31+
* @param mixed $middlewareDefinition
32+
*/
3033
private function convertDefinitionToString($middlewareDefinition): ?string
3134
{
3235
if (is_object($middlewareDefinition)) {
@@ -44,12 +47,20 @@ private function convertDefinitionToString($middlewareDefinition): ?string
4447
return null;
4548
}
4649
}
47-
array_walk($items, static function (&$item, $key) {
48-
$item = '"' . $item . '"';
49-
if (is_string($key)) {
50-
$item = '"' . $key . '" => ' . $item;
50+
array_walk(
51+
$items,
52+
/**
53+
* @param mixed $item
54+
* @psalm-param array-key $key
55+
*/
56+
static function (&$item, $key) {
57+
$item = (string)$item;
58+
$item = '"' . $item . '"';
59+
if (is_string($key)) {
60+
$item = '"' . $key . '" => ' . $item;
61+
}
5162
}
52-
});
63+
);
5364
return '[' . implode(', ', $items) . ']';
5465
}
5566

src/MiddlewareDispatcher.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Psr\Http\Message\ResponseInterface;
88
use Psr\Http\Message\ServerRequestInterface;
9+
use Psr\Http\Server\MiddlewareInterface;
910
use Psr\Http\Server\RequestHandlerInterface;
1011

1112
final class MiddlewareDispatcher
@@ -43,10 +44,10 @@ public function dispatch(ServerRequestInterface $request, RequestHandlerInterfac
4344
* Returns new instance with middleware handlers replaced.
4445
* Last specified handler will be executed first.
4546
*
46-
* @param array $middlewareDefinitions Each array element is a name of PSR-15 middleware, a callable with
47-
* `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
48-
* a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
49-
* are automatically injected using dependency injection container passed to the route.
47+
* @param array[]|callable[]|string[] $middlewareDefinitions Each array element is a name of PSR-15 middleware,
48+
* a callable with `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface`
49+
* signature or a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable
50+
* typed parameters are automatically injected using dependency injection container passed to the route.
5051
* Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
5152
* and {@see RequestHandlerInterface}.
5253
*
@@ -66,6 +67,9 @@ public function hasMiddlewares(): bool
6667
return $this->middlewareDefinitions !== [];
6768
}
6869

70+
/**
71+
* @return MiddlewareInterface[]
72+
*/
6973
private function buildMiddlewares(): array
7074
{
7175
$middlewares = [];

src/MiddlewareFactory.php

+46-18
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44

55
namespace Yiisoft\Middleware\Dispatcher;
66

7+
use Closure;
78
use Psr\Container\ContainerInterface;
89
use Psr\Http\Message\ResponseInterface;
910
use Psr\Http\Message\ServerRequestInterface;
1011
use Psr\Http\Server\MiddlewareInterface;
1112
use Psr\Http\Server\RequestHandlerInterface;
1213
use Yiisoft\Injector\Injector;
1314

15+
use function in_array;
16+
use function is_array;
17+
use function is_string;
18+
1419
final class MiddlewareFactory implements MiddlewareFactoryInterface
1520
{
1621
private ContainerInterface $container;
@@ -20,56 +25,69 @@ public function __construct(ContainerInterface $container)
2025
$this->container = $container;
2126
}
2227

23-
public function create($middlewareDefinition): MiddlewareInterface
24-
{
25-
return $this->createMiddleware($middlewareDefinition);
26-
}
27-
2828
/**
2929
* @param array|callable|string $middlewareDefinition A name of PSR-15 middleware, a callable with
3030
* `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
3131
* a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
3232
* are automatically injected using dependency injection container passed to the route.
3333
* Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
3434
* and {@see RequestHandlerInterface}.
35-
*
36-
* @return MiddlewareInterface
3735
*/
38-
private function createMiddleware($middlewareDefinition): MiddlewareInterface
36+
public function create($middlewareDefinition): MiddlewareInterface
3937
{
4038
$this->validateMiddleware($middlewareDefinition);
4139

4240
if (is_string($middlewareDefinition)) {
41+
/** @var MiddlewareInterface */
4342
return $this->container->get($middlewareDefinition);
4443
}
4544

4645
return $this->wrapCallable($middlewareDefinition);
4746
}
4847

48+
/**
49+
* @param array|callable $callback
50+
*/
4951
private function wrapCallable($callback): MiddlewareInterface
5052
{
51-
if (is_array($callback) && !is_object($callback[0])) {
53+
if (is_array($callback)) {
54+
/**
55+
* @var string $controller
56+
* @var string $action
57+
*/
5258
[$controller, $action] = $callback;
53-
return new class($controller, $action, $this->container) implements MiddlewareInterface {
59+
return new class($controller, $action, $this->container, $callback) implements MiddlewareInterface {
5460
private string $class;
5561
private string $method;
5662
private ContainerInterface $container;
63+
private array $callback;
5764

58-
public function __construct(string $class, string $method, ContainerInterface $container)
65+
public function __construct(string $class, string $method, ContainerInterface $container, array $callback)
5966
{
6067
$this->class = $class;
6168
$this->method = $method;
6269
$this->container = $container;
70+
$this->callback = $callback;
6371
}
6472

6573
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
6674
{
75+
/** @var mixed $controller */
6776
$controller = $this->container->get($this->class);
68-
return (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
77+
78+
/** @var mixed $response */
79+
$response = (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
80+
if ($response instanceof ResponseInterface) {
81+
return $response;
82+
}
83+
84+
throw new InvalidMiddlewareDefinitionException($this->callback);
6985
}
7086
};
7187
}
7288

89+
/** @var callable $callback */
90+
7391
return new class($callback, $this->container) implements MiddlewareInterface {
7492
private ContainerInterface $container;
7593
private $callback;
@@ -82,8 +100,15 @@ public function __construct(callable $callback, ContainerInterface $container)
82100

83101
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
84102
{
103+
/** @var mixed $response */
85104
$response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
86-
return $response instanceof MiddlewareInterface ? $response->process($request, $handler) : $response;
105+
if ($response instanceof ResponseInterface) {
106+
return $response;
107+
}
108+
if ($response instanceof MiddlewareInterface) {
109+
return $response->process($request, $handler);
110+
}
111+
throw new InvalidMiddlewareDefinitionException($this->callback);
87112
}
88113
};
89114
}
@@ -104,23 +129,26 @@ private function validateMiddleware($middlewareDefinition): void
104129
return;
105130
}
106131

107-
if ($this->isCallable($middlewareDefinition) && (!is_array($middlewareDefinition) || !is_object($middlewareDefinition[0]))) {
132+
if ($this->isCallable($middlewareDefinition)) {
108133
return;
109134
}
110135

111136
throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
112137
}
113138

139+
/**
140+
* @param mixed $definition
141+
*/
114142
private function isCallable($definition): bool
115143
{
116-
if (is_callable($definition)) {
117-
return is_object($definition)
118-
? !$definition instanceof MiddlewareInterface
119-
: true;
144+
if ($definition instanceof Closure) {
145+
return true;
120146
}
121147

122148
return is_array($definition)
123149
&& array_keys($definition) === [0, 1]
150+
&& is_string($definition[0])
151+
&& is_string($definition[1])
124152
&& in_array(
125153
$definition[1],
126154
class_exists($definition[0]) ? get_class_methods($definition[0]) : [],

src/MiddlewareStack.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Psr\Http\Message\ServerRequestInterface;
1010
use Psr\Http\Server\MiddlewareInterface;
1111
use Psr\Http\Server\RequestHandlerInterface;
12+
use RuntimeException;
1213
use Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware;
1314
use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware;
1415

@@ -45,9 +46,10 @@ public function build(array $middlewares, RequestHandlerInterface $fallbackHandl
4546
public function handle(ServerRequestInterface $request): ResponseInterface
4647
{
4748
if ($this->isEmpty()) {
48-
throw new \RuntimeException('Stack is empty.');
49+
throw new RuntimeException('Stack is empty.');
4950
}
5051

52+
/** @psalm-suppress PossiblyNullReference */
5153
return $this->stack->handle($request);
5254
}
5355

0 commit comments

Comments
 (0)