Passed
Push — master ( 1f40d1...402cf9 )
by Alexander
02:17
created

MiddlewareFactory::validateMiddleware()

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
nc 3
nop 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
final class MiddlewareFactory implements MiddlewareFactoryInterface
20
{
21
    private ContainerInterface $container;
22
23 26
    public function __construct(ContainerInterface $container)
24
    {
25 26
        $this->container = $container;
26 26
    }
27
28
    /**
29
     * @param array|callable|string $middlewareDefinition A name of PSR-15 middleware, a callable with
30
     * `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
31
     * a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
32
     * are automatically injected using dependency injection container passed to the route.
33
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
34
     * and {@see RequestHandlerInterface}.
35
     */
36 23
    public function create($middlewareDefinition): MiddlewareInterface
37
    {
38 23
        $this->validateMiddleware($middlewareDefinition);
39
40 16
        if (is_string($middlewareDefinition)) {
41
            /** @var MiddlewareInterface */
42 3
            return $this->container->get($middlewareDefinition);
43
        }
44
45 14
        return $this->wrapCallable($middlewareDefinition);
46
    }
47
48
    /**
49
     * @param array|callable $callback
50
     */
51 14
    private function wrapCallable($callback): MiddlewareInterface
52
    {
53 14
        if (is_array($callback)) {
54
            /**
55
             * @var string $controller
56
             * @var string $action
57
             */
58 6
            [$controller, $action] = $callback;
59 6
            return new class($controller, $action, $this->container, $callback) implements MiddlewareInterface {
60
                private string $class;
61
                private string $method;
62
                private ContainerInterface $container;
63
                private array $callback;
64
65
                public function __construct(string $class, string $method, ContainerInterface $container, array $callback)
66
                {
67 6
                    $this->class = $class;
68 6
                    $this->method = $method;
69 6
                    $this->container = $container;
70 6
                    $this->callback = $callback;
71 6
                }
72
73
                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
74
                {
75
                    /** @var mixed $controller */
76 6
                    $controller = $this->container->get($this->class);
77
78
                    /** @var mixed $response */
79 6
                    $response = (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
80 6
                    if ($response instanceof ResponseInterface) {
81 5
                        return $response;
82
                    }
83
84 1
                    throw new InvalidMiddlewareDefinitionException($this->callback);
85
                }
86
            };
87
        }
88
89
        /** @var callable $callback */
90
91 8
        return new class($callback, $this->container) implements MiddlewareInterface {
92
            private ContainerInterface $container;
93
            private $callback;
94
95
            public function __construct(callable $callback, ContainerInterface $container)
96
            {
97 8
                $this->callback = $callback;
98 8
                $this->container = $container;
99 8
            }
100
101
            public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
102
            {
103
                /** @var mixed $response */
104 8
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
105 8
                if ($response instanceof ResponseInterface) {
106 6
                    return $response;
107
                }
108 2
                if ($response instanceof MiddlewareInterface) {
109 1
                    return $response->process($request, $handler);
110
                }
111 1
                throw new InvalidMiddlewareDefinitionException($this->callback);
112
            }
113
        };
114
    }
115
116
    /**
117
     * @param array|callable|string $middlewareDefinition A name of PSR-15 middleware, a callable with
118
     * `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
119
     * a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
120
     * are automatically injected using dependency injection container passed to the route.
121
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
122
     * and {@see RequestHandlerInterface}.
123
     *
124
     * @throws InvalidMiddlewareDefinitionException
125
     */
126 23
    private function validateMiddleware($middlewareDefinition): void
127
    {
128 23
        if (is_string($middlewareDefinition) && is_subclass_of($middlewareDefinition, MiddlewareInterface::class)) {
129 3
            return;
130
        }
131
132 21
        if ($this->isCallable($middlewareDefinition)) {
133 14
            return;
134
        }
135
136 7
        throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
137
    }
138
139
    /**
140
     * @param mixed $definition
141
     */
142 21
    private function isCallable($definition): bool
143
    {
144 21
        if ($definition instanceof Closure) {
145 8
            return true;
146
        }
147
148 13
        return is_array($definition)
149 13
            && array_keys($definition) === [0, 1]
150 13
            && is_string($definition[0])
151 13
            && is_string($definition[1])
152 7
            && in_array(
153 7
                $definition[1],
154 7
                class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
155 13
                true
156
            );
157
    }
158
}
159