Passed
Push — master ( d093e3...81fa39 )
by Dāvis
03:13 queued 25s
created

MiddlewarePass::filterClientMiddleware()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 7
nop 2
dl 0
loc 30
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace Sludio\HelperBundle\DependencyInjection\Compiler;
4
5
use GuzzleHttp\HandlerStack;
6
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
7
use Symfony\Component\DependencyInjection\ContainerBuilder;
8
use Symfony\Component\DependencyInjection\Definition;
9
use Symfony\Component\DependencyInjection\Exception\LogicException;
10
use Symfony\Component\DependencyInjection\Reference;
11
12
/**
13
 * Csa Guzzle middleware compiler pass.
14
 *
15
 * @author Charles Sarrazin <[email protected]>
16
 * @author Tobias Schultze <http://tobion.de>
17
 */
18
class MiddlewarePass implements CompilerPassInterface
19
{
20
    const MIDDLEWARE_TAG = 'sludio_helper.guzzle.middleware';
21
    const CLIENT_TAG = 'sludio_helper.guzzle.client';
22
23
    public function process(ContainerBuilder $container)
24
    {
25
        $middleware = $this->findAvailableMiddleware($container);
26
27
        $this->registerMiddleware($container, $middleware);
28
    }
29
30
    /**
31
     * Fetches the list of available middleware.
32
     *
33
     * @param ContainerBuilder $container
34
     *
35
     * @return array
36
     * @throws \LogicException
37
     */
38
    private function findAvailableMiddleware(ContainerBuilder $container)
39
    {
40
        $services = $container->findTaggedServiceIds(self::MIDDLEWARE_TAG);
41
        $middleware = [];
42
43
        foreach ($services as $id => $tags) {
44
            if (count($tags) > 1) {
45
                throw new \LogicException(sprintf('Middleware should only use a single \'%s\' tag', self::MIDDLEWARE_TAG));
46
            }
47
48
            if (!isset($tags[0]['alias'])) {
49
                throw new \LogicException(sprintf('The \'alias\' attribute is mandatory for the \'%s\' tag', self::MIDDLEWARE_TAG));
50
            }
51
52
            $priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0;
53
54
            $middleware[$priority][] = [
55
                'alias' => $tags[0]['alias'],
56
                'id' => $id,
57
            ];
58
        }
59
60
        if (empty($middleware)) {
61
            return [];
62
        }
63
64
        krsort($middleware);
65
66
        return array_merge(...$middleware);
0 ignored issues
show
Bug introduced by
$middleware is expanded, but the parameter $array1 of array_merge() does not expect variable arguments. ( Ignorable by Annotation )

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

66
        return array_merge(/** @scrutinizer ignore-type */ ...$middleware);
Loading history...
67
    }
68
69
    /**
70
     * Sets up handlers and registers middleware for each tagged client.
71
     *
72
     * @param ContainerBuilder $container
73
     * @param array            $middlewareBag
74
     *
75
     * @throws \LogicException
76
     */
77
    private function registerMiddleware(ContainerBuilder $container, array $middlewareBag)
78
    {
79
        if (empty($middlewareBag)) {
80
            return;
81
        }
82
83
        $clients = $container->findTaggedServiceIds(self::CLIENT_TAG);
84
85
        foreach ($clients as $clientId => $tags) {
86
            if (count($tags) > 1) {
87
                throw new \LogicException(sprintf('Clients should use a single \'%s\' tag', self::CLIENT_TAG));
88
            }
89
90
            try {
91
                $clientMiddleware = $this->filterClientMiddleware($middlewareBag, $tags);
92
            } catch (LogicException $e) {
93
                continue;
94
            }
95
96
            if (empty($clientMiddleware)) {
97
                continue;
98
            }
99
100
            $clientDefinition = $container->findDefinition($clientId);
101
102
            $arguments = $clientDefinition->getArguments();
103
104
            $options = [];
105
            if (!empty($arguments)) {
106
                $options = array_shift($arguments);
107
            }
108
109
            if (!isset($options['handler'])) {
110
                $handlerStack = new Definition(HandlerStack::class);
111
                $handlerStack->setFactory([
112
                    HandlerStack::class,
113
                    'create',
114
                ]);
115
                $handlerStack->setPublic(false);
116
            } else {
117
                $handlerStack = $this->wrapHandlerInHandlerStack($options['handler'], $container);
118
            }
119
120
            $this->addMiddlewareToHandlerStack($handlerStack, $clientMiddleware);
121
            $options['handler'] = $handlerStack;
122
123
            array_unshift($arguments, $options);
124
            $clientDefinition->setArguments($arguments);
125
        }
126
    }
127
128
    /**
129
     * @param Reference|Definition|callable $handler   The configured Guzzle handler
130
     * @param ContainerBuilder              $container The container builder
131
     *
132
     * @return Definition
133
     */
134
    private function wrapHandlerInHandlerStack($handler, ContainerBuilder $container)
135
    {
136
        if ($handler instanceof Reference) {
137
            $handler = $container->getDefinition((string)$handler);
138
        }
139
140
        if ($handler instanceof Definition && HandlerStack::class === $handler->getClass()) {
141
            // no need to wrap the Guzzle handler if it already resolves to a HandlerStack
142
            return $handler;
143
        }
144
145
        $handlerDefinition = new Definition(HandlerStack::class);
146
        $handlerDefinition->setArguments([$handler]);
147
        $handlerDefinition->setPublic(false);
148
149
        return $handlerDefinition;
150
    }
151
152
    private function addMiddlewareToHandlerStack(Definition $handlerStack, array $middlewareBag)
153
    {
154
        foreach ($middlewareBag as $middleware) {
155
            $handlerStack->addMethodCall('push', [
156
                new Reference($middleware['id']),
157
                $middleware['alias'],
158
            ]);
159
        }
160
    }
161
162
    /**
163
     * @param array $middlewareBag The list of availables middleware
164
     * @param array $tags          The tags containing middleware configuration
165
     *
166
     * @return array The list of middleware to enable for the client
167
     *
168
     * @throws LogicException When middleware configuration is invalid
169
     */
170
    private function filterClientMiddleware(array $middlewareBag, array $tags)
171
    {
172
        if (!isset($tags[0]['middleware'])) {
173
            return $middlewareBag;
174
        }
175
176
        $clientMiddlewareList = explode(' ', $tags[0]['middleware']);
177
178
        $whiteList = [];
179
        $blackList = [];
180
        foreach ($clientMiddlewareList as $middleware) {
181
            if ('!' === $middleware[0]) {
182
                $blackList[] = substr($middleware, 1);
183
            } else {
184
                $whiteList[] = $middleware;
185
            }
186
        }
187
188
        if ($whiteList && $blackList) {
189
            throw new LogicException('You cannot mix whitelisting and blacklisting of middleware at the same time.');
190
        }
191
192
        if ($whiteList) {
193
            return array_filter($middlewareBag, function ($value) use ($whiteList) {
194
                return in_array($value['alias'], $whiteList, true);
195
            });
196
        }
197
198
        return array_filter($middlewareBag, function ($value) use ($blackList) {
199
            return !in_array($value['alias'], $blackList, true);
200
        });
201
    }
202
}
203