Completed
Pull Request — 2.x (#148)
by
unknown
04:17
created

SymfonyCache::clearPHPCodeCache()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 0
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 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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timeouts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
80
            @trigger_error('The "timeouts" argument is available since 3.x and will become mandatory in 4.0, please provide it.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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;
0 ignored issues
show
Bug introduced by
The property filesystem does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
90
        $this->cacheDir = $cacheDir;
91
        $this->token = $token;
92
        $this->types = $types;
0 ignored issues
show
Documentation Bug introduced by
It seems like $types of type array is incompatible with the declared type string of property $types.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $content; (string) is incompatible with the return type declared by the interface Sonata\Cache\CacheAdapterInterface::flush of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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 PHP OPcache.
251
     */
252
    protected function clearPHPCodeCache()
253
    {
254
        if (!$this->phpCodeCacheEnabled) {
255
            return;
256
        }
257
258
        if (function_exists('opcache_reset')) {
259
            opcache_reset();
260
        }
261
    }
262
}
263