Passed
Pull Request — master (#68)
by Dmitriy
14:22
created

MiddlewareFactory.php$1 ➔ __construct()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
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
final class MiddlewareFactory implements MiddlewareFactoryInterface
16
{
17
    private ContainerInterface $container;
18
19
    public function __construct(ContainerInterface $container)
20
    {
21
        $this->container = $container;
22
    }
23
24
    public function create($middlewareDefinition): MiddlewareInterface
25
    {
26
        return $this->createMiddleware($middlewareDefinition);
27
    }
28
29
    /**
30
     * @param callable|string|array $middleware
31
     * @return MiddlewareInterface
32
     */
33
    private function createMiddleware($middlewareDefinition): MiddlewareInterface
34
    {
35
        $this->validateMiddleware($middlewareDefinition);
36
37
        if (is_string($middlewareDefinition)) {
38
            if ($this->container === null) {
39
                throw new InvalidArgumentException('Route container must not be null for lazy loaded middleware.');
40
            }
41
            return $this->container->get($middlewareDefinition);
42
        }
43
44
        if (is_array($middlewareDefinition) && !is_object($middlewareDefinition[0])) {
45
            if ($this->container === null) {
46
                throw new InvalidArgumentException('Route container must not be null for handler action.');
47
            }
48
            return $this->wrapCallable($middlewareDefinition);
49
        }
50
51
        if ($this->isCallable($middlewareDefinition)) {
52
            if ($this->container === null) {
53
                throw new InvalidArgumentException('Route container must not be null for callable.');
54
            }
55
            return $this->wrapCallable($middlewareDefinition);
56
        }
57
58
        throw new \RuntimeException('Middleware creating error.');
59
    }
60
61
    private function wrapCallable($callback): MiddlewareInterface
62
    {
63
        if (is_array($callback) && !is_object($callback[0])) {
64
            [$controller, $action] = $callback;
65
            return new class($controller, $action, $this->container) implements MiddlewareInterface {
66
                private string $class;
67
                private string $method;
68
                private ContainerInterface $container;
69
70
                public function __construct(string $class, string $method, ContainerInterface $container)
71
                {
72
                    $this->class = $class;
73
                    $this->method = $method;
74
                    $this->container = $container;
75
                }
76
77
                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
78
                {
79
                    $controller = $this->container->get($this->class);
80
                    return (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
81
                }
82
            };
83
        }
84
85
        return new class($callback, $this->container) implements MiddlewareInterface {
86
            private ContainerInterface $container;
87
            private $callback;
88
89
            public function __construct(callable $callback, ContainerInterface $container)
90
            {
91
                $this->callback = $callback;
92
                $this->container = $container;
93
            }
94
95
            public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
96
            {
97
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
98
                return $response instanceof MiddlewareInterface ? $response->process($request, $handler) : $response;
99
            }
100
        };
101
    }
102
103
    /**
104
     * @param callable|string|array $middlewareDefinition
105
     */
106
    private function validateMiddleware($middlewareDefinition): void
107
    {
108
        if (
109
            is_string($middlewareDefinition) && is_subclass_of($middlewareDefinition, MiddlewareInterface::class)
110
        ) {
111
            return;
112
        }
113
114
        if ($this->isCallable($middlewareDefinition) && (!is_array($middlewareDefinition) || !is_object($middlewareDefinition[0]))) {
115
            return;
116
        }
117
118
        throw new InvalidArgumentException('Parameter should be either PSR middleware class name or a callable.');
119
    }
120
121
    private function isCallable($definition): bool
122
    {
123
        if (is_callable($definition)) {
124
            return true;
125
        }
126
127
        return is_array($definition) && array_keys($definition) === [0, 1] && in_array($definition[1], get_class_methods($definition[0]) ?? [], true);
128
    }
129
}
130