Failed Conditions
Pull Request — master (#7085)
by Guilherme
14:34
created

lib/Doctrine/ORM/Cache/Region/FileLockRegion.php (6 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Cache\Region;
6
7
use Doctrine\ORM\Cache\CacheEntry;
8
use Doctrine\ORM\Cache\CacheKey;
9
use Doctrine\ORM\Cache\CollectionCacheEntry;
10
use Doctrine\ORM\Cache\ConcurrentRegion;
11
use Doctrine\ORM\Cache\Lock;
12
use Doctrine\ORM\Cache\Region;
13
use const DIRECTORY_SEPARATOR;
14
use const LOCK_EX;
15
use function array_filter;
16
use function array_map;
17
use function chmod;
18
use function file_get_contents;
19
use function file_put_contents;
20
use function fileatime;
21
use function glob;
22
use function is_dir;
23
use function is_file;
24
use function is_writable;
25
use function mkdir;
26
use function sprintf;
27
use function time;
28
use function unlink;
29
30
/**
31
 * Very naive concurrent region, based on file locks.
32
 */
33
class FileLockRegion implements ConcurrentRegion
34
{
35
    public const LOCK_EXTENSION = 'lock';
36
37
    /**
38
     * @var Region
39
     */
40
    private $region;
41
42
    /**
43
     * @var string
44
     */
45
    private $directory;
46
47
    /**
48
     * @var int
49
     */
50
    private $lockLifetime;
51
52
    /**
53
     * @param string $directory
54
     * @param string $lockLifetime
55
     *
56
     * @throws \InvalidArgumentException
57
     */
58 12
    public function __construct(Region $region, $directory, $lockLifetime)
59
    {
60 12
        if (! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
61
            throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
62
        }
63
64 12
        if (! is_writable($directory)) {
65
            throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
66
        }
67
68 12
        $this->region       = $region;
69 12
        $this->directory    = $directory;
70 12
        $this->lockLifetime = $lockLifetime;
0 ignored issues
show
Documentation Bug introduced by
The property $lockLifetime was declared of type integer, but $lockLifetime is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
71 12
    }
72
73
    /**
74
     * @return bool
75
     */
76 10
    private function isLocked(CacheKey $key, ?Lock $lock = null)
77
    {
78 10
        $filename = $this->getLockFileName($key);
79
80 10
        if (! is_file($filename)) {
81 10
            return false;
82
        }
83
84 6
        $time    = $this->getLockTime($filename);
85 6
        $content = $this->getLockContent($filename);
86
87 6
        if (! $content || ! $time) {
88
            @unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

88
            /** @scrutinizer ignore-unhandled */ @unlink($filename);

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...
89
90
            return false;
91
        }
92
93 6
        if ($lock && $content === $lock->value) {
94 1
            return false;
95
        }
96
97
        // outdated lock
98 6
        if (($time + $this->lockLifetime) <= time()) {
99 1
            @unlink($filename);
100
101 1
            return false;
102
        }
103
104 5
        return true;
105
    }
106
107
    /**
108
     * @return string
109
     */
110 10
    private function getLockFileName(CacheKey $key)
111
    {
112 10
        return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
113
    }
114
115
    /**
116
     * @param string $filename
117
     *
118
     * @return string
119
     */
120 6
    private function getLockContent($filename)
121
    {
122 6
        return @file_get_contents($filename);
123
    }
124
125
    /**
126
     * @param string $filename
127
     *
128
     * @return int
129
     */
130 6
    private function getLockTime($filename)
131
    {
132 6
        return @fileatime($filename);
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 1
    public function getName()
139
    {
140 1
        return $this->region->getName();
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 10
    public function contains(CacheKey $key)
147
    {
148 10
        if ($this->isLocked($key)) {
149 5
            return false;
150
        }
151
152 10
        return $this->region->contains($key);
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158 6
    public function get(CacheKey $key)
159
    {
160 6
        if ($this->isLocked($key)) {
161 3
            return null;
162
        }
163
164 3
        return $this->region->get($key);
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function getMultiple(CollectionCacheEntry $collection)
171
    {
172
        if (array_filter(array_map([$this, 'isLocked'], $collection->identifiers))) {
173
            return null;
174
        }
175
176
        return $this->region->getMultiple($collection);
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 10
    public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null)
183
    {
184 10
        if ($this->isLocked($key, $lock)) {
185 1
            return false;
186
        }
187
188 10
        return $this->region->put($key, $entry);
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194 3
    public function evict(CacheKey $key)
195
    {
196 3
        if ($this->isLocked($key)) {
197 1
            @unlink($this->getLockFileName($key));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

197
            /** @scrutinizer ignore-unhandled */ @unlink($this->getLockFileName($key));

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...
198
        }
199
200 3
        return $this->region->evict($key);
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206 3
    public function evictAll()
207
    {
208
        // The check below is necessary because on some platforms glob returns false
209
        // when nothing matched (even though no errors occurred)
210 3
        $filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION));
211
212 3
        if ($filenames) {
213 1
            foreach ($filenames as $filename) {
214 1
                @unlink($filename);
215
            }
216
        }
217
218 3
        return $this->region->evictAll();
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224 6
    public function lock(CacheKey $key)
225
    {
226 6
        if ($this->isLocked($key)) {
227 1
            return null;
228
        }
229
230 5
        $lock     = Lock::createLockRead();
231 5
        $filename = $this->getLockFileName($key);
232
233 5
        if (! @file_put_contents($filename, $lock->value, LOCK_EX)) {
234
            return null;
235
        }
236 5
        chmod($filename, 0664);
237
238 5
        return $lock;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 2
    public function unlock(CacheKey $key, Lock $lock)
245
    {
246 2
        if ($this->isLocked($key, $lock)) {
247 1
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Doctrine\ORM\Cache\ConcurrentRegion::unlock() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
248
        }
249
250 1
        if (! @unlink($this->getLockFileName($key))) {
251
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Doctrine\ORM\Cache\ConcurrentRegion::unlock() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
252
        }
253
254 1
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by Doctrine\ORM\Cache\ConcurrentRegion::unlock() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
255
    }
256
}
257