Completed
Push — 2.x ( d5ddac...0a20e4 )
by Christian
01:16
created

Adapter/SymfonyCache.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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