Skip to content

Commit 5375ef9

Browse files
authored
Remove wrapper factory, move parameters resolver from request-model (#68)
1 parent 0cccd5d commit 5375ef9

12 files changed

+238
-172
lines changed

CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Yii Middleware Dispatcher Change Log
22

3-
## 4.0.1 under development
3+
## 5.0.0 under development
44

5-
- no changes in this release.
5+
- Chg #68: Remove wrapper factory (@rustamwin)
6+
- New #68: Add `ParametersResolverInterface` to resolve parameters of middleware that are provided as callable (@rustamwin)
67

78
## 4.0.0 November 10, 2022
89

README.md

+13-15
Original file line numberDiff line numberDiff line change
@@ -96,28 +96,26 @@ executed first. For each middleware
9696
`\Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware` and `\Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware`
9797
events are triggered.
9898

99-
### Customizing callable wrapper
99+
### Creating your own implementation of parameters resolver
100100

101-
Callable wrapper could be customized by providing your own `WrapperFactoryInterface` implementation:
101+
Parameters resolver could be customized by providing your own `ParametersResolverInterface` implementation:
102102

103103
```php
104-
use \Yiisoft\Middleware\Dispatcher\WrapperFactoryInterface;
104+
use \Psr\Http\Message\ServerRequestInterface;
105+
use \Yiisoft\Middleware\Dispatcher\ParametersResolverInterface;
105106

106-
class CoolWrapperFactory implements WrapperFactoryInterface
107+
class CoolParametersResolver implements ParametersResolverInterface
107108
{
108-
private WrapperFactoryInterface $wrapperFactory;
109-
110-
public function __construct(WrapperFactoryInterface $wrapperFactory) {
111-
$this->wrapperFactory = $wrapperFactory;
112-
}
113-
114-
public function create($callable): MiddlewareInterface
109+
public function resolve(array $parameters, ServerRequestInterface $request): MiddlewareInterface
115110
{
116-
if (is_array($callable)) {
117-
return createMiddleware($callable);
111+
$resolvedParameters = [];
112+
foreach ($parameters as $parameter) {
113+
if ($request->getAttribute($parameter->getName()) !== null) {
114+
$resolvedParameters[$parameter->getName()] = $request->getAttribute($parameter->getName())
115+
}
118116
}
119117

120-
$this->wrapperFactory->create($callable);
118+
return $resolvedParameters;
121119
}
122120
}
123121
```
@@ -129,7 +127,7 @@ use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
129127
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
130128

131129
$dispatcher = new MiddlewareDispatcher(
132-
new MiddlewareFactory($diContainer, new CoolWrapperFactory($wrapperFactory)),
130+
new MiddlewareFactory($diContainer, new CoolParametersResolver()),
133131
$eventDispatcher
134132
);
135133
```

composer.json

+1-9
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"rector/rector": "^0.14.3",
3535
"roave/infection-static-analysis-plugin": "^1.18",
3636
"spatie/phpunit-watcher": "^1.23",
37-
"vimeo/psalm": "^4.30|^5.2",
37+
"vimeo/psalm": "^4.30|^5.3",
3838
"yiisoft/test-support": "^1.3"
3939
},
4040
"autoload": {
@@ -47,14 +47,6 @@
4747
"Yiisoft\\Middleware\\Dispatcher\\Tests\\": "tests"
4848
}
4949
},
50-
"extra": {
51-
"config-plugin-options": {
52-
"source-directory": "config"
53-
},
54-
"config-plugin": {
55-
"web": "web.php"
56-
}
57-
},
5850
"config": {
5951
"sort-packages": true,
6052
"allow-plugins": {

config/web.php

-10
This file was deleted.

src/MiddlewareFactory.php

+130-10
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66

77
use Closure;
88
use Psr\Container\ContainerInterface;
9+
use Psr\Http\Message\ResponseInterface;
910
use Psr\Http\Message\ServerRequestInterface;
1011
use Psr\Http\Server\MiddlewareInterface;
1112
use Psr\Http\Server\RequestHandlerInterface;
13+
use ReflectionClass;
14+
use ReflectionFunction;
1215
use Yiisoft\Definitions\ArrayDefinition;
1316
use Yiisoft\Definitions\Exception\InvalidConfigException;
1417
use Yiisoft\Definitions\Helpers\DefinitionValidator;
18+
use Yiisoft\Injector\Injector;
1519

1620
use function in_array;
1721
use function is_array;
@@ -29,7 +33,7 @@ final class MiddlewareFactory
2933
*/
3034
public function __construct(
3135
private ContainerInterface $container,
32-
private WrapperFactoryInterface $wrapperFactory
36+
private ?ParametersResolverInterface $parametersResolver = null
3337
) {
3438
}
3539

@@ -59,15 +63,13 @@ public function create(array|callable|string $middlewareDefinition): MiddlewareI
5963
}
6064

6165
if ($this->isCallableDefinition($middlewareDefinition)) {
62-
/** @var array{0:class-string, 1:string}|Closure $middlewareDefinition */
63-
return $this->wrapperFactory->create($middlewareDefinition);
66+
return $this->wrapCallable($middlewareDefinition);
6467
}
6568

6669
if ($this->isArrayDefinition($middlewareDefinition)) {
6770
/**
68-
* @psalm-var ArrayDefinitionConfig $middlewareDefinition
69-
*
7071
* @var MiddlewareInterface
72+
* @psalm-suppress InvalidArgument Need for Psalm version 4.* only.
7173
*/
7274
return ArrayDefinition::fromConfig($middlewareDefinition)->resolve($this->container);
7375
}
@@ -78,16 +80,16 @@ public function create(array|callable|string $middlewareDefinition): MiddlewareI
7880
/**
7981
* @psalm-assert-if-true class-string<MiddlewareInterface> $definition
8082
*/
81-
private function isMiddlewareClassDefinition(mixed $definition): bool
83+
private function isMiddlewareClassDefinition(array|callable|string $definition): bool
8284
{
8385
return is_string($definition)
8486
&& is_subclass_of($definition, MiddlewareInterface::class);
8587
}
8688

8789
/**
88-
* @psalm-assert-if-true array|Closure $definition
90+
* @psalm-assert-if-true array{0:class-string, 1:non-empty-string}|Closure $definition
8991
*/
90-
private function isCallableDefinition(mixed $definition): bool
92+
private function isCallableDefinition(array|callable|string $definition): bool
9193
{
9294
if ($definition instanceof Closure) {
9395
return true;
@@ -107,7 +109,7 @@ class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
107109
/**
108110
* @psalm-assert-if-true ArrayDefinitionConfig $definition
109111
*/
110-
private function isArrayDefinition(mixed $definition): bool
112+
private function isArrayDefinition(array|callable|string $definition): bool
111113
{
112114
if (!is_array($definition)) {
113115
return false;
@@ -119,6 +121,124 @@ private function isArrayDefinition(mixed $definition): bool
119121
return false;
120122
}
121123

122-
return is_subclass_of((string) ($definition['class'] ?? ''), MiddlewareInterface::class);
124+
return is_subclass_of((string)($definition['class'] ?? ''), MiddlewareInterface::class);
125+
}
126+
127+
/**
128+
* @param array{0:class-string, 1:non-empty-string}|Closure $callable
129+
*/
130+
private function wrapCallable(array|Closure $callable): MiddlewareInterface
131+
{
132+
if (is_array($callable)) {
133+
return $this->createActionWrapper($callable[0], $callable[1]);
134+
}
135+
136+
return $this->createCallableWrapper($callable);
137+
}
138+
139+
private function createCallableWrapper(callable $callback): MiddlewareInterface
140+
{
141+
return new class ($callback, $this->container, $this->parametersResolver) implements MiddlewareInterface {
142+
private $callback;
143+
144+
public function __construct(
145+
callable $callback,
146+
private ContainerInterface $container,
147+
private ?ParametersResolverInterface $parametersResolver
148+
) {
149+
$this->callback = $callback;
150+
}
151+
152+
public function process(
153+
ServerRequestInterface $request,
154+
RequestHandlerInterface $handler
155+
): ResponseInterface {
156+
$parameters = [$request, $handler];
157+
if ($this->parametersResolver !== null) {
158+
$parameters = array_merge(
159+
$parameters,
160+
$this->parametersResolver->resolve($this->getCallableParameters(), $request)
161+
);
162+
}
163+
/** @var MiddlewareInterface|mixed|ResponseInterface $response */
164+
$response = (new Injector($this->container))->invoke($this->callback, $parameters);
165+
if ($response instanceof ResponseInterface) {
166+
return $response;
167+
}
168+
if ($response instanceof MiddlewareInterface) {
169+
return $response->process($request, $handler);
170+
}
171+
throw new InvalidMiddlewareDefinitionException($this->callback);
172+
}
173+
174+
/**
175+
* @return \ReflectionParameter[]
176+
*/
177+
private function getCallableParameters(): array
178+
{
179+
$callback = Closure::fromCallable($this->callback);
180+
181+
return (new ReflectionFunction($callback))->getParameters();
182+
}
183+
};
184+
}
185+
186+
/**
187+
* @param class-string $class
188+
* @param non-empty-string $method
189+
*/
190+
private function createActionWrapper(string $class, string $method): MiddlewareInterface
191+
{
192+
return new class ($this->container, $this->parametersResolver, $class, $method) implements MiddlewareInterface {
193+
public function __construct(
194+
private ContainerInterface $container,
195+
private ?ParametersResolverInterface $parametersResolver,
196+
/** @var class-string */
197+
private string $class,
198+
/** @var non-empty-string */
199+
private string $method
200+
) {
201+
}
202+
203+
public function process(
204+
ServerRequestInterface $request,
205+
RequestHandlerInterface $handler
206+
): ResponseInterface {
207+
/** @var mixed $controller */
208+
$controller = $this->container->get($this->class);
209+
$parameters = [$request, $handler];
210+
if ($this->parametersResolver !== null) {
211+
$parameters = array_merge(
212+
$parameters,
213+
$this->parametersResolver->resolve($this->getActionParameters(), $request)
214+
);
215+
}
216+
217+
/** @var mixed|ResponseInterface $response */
218+
$response = (new Injector($this->container))->invoke([$controller, $this->method], $parameters);
219+
if ($response instanceof ResponseInterface) {
220+
return $response;
221+
}
222+
223+
throw new InvalidMiddlewareDefinitionException([$this->class, $this->method]);
224+
}
225+
226+
/**
227+
* @throws \ReflectionException
228+
*
229+
* @return \ReflectionParameter[]
230+
*/
231+
private function getActionParameters(): array
232+
{
233+
return (new ReflectionClass($this->class))->getMethod($this->method)->getParameters();
234+
}
235+
236+
public function __debugInfo()
237+
{
238+
return [
239+
'callback' => [$this->class, $this->method],
240+
];
241+
}
242+
};
123243
}
124244
}

src/ParametersResolverInterface.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Middleware\Dispatcher;
6+
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use ReflectionParameter;
9+
10+
/**
11+
* Resolves parameters of PSR-15 middleware that are provided as callable.
12+
* You may implement this interface if you want to introduce custom dependencies or inject additional data from
13+
* the {@see ServerRequestInterface} (e.g. using attributes) to the middleware.
14+
*/
15+
interface ParametersResolverInterface
16+
{
17+
/**
18+
* Resolve parameters of a PSR-15 middleware the provided as callable.
19+
*
20+
* @param ReflectionParameter[] $parameters
21+
*
22+
* @return array<array-key, mixed>
23+
*/
24+
public function resolve(array $parameters, ServerRequestInterface $request): array;
25+
}

0 commit comments

Comments
 (0)