Passed
Pull Request — master (#190)
by Dmitriy
04:22 queued 01:36
created

MiddlewareFactory::createMiddleware()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 14
ccs 8
cts 8
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Queue\Middleware;
6
7
use Closure;
8
use Psr\Container\ContainerInterface;
9
use Yiisoft\Definitions\ArrayDefinition;
10
use Yiisoft\Definitions\Exception\InvalidConfigException;
11
use Yiisoft\Definitions\Helpers\DefinitionValidator;
12
use Yiisoft\Injector\Injector;
13
14
use function is_string;
15
16
/**
17
 * Creates a middleware based on the definition provided.
18
 */
19
final class MiddlewareFactory implements MiddlewareFactoryInterface
20
{
21
    /**
22
     * @param ContainerInterface $container Container to use for resolving definitions.
23
     */
24 85
    public function __construct(
25
        private ContainerInterface $container,
26
        private CallableFactory $callableFactory,
27
    ) {
28 85
    }
29
30
    /**
31
     * @param array|callable|MiddlewareInterface|string $middlewareDefinition Middleware definition in one of the
32
     *     following formats:
33
     *
34
     * - A middleware object.
35
     * - A name of a middleware class. The middleware instance will be obtained from container and executed.
36
     * - A callable with `function(ServerRequestInterface $request, RequestHandlerInterface $handler):
37
     *     ResponseInterface` signature.
38
     * - A controller handler action in format `[TestController::class, 'index']`. `TestController` instance will
39
     *   be created and `index()` method will be executed.
40
     * - A function returning a middleware. The middleware returned will be executed.
41
     *
42
     * For handler action and callable
43
     * typed parameters are automatically injected using dependency injection container.
44
     * Current request and handler could be obtained by type-hinting for {@see ServerRequestInterface}
45
     * and {@see RequestHandlerInterface}.
46
     *
47
     * @throws InvalidMiddlewareDefinitionException
48
     *
49
     * @return MiddlewareInterface
50
     */
51 68
    public function createMiddleware(
52
        MiddlewareInterface|callable|array|string $middlewareDefinition
53
    ): MiddlewareInterface {
54 68
        if ($middlewareDefinition instanceof MiddlewareInterface) {
0 ignored issues
show
introduced by
$middlewareDefinition is never a sub-type of Yiisoft\Queue\Middleware\MiddlewareInterface.
Loading history...
55 3
            return $middlewareDefinition;
56
        }
57
58 66
        if (is_string($middlewareDefinition)) {
0 ignored issues
show
introduced by
The condition is_string($middlewareDefinition) is always false.
Loading history...
59 17
            return $this->getFromContainer($middlewareDefinition);
60
        }
61
62 53
        return $this->tryGetFromCallable($middlewareDefinition)
63 53
            ?? $this->tryGetFromArrayDefinition($middlewareDefinition)
64 41
            ?? throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
65
    }
66
67 17
    private function getFromContainer(string $middlewareDefinition): MiddlewareInterface
68
    {
69 17
        if (class_exists($middlewareDefinition)) {
70 13
            if (is_subclass_of($middlewareDefinition, MiddlewareInterface::class)) {
71
                /** @var MiddlewareInterface */
72 13
                return $this->container->get($middlewareDefinition);
73
            }
74 4
        } elseif ($this->container->has($middlewareDefinition)) {
75 1
            $middleware = $this->container->get($middlewareDefinition);
76 1
            if ($middleware instanceof MiddlewareInterface) {
77 1
                return $middleware;
78
            }
79
        }
80
81 6
        throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
82
    }
83
84 32
    private function wrapCallable(callable $callback): MiddlewareInterface
85
    {
86 32
        return new class ($callback, $this->container) implements MiddlewareInterface {
87
            private $callback;
88
89
            public function __construct(callable $callback, private ContainerInterface $container)
90
            {
91 32
                $this->callback = $callback;
92
            }
93
94
            public function process(Request $request, MessageHandlerInterface $handler): Request
95
            {
96 32
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
97 32
                if ($response instanceof Request) {
98 25
                    return $response;
99
                }
100
101 8
                if ($response instanceof MiddlewareInterface) {
102 4
                    return $response->process($request, $handler);
103
                }
104
105 4
                throw new InvalidMiddlewareDefinitionException($this->callback);
106
            }
107 32
        };
108
    }
109
110 53
    private function tryGetFromCallable(
111
        callable|MiddlewareInterface|array|string $definition
112
    ): ?MiddlewareInterface {
113 53
        if ($definition instanceof Closure) {
0 ignored issues
show
introduced by
$definition is never a sub-type of Closure.
Loading history...
114 17
            return $this->wrapCallable($definition);
115
        }
116
117
        if (
118 37
            is_array($definition)
119 37
            && array_keys($definition) === [0, 1]
120
        ) {
121
            try {
122 25
                return $this->wrapCallable($this->callableFactory->create($definition));
123 9
            } catch (InvalidCallableConfigurationException $exception) {
124 9
                throw new InvalidMiddlewareDefinitionException($definition, previous: $exception);
125
            }
126
        } else {
127 13
            return null;
128
        }
129
    }
130
131 13
    private function tryGetFromArrayDefinition(
132
        callable|MiddlewareInterface|array|string $definition
133
    ): ?MiddlewareInterface {
134 13
        if (!is_array($definition)) {
0 ignored issues
show
introduced by
The condition is_array($definition) is always true.
Loading history...
135
            return null;
136
        }
137
138
        try {
139 13
            DefinitionValidator::validateArrayDefinition($definition);
140
141 7
            $middleware = ArrayDefinition::fromConfig($definition)->resolve($this->container);
142 7
            if ($middleware instanceof MiddlewareInterface) {
143 4
                return $middleware;
144
            }
145
146 3
            throw new InvalidMiddlewareDefinitionException($definition);
147 9
        } catch (InvalidConfigException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
148
        }
149
150 6
        return null;
151
    }
152
}
153