Passed
Push — master ( bed3a9...016111 )
by Alexander
02:00
created

MiddlewareFactory.php$0 ➔ __debugInfo()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
cc 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Middleware\Dispatcher;
6
7
use Closure;
8
use Psr\Container\ContainerInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Yiisoft\Injector\Injector;
14
15
use function in_array;
16
use function is_array;
17
use function is_string;
18
19
/**
20
 * Creates a PSR-15 middleware based on the definition provided.
21
 */
22
final class MiddlewareFactory implements MiddlewareFactoryInterface
23
{
24
    private ContainerInterface $container;
25
26
    /**
27
     * @param ContainerInterface $container Container to use for resolving definitions.
28
     */
29 26
    public function __construct(ContainerInterface $container)
30
    {
31 26
        $this->container = $container;
32 26
    }
33
34
    /**
35
     * @param array|callable|string $middlewareDefinition Middleware definition in one of the following formats:
36
     *
37
     * - A name of PSR-15 middleware class. The middleware instance will be obtained from container and executed.
38
     * - A callable with `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface`
39
     *   signature.
40
     * - A controller handler action in format `[TestController::class, 'index']`. `TestController` instance will
41
     *   be created and `index()` method will be executed.
42
     * - A function returning a middleware. The middleware returned will be executed.
43
     *
44
     * For handler action and callable
45
     * typed parameters are automatically injected using dependency injection container.
46
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
47
     * and {@see RequestHandlerInterface}.
48
     */
49 23
    public function create($middlewareDefinition): MiddlewareInterface
50
    {
51 23
        $this->validateMiddleware($middlewareDefinition);
52
53 15
        if (is_string($middlewareDefinition)) {
54
            /** @var MiddlewareInterface */
55 3
            return $this->container->get($middlewareDefinition);
56
        }
57
58 13
        return $this->wrapCallable($middlewareDefinition);
59
    }
60
61
    /**
62
     * @param array|callable $callback
63
     */
64 13
    private function wrapCallable($callback): MiddlewareInterface
65
    {
66 13
        if (is_array($callback)) {
67 5
            return new class ($this->container, $callback) implements MiddlewareInterface {
68
                private string $class;
69
                private string $method;
70
                private ContainerInterface $container;
71
                private array $callback;
72
73
                public function __construct(ContainerInterface $container, array $callback)
74
                {
75 5
                    [$this->class, $this->method] = $callback;
76 5
                    $this->container = $container;
77 5
                    $this->callback = $callback;
78 5
                }
79
80
                public function process(
81
                    ServerRequestInterface $request,
82
                    RequestHandlerInterface $handler
83
                ): ResponseInterface {
84
                    /** @var mixed $controller */
85 5
                    $controller = $this->container->get($this->class);
86
87
                    /** @var mixed $response */
88 5
                    $response = (new Injector($this->container))
89 5
                        ->invoke([$controller, $this->method], [$request, $handler]);
90 5
                    if ($response instanceof ResponseInterface) {
91 4
                        return $response;
92
                    }
93
94 1
                    throw new InvalidMiddlewareDefinitionException($this->callback);
95
                }
96
97
                public function __debugInfo()
98
                {
99
                    return [
100 1
                        'callback' => $this->callback,
101
                    ];
102
                }
103
            };
104
        }
105
106 8
        return new class ($callback, $this->container) implements MiddlewareInterface {
107
            private ContainerInterface $container;
108
            private $callback;
109
110
            public function __construct(callable $callback, ContainerInterface $container)
111
            {
112 8
                $this->callback = $callback;
113 8
                $this->container = $container;
114 8
            }
115
116
            public function process(
117
                ServerRequestInterface $request,
118
                RequestHandlerInterface $handler
119
            ): ResponseInterface {
120
                /** @var mixed $response */
121 8
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
122 8
                if ($response instanceof ResponseInterface) {
123 5
                    return $response;
124
                }
125 3
                if ($response instanceof MiddlewareInterface) {
126 2
                    return $response->process($request, $handler);
127
                }
128 1
                throw new InvalidMiddlewareDefinitionException($this->callback);
129
            }
130
        };
131
    }
132
133
    /**
134
     * @param array|callable|string $middlewareDefinition A name of PSR-15 middleware, a callable with
135
     * `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
136
     * a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
137
     * are automatically injected using dependency injection container passed to the route.
138
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
139
     * and {@see RequestHandlerInterface}.
140
     *
141
     * @throws InvalidMiddlewareDefinitionException
142
     */
143 23
    private function validateMiddleware($middlewareDefinition): void
144
    {
145 23
        if (is_string($middlewareDefinition) && is_subclass_of($middlewareDefinition, MiddlewareInterface::class)) {
146 3
            return;
147
        }
148
149 21
        if ($this->isCallable($middlewareDefinition)) {
150 13
            return;
151
        }
152
153 8
        throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
154
    }
155
156
    /**
157
     * @param mixed $definition
158
     */
159 21
    private function isCallable($definition): bool
160
    {
161 21
        if ($definition instanceof Closure) {
162 8
            return true;
163
        }
164
165 13
        return is_array($definition)
166 13
            && array_keys($definition) === [0, 1]
167 13
            && is_string($definition[0])
168 13
            && is_string($definition[1])
169 6
            && in_array(
170 6
                $definition[1],
171 6
                class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
172 13
                true
173
            );
174
    }
175
}
176