Completed
Pull Request — master (#256)
by
unknown
01:22
created

SymfonyCache::cacheAction()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 8.0835
c 0
b 0
f 0
cc 8
nc 18
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\CacheBundle\Adapter;
15
16
use Sonata\Cache\CacheAdapterInterface;
17
use Sonata\Cache\CacheElementInterface;
18
use Sonata\Cache\Exception\UnsupportedException;
19
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
20
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
21
use Symfony\Component\Filesystem\Filesystem;
22
use Symfony\Component\HttpFoundation\Response;
23
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
24
use Symfony\Component\Routing\RouterInterface;
25
26
/**
27
 * Handles Symfony cache.
28
 *
29
 * @author Vincent Composieux <[email protected]>
30
 */
31
class SymfonyCache implements CacheAdapterInterface
32
{
33
    /**
34
     * @var RouterInterface
35
     */
36
    protected $router;
37
38
    /**
39
     * @var string
40
     */
41
    protected $cacheDir;
42
43
    /**
44
     * @var string
45
     */
46
    protected $token;
47
48
    /**
49
     * @var string[]
50
     */
51
    protected $types;
52
53
    /**
54
     * @var bool
55
     */
56
    protected $phpCodeCacheEnabled;
57
58
    /**
59
     * @var array
60
     */
61
    protected $servers;
62
63
    /**
64
     * @var array
65
     */
66
    protected $timeouts;
67
68
    /**
69
     * @var Filesystem
70
     */
71
    protected $filesystem;
72
73
    /**
74
     * @var EventDispatcherInterface
75
     */
76
    protected $eventDispatcher;
77
78
    public function __construct(
79
        RouterInterface $router,
80
        Filesystem $filesystem,
81
        EventDispatcherInterface $eventDispatcher,
82
        string $cacheDir,
83
        string $token,
84
        bool $phpCodeCacheEnabled,
85
        array $types,
86
        array $servers,
87
        array $timeouts
88
    ) {
89
        $this->router = $router;
90
        $this->filesystem = $filesystem;
91
        $this->eventDispatcher = $eventDispatcher;
92
        $this->cacheDir = $cacheDir;
93
        $this->token = $token;
94
        $this->types = $types;
95
        $this->phpCodeCacheEnabled = $phpCodeCacheEnabled;
96
        $this->servers = $servers;
97
        $this->timeouts = $timeouts;
98
    }
99
100
    public function flushAll(): bool
101
    {
102
        return $this->flush(['all']);
103
    }
104
105
    /**
106
     * @throws \InvalidArgumentException
107
     * @throws \UnexpectedValueException
108
     */
109
    public function flush(array $keys = ['all']): bool
110
    {
111
        $result = true;
112
113
        foreach ($this->servers as $server) {
114
            foreach ($keys as $type) {
115
                $ip = $server['ip'];
116
117
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
118
                    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
119
                } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
120
                    $socket = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
121
                } else {
122
                    throw new \InvalidArgumentException(sprintf('"%s" is not a valid ip address', $ip));
123
                }
124
125
                // generate the raw http request
126
                $command = sprintf("GET %s HTTP/1.1\r\n", $this->getUrl($type));
127
                $command .= sprintf("Host: %s\r\n", $server['domain']);
128
129
                if ($server['basic']) {
130
                    $command .= sprintf("Authorization: Basic %s\r\n", $server['basic']);
131
                }
132
133
                $command .= "Connection: Close\r\n\r\n";
134
135
                // setup the default timeout (avoid max execution time)
136
                socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $this->timeouts['SND']);
137
                socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $this->timeouts['RCV']);
138
139
                socket_connect($socket, $server['ip'], $server['port']);
140
                socket_write($socket, $command);
141
142
                $content = '';
143
144
                do {
145
                    $buffer = socket_read($socket, 1024);
146
                    $content .= $buffer;
147
                } while (!empty($buffer));
148
149
                if ($result) {
150
                    $result = 'ok' === substr($content, -2);
151
                } else {
152
                    throw new \UnexpectedValueException(sprintf(
153
                        'Server answered with "%s"',
154
                        $content
155
                    ));
156
                }
157
            }
158
        }
159
160
        return $result;
161
    }
162
163
    /**
164
     * @throws AccessDeniedHttpException
165
     * @throws \RuntimeException
166
     */
167
    public function cacheAction(string $token, string $type): Response
168
    {
169
        if ($this->token !== $token) {
170
            throw new AccessDeniedHttpException('Invalid token.');
171
        }
172
173
        if (!\in_array($type, $this->types, true)) {
174
            throw new \RuntimeException(
175
                sprintf('Type "%s" is not defined, allowed types are: "%s"', $type, implode(', ', $this->types))
176
            );
177
        }
178
179
        $path = 'all' === $type ? $this->cacheDir : sprintf('%s/%s', $this->cacheDir, $type);
180
181
        // clean up event dispatcher just before clearing the cache to prevent further events execution
182
        foreach ($this->eventDispatcher->getListeners() as $eventName => $eventListenerList) {
183
            foreach ($eventListenerList as $listener) {
184
                $listener[0] instanceof EventSubscriberInterface ?
185
                    $this->eventDispatcher->removeSubscriber($listener[0]) :
186
                    $this->eventDispatcher->removeListener($eventName, [$listener[0], $listener[1]]);
187
            }
188
        }
189
190
        if ($this->filesystem->exists($path)) {
191
            $movedPath = $path.'_old_'.uniqid();
192
193
            $this->filesystem->rename($path, $movedPath);
194
            $this->filesystem->remove($movedPath);
195
196
            $this->clearPHPCodeCache();
197
        }
198
199
        return new Response('ok', 200, [
200
            'Cache-Control' => 'no-cache, must-revalidate',
201
            'Content-Length' => 2, // to prevent chunked transfer encoding
202
        ]);
203
    }
204
205
    /**
206
     * @throws UnsupportedException
207
     */
208
    public function has(array $keys): bool
209
    {
210
        throw new UnsupportedException('SymfonyCache has() method does not exist.');
211
    }
212
213
    /**
214
     * @throws UnsupportedException
215
     */
216
    public function set(array $keys, $data, int $ttl = 84600, array $contextualKeys = []): CacheElementInterface
217
    {
218
        throw new UnsupportedException('SymfonyCache set() method does not exist.');
219
    }
220
221
    /**
222
     * @throws UnsupportedException
223
     */
224
    public function get(array $keys): CacheElementInterface
225
    {
226
        throw new UnsupportedException('SymfonyCache get() method does not exist.');
227
    }
228
229
    public function isContextual(): bool
230
    {
231
        return false;
232
    }
233
234
    protected function getUrl(string $type): ?string
235
    {
236
        return $this->router->generate('sonata_cache_symfony', [
237
            'token' => $this->token,
238
            'type' => $type,
239
        ]);
240
    }
241
242
    protected function clearPHPCodeCache(): void
243
    {
244
        if (!$this->phpCodeCacheEnabled) {
245
            return;
246
        }
247
248
        if (\function_exists('opcache_reset')) {
249
            opcache_reset();
250
        }
251
    }
252
}
253