Passed
Push — master ( 118867...5c417f )
by Sergei
02:29
created

MiddlewareFactory.php$1 ➔ wrapCallableDefinition()   B

Complexity

Conditions 3

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 65
ccs 22
cts 22
cp 1
rs 8.7636
cc 3
crap 3

5 Methods

Rating   Name   Duplication   Size   Complexity  
A MiddlewareFactory.php$1 ➔ __construct() 0 4 1
A MiddlewareFactory.php$1 ➔ process() 0 13 3
A MiddlewareFactory.php$0 ➔ __debugInfo() 0 4 1
A MiddlewareFactory.php$0 ➔ process() 0 15 2
A MiddlewareFactory.php$0 ➔ __construct() 0 5 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Definitions\ArrayDefinition;
14
use Yiisoft\Definitions\Exception\InvalidConfigException;
15
use Yiisoft\Definitions\Helpers\DefinitionValidator;
16
use Yiisoft\Injector\Injector;
17
18
use function in_array;
19
use function is_array;
20
use function is_string;
21
22
/**
23
 * Creates a PSR-15 middleware based on the definition provided.
24
 *
25
 * @psalm-import-type ArrayDefinitionConfig from ArrayDefinition
26
 */
27
final class MiddlewareFactory implements MiddlewareFactoryInterface
28
{
29
    private ContainerInterface $container;
30
31
    /**
32
     * @param ContainerInterface $container Container to use for resolving definitions.
33
     */
34 27
    public function __construct(ContainerInterface $container)
35
    {
36 27
        $this->container = $container;
37
    }
38
39
    /**
40
     * @param array|callable|string $middlewareDefinition Middleware definition in one of the following formats:
41
     *
42
     * - A name of PSR-15 middleware class. The middleware instance will be obtained from container and executed.
43
     * - A callable with
44
     *   `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface`
45
     *   signature.
46
     * - A controller handler action in format `[TestController::class, 'index']`. `TestController` instance will
47
     *   be created and `index()` method will be executed.
48
     * - A function returning a middleware. The middleware returned will be executed.
49
     *
50
     * For handler action and callable
51
     * typed parameters are automatically injected using dependency injection container.
52
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
53
     * and {@see RequestHandlerInterface}.
54
     *
55
     * @throws InvalidMiddlewareDefinitionException
56
     */
57 24
    public function create($middlewareDefinition): MiddlewareInterface
58
    {
59 24
        if ($this->isMiddlewareClassDefinition($middlewareDefinition)) {
60
            /** @var MiddlewareInterface */
61 3
            return $this->container->get($middlewareDefinition);
0 ignored issues
show
Bug introduced by
It seems like $middlewareDefinition can also be of type array and callable; however, parameter $id of Psr\Container\ContainerInterface::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

61
            return $this->container->get(/** @scrutinizer ignore-type */ $middlewareDefinition);
Loading history...
62
        }
63
64 22
        if ($this->isCallableDefinition($middlewareDefinition)) {
65 13
            return $this->wrapCallableDefinition($middlewareDefinition);
0 ignored issues
show
Bug introduced by
It seems like $middlewareDefinition can also be of type string; however, parameter $callback of Yiisoft\Middleware\Dispa...rapCallableDefinition() does only seem to accept Closure|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

65
            return $this->wrapCallableDefinition(/** @scrutinizer ignore-type */ $middlewareDefinition);
Loading history...
66
        }
67
68 9
        if ($this->isArrayDefinition($middlewareDefinition)) {
69
            /**
70
             * @psalm-var ArrayDefinitionConfig $middlewareDefinition
71
             *
72
             * @var MiddlewareInterface
73
             */
74 1
            return ArrayDefinition::fromConfig($middlewareDefinition)->resolve($this->container);
0 ignored issues
show
Bug introduced by
It seems like $middlewareDefinition can also be of type callable and string; however, parameter $config of Yiisoft\Definitions\ArrayDefinition::fromConfig() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

74
            return ArrayDefinition::fromConfig(/** @scrutinizer ignore-type */ $middlewareDefinition)->resolve($this->container);
Loading history...
75
        }
76
77 8
        throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
78
    }
79
80
    /**
81
     * @param array|Closure $callback
82
     */
83 13
    private function wrapCallableDefinition($callback): MiddlewareInterface
84
    {
85 13
        if (is_array($callback)) {
86 5
            return new class ($this->container, $callback) implements MiddlewareInterface {
87
                private string $class;
88
                private string $method;
89
                private ContainerInterface $container;
90
                private array $callback;
91
92
                public function __construct(ContainerInterface $container, array $callback)
93
                {
94 5
                    [$this->class, $this->method] = $callback;
95 5
                    $this->container = $container;
96 5
                    $this->callback = $callback;
97
                }
98
99
                public function process(
100
                    ServerRequestInterface $request,
101
                    RequestHandlerInterface $handler
102
                ): ResponseInterface {
103
                    /** @var mixed $controller */
104 5
                    $controller = $this->container->get($this->class);
105
106
                    /** @var mixed $response */
107 5
                    $response = (new Injector($this->container))
108 5
                        ->invoke([$controller, $this->method], [$request, $handler]);
109 5
                    if ($response instanceof ResponseInterface) {
110 4
                        return $response;
111
                    }
112
113 1
                    throw new InvalidMiddlewareDefinitionException($this->callback);
114
                }
115
116
                public function __debugInfo()
117
                {
118
                    return [
119 1
                        'callback' => $this->callback,
120
                    ];
121
                }
122
            };
123
        }
124
125 8
        return new class ($callback, $this->container) implements MiddlewareInterface {
126
            private ContainerInterface $container;
127
            private $callback;
128
129
            public function __construct(callable $callback, ContainerInterface $container)
130
            {
131 8
                $this->callback = $callback;
132 8
                $this->container = $container;
133
            }
134
135
            public function process(
136
                ServerRequestInterface $request,
137
                RequestHandlerInterface $handler
138
            ): ResponseInterface {
139
                /** @var mixed $response */
140 8
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
141 8
                if ($response instanceof ResponseInterface) {
142 5
                    return $response;
143
                }
144 3
                if ($response instanceof MiddlewareInterface) {
145 2
                    return $response->process($request, $handler);
146
                }
147 1
                throw new InvalidMiddlewareDefinitionException($this->callback);
148
            }
149
        };
150
    }
151
152
    /**
153
     * @param mixed $definition
154
     *
155
     * @psalm-assert-if-true class-string<MiddlewareInterface> $definition
156
     */
157 24
    private function isMiddlewareClassDefinition($definition): bool
158
    {
159 24
        return is_string($definition)
160 24
            && is_subclass_of($definition, MiddlewareInterface::class);
161
    }
162
163
    /**
164
     * @param mixed $definition
165
     *
166
     * @psalm-assert-if-true array|Closure $definition
167
     */
168 22
    private function isCallableDefinition($definition): bool
169
    {
170 22
        if ($definition instanceof Closure) {
171 8
            return true;
172
        }
173
174 14
        return is_array($definition)
175 14
            && array_keys($definition) === [0, 1]
176 14
            && is_string($definition[0])
177 14
            && is_string($definition[1])
178 6
            && in_array(
179 6
                $definition[1],
180 6
                class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
181
                true
182
            );
183
    }
184
185
    /**
186
     * @param mixed $definition
187
     *
188
     * @psalm-assert-if-true ArrayDefinitionConfig $definition
189
     */
190 9
    private function isArrayDefinition($definition): bool
191
    {
192 9
        if (!is_array($definition)) {
193 3
            return false;
194
        }
195
196
        try {
197 6
            DefinitionValidator::validateArrayDefinition($definition);
198 5
        } catch (InvalidConfigException $e) {
199 5
            return false;
200
        }
201
202 1
        return is_subclass_of((string) ($definition['class'] ?? ''), MiddlewareInterface::class);
203
    }
204
}
205