Passed
Pull Request — master (#15)
by Dmitriy
02:08
created

MiddlewareFactory.php$1 ➔ isCallable()   A

Complexity

Conditions 6

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
ccs 11
cts 11
cp 1
rs 9.2222
cc 6
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Middleware\Dispatcher;
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
use Yiisoft\Middleware\Dispatcher\ActionParametersCollector\ActionParametersCollectorInterface;
15
16
final class MiddlewareFactory implements MiddlewareFactoryInterface
17
{
18
    private ContainerInterface $container;
19
    private ActionParametersCollectorInterface $actionParametersCollector;
20
21 16
    public function __construct(ContainerInterface $container, ActionParametersCollectorInterface $actionParametersCollector)
22
    {
23 16
        $this->container = $container;
24 16
        $this->actionParametersCollector = $actionParametersCollector;
25 16
    }
26
27 16
    public function create($middlewareDefinition): MiddlewareInterface
28
    {
29 16
        return $this->createMiddleware($middlewareDefinition);
30
    }
31
32
    /**
33
     * @param array|callable|string $middlewareDefinition A name of PSR-15 middleware, a callable with
34
     * `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
35
     * a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
36
     * are automatically injected using dependency injection container passed to the route.
37
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
38
     * and {@see RequestHandlerInterface}.
39
     *
40
     * @return MiddlewareInterface
41
     */
42 16
    private function createMiddleware($middlewareDefinition): MiddlewareInterface
43
    {
44 16
        $this->validateMiddleware($middlewareDefinition);
45
46 10
        if (is_string($middlewareDefinition)) {
47 2
            return $this->container->get($middlewareDefinition);
48
        }
49
50 9
        return $this->wrapCallable($middlewareDefinition);
51
    }
52
53 9
    private function wrapCallable($callback): MiddlewareInterface
54
    {
55 9
        if (is_array($callback) && !is_object($callback[0])) {
56 3
            [$controller, $action] = $callback;
57 3
            return new class($controller, $action, $this->container, $this->actionParametersCollector) implements MiddlewareInterface {
58
                private string $class;
59
                private string $method;
60
                private ContainerInterface $container;
61
                private ActionParametersCollectorInterface $actionParametersCollector;
62
63
                public function __construct(string $class, string $method, ContainerInterface $container, ActionParametersCollectorInterface $actionParametersCollector)
64
                {
65 3
                    $this->class = $class;
66 3
                    $this->method = $method;
67 3
                    $this->container = $container;
68 3
                    $this->actionParametersCollector = $actionParametersCollector;
69 3
                }
70
71
                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
72
                {
73 2
                    $controller = $this->container->get($this->class);
74 2
                    $actionParameters = array_merge([$request, $handler], $this->actionParametersCollector->getParameters());
75 2
                    return (new Injector($this->container))->invoke([$controller, $this->method], $actionParameters);
76
                }
77
            };
78
        }
79
80 6
        return new class($callback, $this->container, $this->actionParametersCollector) implements MiddlewareInterface {
81
            private ContainerInterface $container;
82
            private $callback;
83
            private ActionParametersCollectorInterface $actionParametersCollector;
84
85
            public function __construct(callable $callback, ContainerInterface $container, ActionParametersCollectorInterface $actionParametersCollector)
86
            {
87 6
                $this->callback = $callback;
88 6
                $this->container = $container;
89 6
                $this->actionParametersCollector = $actionParametersCollector;
90 6
            }
91
92
            public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
93
            {
94 6
                $actionParameters = array_merge([$request, $handler], $this->actionParametersCollector->getParameters());
95 6
                $response = (new Injector($this->container))->invoke($this->callback, $actionParameters);
96 6
                return $response instanceof MiddlewareInterface ? $response->process($request, $handler) : $response;
97
            }
98
        };
99
    }
100
101
    /**
102
     * @param array|callable|string $middlewareDefinition A name of PSR-15 middleware, a callable with
103
     * `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` signature or
104
     * a handler action (an array of [handlerClass, handlerMethod]). For handler action and callable typed parameters
105
     * are automatically injected using dependency injection container passed to the route.
106
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
107
     * and {@see RequestHandlerInterface}.
108
     */
109 16
    private function validateMiddleware($middlewareDefinition): void
110
    {
111 16
        if (is_string($middlewareDefinition) && is_subclass_of($middlewareDefinition, MiddlewareInterface::class)) {
112 2
            return;
113
        }
114
115 15
        if ($this->isCallable($middlewareDefinition) && (!is_array($middlewareDefinition) || !is_object($middlewareDefinition[0]))) {
116 9
            return;
117
        }
118
119 6
        throw new InvalidArgumentException('Parameter should be either PSR middleware class name or a callable.');
120
    }
121
122 15
    private function isCallable($definition): bool
123
    {
124 15
        if (is_callable($definition)) {
125 9
            return is_object($definition)
126 6
                ? !$definition instanceof MiddlewareInterface
127 9
                : true;
128
        }
129
130 6
        return is_array($definition)
131 6
            && array_keys($definition) === [0, 1]
132 1
            && in_array(
133 1
                $definition[1],
134 1
                class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
135 6
                true
136
            );
137
    }
138
}
139