6
6
7
7
use Closure ;
8
8
use Psr \Container \ContainerInterface ;
9
+ use Psr \Http \Message \ResponseInterface ;
9
10
use Psr \Http \Message \ServerRequestInterface ;
10
11
use Psr \Http \Server \MiddlewareInterface ;
11
12
use Psr \Http \Server \RequestHandlerInterface ;
13
+ use ReflectionClass ;
14
+ use ReflectionFunction ;
12
15
use Yiisoft \Definitions \ArrayDefinition ;
13
16
use Yiisoft \Definitions \Exception \InvalidConfigException ;
14
17
use Yiisoft \Definitions \Helpers \DefinitionValidator ;
18
+ use Yiisoft \Injector \Injector ;
15
19
16
20
use function in_array ;
17
21
use function is_array ;
@@ -29,7 +33,7 @@ final class MiddlewareFactory
29
33
*/
30
34
public function __construct (
31
35
private ContainerInterface $ container ,
32
- private WrapperFactoryInterface $ wrapperFactory
36
+ private ? ParametersResolverInterface $ parametersResolver = null
33
37
) {
34
38
}
35
39
@@ -59,15 +63,13 @@ public function create(array|callable|string $middlewareDefinition): MiddlewareI
59
63
}
60
64
61
65
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 );
64
67
}
65
68
66
69
if ($ this ->isArrayDefinition ($ middlewareDefinition )) {
67
70
/**
68
- * @psalm-var ArrayDefinitionConfig $middlewareDefinition
69
- *
70
71
* @var MiddlewareInterface
72
+ * @psalm-suppress InvalidArgument Need for Psalm version 4.* only.
71
73
*/
72
74
return ArrayDefinition::fromConfig ($ middlewareDefinition )->resolve ($ this ->container );
73
75
}
@@ -78,16 +80,16 @@ public function create(array|callable|string $middlewareDefinition): MiddlewareI
78
80
/**
79
81
* @psalm-assert-if-true class-string<MiddlewareInterface> $definition
80
82
*/
81
- private function isMiddlewareClassDefinition (mixed $ definition ): bool
83
+ private function isMiddlewareClassDefinition (array | callable | string $ definition ): bool
82
84
{
83
85
return is_string ($ definition )
84
86
&& is_subclass_of ($ definition , MiddlewareInterface::class);
85
87
}
86
88
87
89
/**
88
- * @psalm-assert-if-true array|Closure $definition
90
+ * @psalm-assert-if-true array{0:class-string, 1:non-empty-string} |Closure $definition
89
91
*/
90
- private function isCallableDefinition (mixed $ definition ): bool
92
+ private function isCallableDefinition (array | callable | string $ definition ): bool
91
93
{
92
94
if ($ definition instanceof Closure) {
93
95
return true ;
@@ -107,7 +109,7 @@ class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
107
109
/**
108
110
* @psalm-assert-if-true ArrayDefinitionConfig $definition
109
111
*/
110
- private function isArrayDefinition (mixed $ definition ): bool
112
+ private function isArrayDefinition (array | callable | string $ definition ): bool
111
113
{
112
114
if (!is_array ($ definition )) {
113
115
return false ;
@@ -119,6 +121,124 @@ private function isArrayDefinition(mixed $definition): bool
119
121
return false ;
120
122
}
121
123
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
+ };
123
243
}
124
244
}
0 commit comments