Passed
Push — master ( 654348...8ec1ee )
by Dāvis
03:00
created

MiddlewarePass::filterClientMiddleware()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 18
nc 7
nop 2
dl 0
loc 29
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
     */
37
    private function findAvailableMiddleware(ContainerBuilder $container)
38
    {
39
        $services = $container->findTaggedServiceIds(self::MIDDLEWARE_TAG);
40
        $middleware = [];
41
42
        foreach ($services as $id => $tags) {
43
            if (count($tags) > 1) {
44
                throw new \LogicException(sprintf('Middleware should only use a single \'%s\' tag', self::MIDDLEWARE_TAG));
45
            }
46
47
            if (!isset($tags[0]['alias'])) {
48
                throw new \LogicException(sprintf('The \'alias\' attribute is mandatory for the \'%s\' tag', self::MIDDLEWARE_TAG));
49
            }
50
51
            $priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0;
52
53
            $middleware[$priority][] = [
54
                'alias' => $tags[0]['alias'],
55
                'id' => $id,
56
            ];
57
        }
58
59
        if (empty($middleware)) {
60
            return [];
61
        }
62
63
        krsort($middleware);
64
65
        return call_user_func_array('array_merge', $middleware);
66
    }
67
68
    /**
69
     * Sets up handlers and registers middleware for each tagged client.
70
     *
71
     * @param ContainerBuilder $container
72
     * @param array            $middlewareBag
73
     */
74
    private function registerMiddleware(ContainerBuilder $container, array $middlewareBag)
75
    {
76
        if (empty($middlewareBag)) {
77
            return;
78
        }
79
80
        $clients = $container->findTaggedServiceIds(self::CLIENT_TAG);
81
82
        foreach ($clients as $clientId => $tags) {
83
            if (count($tags) > 1) {
84
                throw new \LogicException(sprintf('Clients should use a single \'%s\' tag', self::CLIENT_TAG));
85
            }
86
87
            $clientMiddleware = $this->filterClientMiddleware($middlewareBag, $tags);
88
89
            if (empty($clientMiddleware)) {
90
                continue;
91
            }
92
93
            $clientDefinition = $container->findDefinition($clientId);
94
95
            $arguments = $clientDefinition->getArguments();
96
97
            if (!empty($arguments)) {
98
                $options = array_shift($arguments);
99
            } else {
100
                $options = [];
101
            }
102
103
            if (!isset($options['handler'])) {
104
                $handlerStack = new Definition(HandlerStack::class);
105
                $handlerStack->setFactory([
106
                    HandlerStack::class,
107
                    'create',
108
                ]);
109
                $handlerStack->setPublic(false);
110
            } else {
111
                $handlerStack = $this->wrapHandlerInHandlerStack($options['handler'], $container);
112
            }
113
114
            $this->addMiddlewareToHandlerStack($handlerStack, $clientMiddleware);
115
            $options['handler'] = $handlerStack;
116
117
            array_unshift($arguments, $options);
118
            $clientDefinition->setArguments($arguments);
119
        }
120
    }
121
122
    /**
123
     * @param Reference|Definition|callable $handler The configured Guzzle handler
124
     * @param ContainerBuilder              $container The container builder
125
     *
126
     * @return Definition
127
     */
128
    private function wrapHandlerInHandlerStack($handler, ContainerBuilder $container)
129
    {
130
        if ($handler instanceof Reference) {
131
            $handler = $container->getDefinition((string)$handler);
132
        }
133
134
        if ($handler instanceof Definition && HandlerStack::class === $handler->getClass()) {
135
            // no need to wrap the Guzzle handler if it already resolves to a HandlerStack
136
            return $handler;
137
        }
138
139
        $handlerDefinition = new Definition(HandlerStack::class);
140
        $handlerDefinition->setArguments([$handler]);
141
        $handlerDefinition->setPublic(false);
142
143
        return $handlerDefinition;
144
    }
145
146
    private function addMiddlewareToHandlerStack(Definition $handlerStack, array $middlewareBag)
147
    {
148
        foreach ($middlewareBag as $middleware) {
149
            $handlerStack->addMethodCall('push', [
150
                new Reference($middleware['id']),
151
                $middleware['alias'],
152
            ]);
153
        }
154
    }
155
156
    /**
157
     * @param array $middlewareBag The list of availables middleware
158
     * @param array $tags          The tags containing middleware configuration
159
     *
160
     * @return array The list of middleware to enable for the client
161
     *
162
     * @throws LogicException When middleware configuration is invalid
163
     */
164
    private function filterClientMiddleware(array $middlewareBag, array $tags)
165
    {
166
        if (!isset($tags[0]['middleware'])) {
167
            return $middlewareBag;
168
        }
169
170
        $clientMiddlewareList = explode(' ', $tags[0]['middleware']);
171
172
        $whiteList = [];
173
        $blackList = [];
174
        foreach ($clientMiddlewareList as $middleware) {
175
            if ('!' === $middleware[0]) {
176
                $blackList[] = substr($middleware, 1);
177
            } else {
178
                $whiteList[] = $middleware;
179
            }
180
        }
181
182
        if ($whiteList && $blackList) {
183
            throw new LogicException('You cannot mix whitelisting and blacklisting of middleware at the same time.');
184
        }
185
186
        if ($whiteList) {
187
            return array_filter($middlewareBag, function($value) use ($whiteList) {
188
                return in_array($value['alias'], $whiteList, true);
189
            });
190
        } else {
191
            return array_filter($middlewareBag, function($value) use ($blackList) {
192
                return !in_array($value['alias'], $blackList, true);
193
            });
194
        }
195
    }
196
}