Completed
Pull Request — master (#161)
by Grégoire
01:22
created

SymfonyCache::flush()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 53
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
rs 7.1199
c 0
b 0
f 0
cc 8
eloc 32
nc 11
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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