Passed
Push — master ( 5c8ce9...10deca )
by Rustam
12:11
created

MiddlewareFactory.php$1 ➔ isMiddlewareClassDefinition()   A

Complexity

Conditions 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 2
crap 2
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
17
use function in_array;
18
use function is_array;
19
use function is_string;
20
21
/**
22
 * Creates a PSR-15 middleware based on the definition provided.
23
 *
24
 * @psalm-import-type ArrayDefinitionConfig from ArrayDefinition
25
 */
26
final class MiddlewareFactory implements MiddlewareFactoryInterface
27
{
28
    private ContainerInterface $container;
29
    private WrapperFactoryInterface $wrapperFactory;
30
31
    /**
32
     * @param ContainerInterface $container Container to use for resolving definitions.
33
     */
34 27
    public function __construct(ContainerInterface $container, WrapperFactoryInterface $wrapperFactory)
35
    {
36 27
        $this->container = $container;
37 27
        $this->wrapperFactory = $wrapperFactory;
38
    }
39
40
    /**
41
     * @param array|callable|string $middlewareDefinition Middleware definition in one of the following formats:
42
     *
43
     * - A name of PSR-15 middleware class. The middleware instance will be obtained from container and executed.
44
     * - A callable with
45
     *   `function(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface`
46
     *   signature.
47
     * - A controller handler action in format `[TestController::class, 'index']`. `TestController` instance will
48
     *   be created and `index()` method will be executed.
49
     * - A function returning a middleware. The middleware returned will be executed.
50
     *
51
     * For handler action and callable
52
     * typed parameters are automatically injected using dependency injection container.
53
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
54
     * and {@see RequestHandlerInterface}.
55
     *
56
     * @throws InvalidMiddlewareDefinitionException
57
     */
58 24
    public function create($middlewareDefinition): MiddlewareInterface
59
    {
60 24
        if ($this->isMiddlewareClassDefinition($middlewareDefinition)) {
61
            /** @var MiddlewareInterface */
62 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

62
            return $this->container->get(/** @scrutinizer ignore-type */ $middlewareDefinition);
Loading history...
63
        }
64
65 22
        if ($this->isCallableDefinition($middlewareDefinition)) {
66
            /** @var array{0:class-string, 1:string}|Closure $middlewareDefinition */
67 13
            return $this->wrapperFactory->create($middlewareDefinition);
68
        }
69
70 9
        if ($this->isArrayDefinition($middlewareDefinition)) {
71
            /**
72
             * @psalm-var ArrayDefinitionConfig $middlewareDefinition
73
             *
74
             * @var MiddlewareInterface
75
             */
76 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

76
            return ArrayDefinition::fromConfig(/** @scrutinizer ignore-type */ $middlewareDefinition)->resolve($this->container);
Loading history...
77
        }
78
79 8
        throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
80
    }
81
82
    /**
83
     * @param mixed $definition
84
     *
85
     * @psalm-assert-if-true class-string<MiddlewareInterface> $definition
86
     */
87 24
    private function isMiddlewareClassDefinition($definition): bool
88
    {
89 24
        return is_string($definition)
90 24
            && is_subclass_of($definition, MiddlewareInterface::class);
91
    }
92
93
    /**
94
     * @param mixed $definition
95
     *
96
     * @psalm-assert-if-true array|Closure $definition
97
     */
98 22
    private function isCallableDefinition($definition): bool
99
    {
100 22
        if ($definition instanceof Closure) {
101 8
            return true;
102
        }
103
104 14
        return is_array($definition)
105 14
            && array_keys($definition) === [0, 1]
106 14
            && is_string($definition[0])
107 14
            && is_string($definition[1])
108 6
            && in_array(
109 6
                $definition[1],
110 6
                class_exists($definition[0]) ? get_class_methods($definition[0]) : [],
111
                true
112
            );
113
    }
114
115
    /**
116
     * @param mixed $definition
117
     *
118
     * @psalm-assert-if-true ArrayDefinitionConfig $definition
119
     */
120 9
    private function isArrayDefinition($definition): bool
121
    {
122 9
        if (!is_array($definition)) {
123 3
            return false;
124
        }
125
126
        try {
127 6
            DefinitionValidator::validateArrayDefinition($definition);
128 5
        } catch (InvalidConfigException $e) {
129 5
            return false;
130
        }
131
132 1
        return is_subclass_of((string) ($definition['class'] ?? ''), MiddlewareInterface::class);
133
    }
134
}
135