Passed
Pull Request — master (#189)
by Dmitriy
02:41
created

anonymous//src/Middleware/MiddlewareFactory.php$0   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 20
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
wmc 4
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 41
    public function __construct(
25
        private ContainerInterface $container,
26
        private CallableFactory $callableFactory,
27
    ) {
28 41
    }
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 23
    public function createMiddleware(
52
        MiddlewareInterface|callable|array|string $middlewareDefinition
53
    ): MiddlewareInterface {
54 23
        if ($middlewareDefinition instanceof MiddlewareInterface) {
0 ignored issues
show
introduced by
$middlewareDefinition is never a sub-type of Yiisoft\Queue\Middleware\MiddlewareInterface.
Loading history...
55 2
            return $middlewareDefinition;
56
        }
57
58 21
        if (is_string($middlewareDefinition)) {
0 ignored issues
show
introduced by
The condition is_string($middlewareDefinition) is always false.
Loading history...
59 5
            return $this->getFromContainer($middlewareDefinition);
60
        }
61
62 17
        return $this->tryGetFromCallable($middlewareDefinition)
63 17
            ?? $this->tryGetFromArrayDefinition($middlewareDefinition)
64 11
            ?? throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
65
    }
66
67 5
    private function getFromContainer(string $middlewareDefinition): MiddlewareInterface
68
    {
69 5
        if (class_exists($middlewareDefinition)) {
70 4
            if (is_subclass_of($middlewareDefinition, MiddlewareInterface::class)) {
71
                /** @var MiddlewareInterface */
72 4
                return $this->container->get($middlewareDefinition);
73
            }
74 1
        } elseif ($this->container->has($middlewareDefinition)) {
75
            $middleware = $this->container->get($middlewareDefinition);
76
            if ($middleware instanceof MiddlewareInterface) {
77
                return $middleware;
78
            }
79
        }
80
81 2
        throw new InvalidMiddlewareDefinitionException($middlewareDefinition);
82
    }
83
84 10
    private function wrapCallable(callable $callback): MiddlewareInterface
85
    {
86 10
        return new class ($callback, $this->container) implements MiddlewareInterface {
87
            private $callback;
88
89
            public function __construct(callable $callback, private ContainerInterface $container)
90
            {
91 10
                $this->callback = $callback;
92
            }
93
94
            public function process(Request $request, MessageHandlerInterface $handler): Request
95
            {
96 10
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
97 10
                if ($response instanceof Request) {
98 8
                    return $response;
99
                }
100
101 2
                if ($response instanceof MiddlewareInterface) {
102 1
                    return $response->process($request, $handler);
103
                }
104
105 1
                throw new InvalidMiddlewareDefinitionException($this->callback);
106
            }
107 10
        };
108
    }
109
110 17
    private function tryGetFromCallable(
111
        callable|MiddlewareInterface|array|string $definition
112
    ): ?MiddlewareInterface {
113 17
        if ($definition instanceof Closure) {
0 ignored issues
show
introduced by
$definition is never a sub-type of Closure.
Loading history...
114 5
            return $this->wrapCallable($definition);
115
        }
116
117
        if (
118 12
            is_array($definition)
119 12
            && array_keys($definition) === [0, 1]
120
        ) {
121
            try {
122 8
                return $this->wrapCallable($this->callableFactory->create($definition));
123 3
            } catch (InvalidCallableConfigurationException $exception) {
124 3
                throw new InvalidMiddlewareDefinitionException($definition, previous: $exception);
125
            }
126
        } else {
127 4
            return null;
128
        }
129
    }
130
131 4
    private function tryGetFromArrayDefinition(
132
        callable|MiddlewareInterface|array|string $definition
133
    ): ?MiddlewareInterface {
134 4
        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 4
            DefinitionValidator::validateArrayDefinition($definition);
140
141 2
            $middleware = ArrayDefinition::fromConfig($definition)->resolve($this->container);
142 2
            if ($middleware instanceof MiddlewareInterface) {
143 1
                return $middleware;
144
            }
145
146 1
            throw new InvalidMiddlewareDefinitionException($definition);
147 3
        } 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 2
        throw new InvalidMiddlewareDefinitionException($definition);
151
    }
152
}
153