Completed
Push — master ( 2a239b...205ee7 )
by Marco
22s
created

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

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 SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the MIT license. For more information, see
18
 * <http://www.doctrine-project.org>.
19
 */
20
21
namespace Doctrine\ORM\Cache\Region;
22
23
use Doctrine\ORM\Cache\CollectionCacheEntry;
24
use Doctrine\ORM\Cache\Lock;
25
use Doctrine\ORM\Cache\Region;
26
use Doctrine\ORM\Cache\CacheKey;
27
use Doctrine\ORM\Cache\CacheEntry;
28
use Doctrine\ORM\Cache\ConcurrentRegion;
29
30
/**
31
 * Very naive concurrent region, based on file locks.
32
 *
33
 * @since   2.5
34
 * @author  Fabio B. Silva <fabio.bat.silvagmail.com>
35
 */
36
class FileLockRegion implements ConcurrentRegion
37
{
38
    const LOCK_EXTENSION = 'lock';
39
40
    /**
41
     * var \Doctrine\ORM\Cache\Region
42
     */
43
    private $region;
44
45
    /**
46
     * @var string
47
     */
48
    private $directory;
49
50
    /**
51
     * var integer
52
     */
53
    private $lockLifetime;
54
55
    /**
56
     * @param \Doctrine\ORM\Cache\Region $region
57
     * @param string                     $directory
58
     * @param string                     $lockLifetime
59
     *
60
     * @throws \InvalidArgumentException
61
     */
62 12
    public function __construct(Region $region, $directory, $lockLifetime)
63
    {
64 12
        if ( ! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
65
            throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
66
        }
67
68 12
        if ( ! is_writable($directory)) {
69
            throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
70
        }
71
72 12
        $this->region       = $region;
73 12
        $this->directory    = $directory;
74 12
        $this->lockLifetime = $lockLifetime;
75 12
    }
76
77
    /**
78
     * @param \Doctrine\ORM\Cache\CacheKey $key
79
     * @param \Doctrine\ORM\Cache\Lock     $lock
80
     *
81
     * @return boolean
82
     */
83 10
    private function isLocked(CacheKey $key, Lock $lock = null)
84
    {
85 10
        $filename = $this->getLockFileName($key);
86
87 10
        if ( ! is_file($filename)) {
88 10
            return false;
89
        }
90
91 6
        $time     = $this->getLockTime($filename);
92 6
        $content  = $this->getLockContent($filename);
93
94 6
        if ( ! $content || ! $time) {
95
            @unlink($filename);
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...
96
97
            return false;
98
        }
99
100 6
        if ($lock && $content === $lock->value) {
101 1
            return false;
102
        }
103
104
        // outdated lock
105 6
        if (($time + $this->lockLifetime) <= time()) {
106 1
            @unlink($filename);
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...
107
108 1
            return false;
109
        }
110
111 5
        return true;
112
    }
113
114
    /**
115
     * @param \Doctrine\ORM\Cache\CacheKey $key
116
     *
117
     * @return string
118
     */
119 10
    private function getLockFileName(CacheKey $key)
120
    {
121 10
        return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
122
    }
123
124
    /**
125
     * @param string $filename
126
     *
127
     * @return string
128
     */
129 6
    private function getLockContent($filename)
130
    {
131 6
        return @file_get_contents($filename);
132
    }
133
134
    /**
135
     * @param string $filename
136
     *
137
     * @return integer
138
     */
139 6
    private function getLockTime($filename)
140
    {
141 6
        return @fileatime($filename);
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 1
    public function getName()
148
    {
149 1
        return $this->region->getName();
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 10
    public function contains(CacheKey $key)
156
    {
157 10
        if ($this->isLocked($key)) {
158 5
            return false;
159
        }
160
161 10
        return $this->region->contains($key);
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167 6
    public function get(CacheKey $key)
168
    {
169 6
        if ($this->isLocked($key)) {
170 3
            return null;
171
        }
172
173 3
        return $this->region->get($key);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function getMultiple(CollectionCacheEntry $collection)
180
    {
181
        if (array_filter(array_map([$this, 'isLocked'], $collection->identifiers))) {
182
            return null;
183
        }
184
185
        return $this->region->getMultiple($collection);
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 10
    public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
192
    {
193 10
        if ($this->isLocked($key, $lock)) {
194 1
            return false;
195
        }
196
197 10
        return $this->region->put($key, $entry);
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203 3
    public function evict(CacheKey $key)
204
    {
205 3
        if ($this->isLocked($key)) {
206 1
            @unlink($this->getLockFileName($key));
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...
207
        }
208
209 3
        return $this->region->evict($key);
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 3
    public function evictAll()
216
    {
217
        // The check below is necessary because on some platforms glob returns false
218
        // when nothing matched (even though no errors occurred)
219 3
        $filenames = glob(sprintf("%s/*.%s" , $this->directory, self::LOCK_EXTENSION));
220
221 3
        if ($filenames) {
222 1
            foreach ($filenames as $filename) {
223 1
                @unlink($filename);
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...
224
            }
225
        }
226
227 3
        return $this->region->evictAll();
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 6
    public function lock(CacheKey $key)
234
    {
235 6
        if ($this->isLocked($key)) {
236 1
            return null;
237
        }
238
239 5
        $lock     = Lock::createLockRead();
240 5
        $filename = $this->getLockFileName($key);
241
242 5
        if ( ! @file_put_contents($filename, $lock->value, LOCK_EX)) {
243
            return null;
244
        }
245 5
        chmod($filename, 0664);
246
247 5
        return $lock;
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253 2
    public function unlock(CacheKey $key, Lock $lock)
254
    {
255 2
        if ($this->isLocked($key, $lock)) {
256 1
            return false;
257
        }
258
259 1
        if ( ! @unlink($this->getLockFileName($key))) {
260
            return false;
261
        }
262
263 1
        return true;
264
    }
265
}
266