Passed
Pull Request — master (#68)
by Alexander
22:08 queued 07:09
created

MiddlewareFactory::isCallable()

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 12
c 1
b 0
f 0
eloc 8
nc 4
nop 1
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 $middlewareDefinition
31
     * @return MiddlewareInterface
32
     */
33
    private function createMiddleware($middlewareDefinition): MiddlewareInterface
34
    {
35
        $this->validateMiddleware($middlewareDefinition);
36
37
        if (is_string($middlewareDefinition)) {
38
            return $this->container->get($middlewareDefinition);
39
        }
40
41
        if (is_array($middlewareDefinition) && !is_object($middlewareDefinition[0])) {
42
            return $this->wrapCallable($middlewareDefinition);
43
        }
44
45
        if ($this->isCallable($middlewareDefinition)) {
46
            return $this->wrapCallable($middlewareDefinition);
47
        }
48
49
        throw new \RuntimeException('Middleware creating error.');
50
    }
51
52
    private function wrapCallable($callback): MiddlewareInterface
53
    {
54
        if (is_array($callback) && !is_object($callback[0])) {
55
            [$controller, $action] = $callback;
56
            return new class($controller, $action, $this->container) implements MiddlewareInterface {
57
                private string $class;
58
                private string $method;
59
                private ContainerInterface $container;
60
61
                public function __construct(string $class, string $method, ContainerInterface $container)
62
                {
63
                    $this->class = $class;
64
                    $this->method = $method;
65
                    $this->container = $container;
66
                }
67
68
                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
69
                {
70
                    $controller = $this->container->get($this->class);
71
                    return (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
72
                }
73
            };
74
        }
75
76
        return new class($callback, $this->container) implements MiddlewareInterface {
77
            private ContainerInterface $container;
78
            private $callback;
79
80
            public function __construct(callable $callback, ContainerInterface $container)
81
            {
82
                $this->callback = $callback;
83
                $this->container = $container;
84
            }
85
86
            public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
87
            {
88
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
89
                return $response instanceof MiddlewareInterface ? $response->process($request, $handler) : $response;
90
            }
91
        };
92
    }
93
94
    /**
95
     * @param callable|string|array $middlewareDefinition
96
     */
97
    private function validateMiddleware($middlewareDefinition): void
98
    {
99
        if (
100
            is_string($middlewareDefinition) && is_subclass_of($middlewareDefinition, MiddlewareInterface::class)
101
        ) {
102
            return;
103
        }
104
105
        if ($this->isCallable($middlewareDefinition) && (!is_array($middlewareDefinition) || !is_object($middlewareDefinition[0]))) {
106
            return;
107
        }
108
109
        throw new InvalidArgumentException('Parameter should be either PSR middleware class name or a callable.');
110
    }
111
112
    private function isCallable($definition): bool
113
    {
114
        if (is_callable($definition)) {
115
            return true;
116
        }
117
118
        return is_array($definition)
119
            && array_keys($definition) === [0, 1]
120
            && in_array(
121
                $definition[1],
122
                class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
123
                true
124
            );
125
    }
126
}
127