Test Setup Failed
Push — master ( 2a55ac...fe9d21 )
by Никита
04:10
created

FileMutex::is_acquired()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php declare(strict_types=1);
2
3
namespace NokitaKaze\Mutex;
4
5
class FileMutex implements MutexInterface
6
{
7
    /**
8
     * @var string Название файла, на котором будет работать мьютекс
9
     */
10
    public $filename = '';
11
    protected $_mutex_type;
12
    /**
13
     * @var string|null Название мьютекса, должно состоять из валидных для имени файлов символов
14
     */
15
    public $_mutex_name;
16
    /**
17
     * @var string|null Место, где расположен файл.
18
     *
19
     * Не используется в логике класса
20
     */
21
    protected $_mutex_folder;
22
    /**
23
     * @var double|null Время, когда мьютекс был получен
24
     */
25
    protected $_lock_acquired_time = null;
26
    /**
27
     * @var boolean Текущее состояние мьютекса
28
     */
29
    protected $_lock_acquired = false;
30
    /**
31
     * @var resource|false Файл, открывающийся через fopen
32
     */
33
    protected $_file_handler = false;
34
    /**
35
     * @var boolean Удалять файл при анлоке
36
     *
37
     * В данный момент это unsafe поведение, из-за немандаторного доступа к файлам из PHP
38
     */
39
    protected $_delete_on_release = false;
40
41 5
    /**
42
     * @param MutexSettings|array $settings
43
     */
44
    public function __construct($settings)
45 5
    {
46 5
        /**
47 1
         * @var MutexSettings $settings
48 1
         */
49 5
        $settings = (object)$settings;
50 1
        if (!isset($settings->type)) {
51 1
            $settings->type = null;
52
        }
53 5
        if (!isset($settings->folder)) {
54 5
            $settings->folder = null;
55 5
        }
56 5
57 1
        $this->_mutex_type = is_null($settings->type) ? self::SERVER : $settings->type;
58 1
        $this->_mutex_folder = is_null($settings->folder) ? sys_get_temp_dir() : $settings->folder;
59
        $this->_mutex_name = $settings->name;
60 5
        if (isset($settings->delete_on_release)) {
61 5
            $this->_delete_on_release = $settings->delete_on_release;
62 1
        }
63 5
64 1
        $prefix = '';
65 5
        if (isset($settings->prefix)) {
66 1
            $prefix = $settings->prefix;
67 1
        } elseif ($this->_mutex_type == self::DOMAIN) {
68 5
            $prefix = hash('sha512', self::getDomainString()) . '_';
69 5
        } elseif ($this->_mutex_type == self::DIRECTORY) {
70
            $prefix = hash('sha512', strtolower(self::getDirectoryString())) . '_';
71 5
        }
72 5
        $this->filename = $this->_mutex_folder . DIRECTORY_SEPARATOR . 'smartmutex_' . $prefix . $this->_mutex_name . '.lock';
73 5
    }
74
75
    public function __destruct()
76
    {
77
        $this->release_lock();
78 3
    }
79 3
80 1
    /**
81 3
     * @return string
82 3
     */
83
    public static function getDomainString(): string
84
    {
85
        if (isset($_SERVER['HTTP_HOST']) and ($_SERVER['HTTP_HOST'] != '')) {
86
            return $_SERVER['HTTP_HOST'];
87
        } elseif (gethostname() != '') {
88
            return gethostname();
89
        } elseif (isset($_SERVER['SCRIPT_NAME'])) {
90
            return $_SERVER['SCRIPT_NAME'];
91
        } else {
92
            return 'none';
93 2
        }
94 2
    }
95 1
96 2
    /**
97 2
     * @return string
98 1
     */
99 1
    public static function getDirectoryString(): string
100
    {
101 1
        if (isset($_SERVER['DOCUMENT_ROOT']) and ($_SERVER['DOCUMENT_ROOT'] != '')) {
102
            return $_SERVER['DOCUMENT_ROOT'];
103
        } elseif (isset($_SERVER['PWD']) and ($_SERVER['PWD'] != '')) {
104
            return $_SERVER['PWD'];
105
        } elseif (isset($_SERVER['SCRIPT_NAME'])) {
106
            return dirname($_SERVER['SCRIPT_NAME']);
107
        } else {
108
            return 'none';
109
        }
110 1
    }
111 1
112 1
    /**
113
     * @return boolean
114 1
     *
115 1
     * @throws MutexException
116 1
     */
117 1
    public function is_free(): bool
118 1
    {
119 1
        if ($this->_lock_acquired) {
120 1
            return false;
121 1
        }
122
        if (!file_exists(dirname($this->filename))) {
123
            throw new MutexException('Folder "' . dirname($this->filename) . '" does not exist', 1);
124 1
        } elseif (!is_dir(dirname($this->filename))) {
125 1
            throw new MutexException('Folder "' . dirname($this->filename) . '" does not exist', 4);
126 1
        } elseif (!is_writable(dirname($this->filename))) {
127 1
            throw new MutexException('Folder "' . dirname($this->filename) . '" is not writable', 2);
128
        } elseif (file_exists($this->filename) and !is_writable($this->filename)) {
129 1
            throw new MutexException('File "' . $this->filename . '" is not writable', 3);
130
        }
131
132
        $fo = fopen($this->filename, 'ab');
133
        $result = flock($fo, LOCK_EX | LOCK_NB);
134
        flock($fo, LOCK_UN);
135
        fclose($fo);
136
137
        return $result;
138 4
    }
139 4
140 4
    /**
141 3
     * @param double|integer $time
142
     *
143
     * @return bool
144 4
     * @throws MutexException
145 4
     */
146
    public function get_lock(float $time = -1): bool
147 4
    {
148 1
        $tmp_time = microtime(true);
149 4
        if ($this->_lock_acquired) {
150 1
            return true;
151
        }
152
153
        self::create_folders_in_path(dirname($this->filename));
154 4
        if (!is_dir(dirname($this->filename))) {
155 4
            throw new MutexException('Folder "' . dirname($this->filename) . '" is not a folder', 4);
156
        } elseif (!is_writable(dirname($this->filename))) {
157 4
            throw new MutexException('Folder "' . dirname($this->filename) . '" is not writable', 2);
158
        } elseif (file_exists($this->filename) and !is_writable($this->filename)) {
159
            throw new MutexException('File "' . $this->filename . '" is not writable', 3);
160
        }
161 4
162
        // Открываем файл
163
        $this->_file_handler = fopen($this->filename, file_exists($this->filename) ? 'ab' : 'wb');
164
        while (($this->_file_handler === false) and (
165
                ($tmp_time + $time >= microtime(true)) or ($time == -1)
166 4
            )) {
167 4
            // Active locks. Yes, this is programming language we have
168 4
            usleep(10000);
169
            $this->_file_handler = fopen($this->filename, 'ab');
170 1
        }
171 1
        if ($this->_file_handler === false) {
172 1
            return false;
173 4
        }
174
175
        // Блочим файл
176
        if ($time >= 0) {
177 4
            $result = flock($this->_file_handler, LOCK_EX | LOCK_NB);
178 4
            while (!$result and ($tmp_time + $time >= microtime(true))) {
179
                usleep(10000);
180 4
                $result = flock($this->_file_handler, LOCK_EX | LOCK_NB);
181 4
            }
182 4
        } else {
183 4
            $result = flock($this->_file_handler, LOCK_EX);
184 1
        }
185 1
186
        if ($result) {
187
            $this->_lock_acquired_time = microtime(true);
188 4
            fwrite($this->_file_handler, self::getpid() . "\n" . microtime(true) . "\n" . self::getuid() . "\n\n");
189
            fflush($this->_file_handler);
190
            $this->_lock_acquired = true;
191 5
        } else {
192 5
            fclose($this->_file_handler);
193 5
            $this->_file_handler = false;
194
        }
195 4
196 4
        return $result;
197 4
    }
198 4
199 4
    public function release_lock()
200 4
    {
201 4
        if (!$this->_lock_acquired) {
202
            return;
203 4
        }
204 2
        if (is_resource($this->_file_handler)) {// @hint По неизвестным причинам это не always true condition
205 2
            flock($this->_file_handler, LOCK_UN);
206 4
            fclose($this->_file_handler);
207
        }
208
        $this->_file_handler = false;
209
        $this->_lock_acquired = false;
210
        $this->_lock_acquired_time = null;
211 1
212 1
        if ($this->_delete_on_release and file_exists($this->filename)) {
213
            @unlink($this->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...
214
        }
215
    }
216
217
    /**
218 1
     * @return double|null
219 1
     */
220
    public function get_acquired_time(): ?float
221
    {
222
        return $this->_lock_acquired_time;
223
    }
224
225 2
    /**
226 2
     * @return string
227
     */
228
    public function get_mutex_name(): string
229
    {
230
        return $this->_mutex_name;
231
    }
232 4
233 4
    /**
234
     * @return boolean
235
     */
236
    public function get_delete_on_release(): bool
237
    {
238
        return $this->_delete_on_release;
239 2
    }
240 2
241 2
    /**
242
     * @return boolean
243
     */
244
    public function is_acquired(): bool
245 2
    {
246 2
        return $this->_lock_acquired;
247
    }
248
249
    /**
250
     * @return string
251
     */
252
    public static function get_last_php_error_as_string(): string
253
    {
254 20
        $error = error_get_last();
255 20
        if (empty($error)) {
256
            return '';
257 20
        }
258 20
259 20
        return sprintf(
260
            '%s%s',
261 20
            $error['message'],
262 20
            isset($error['code']) ? ' (#' . $error['code'] . ')' : ''
263 20
        );
264
    }
265 20
266
    /**
267
     * @param string $path
268
     *
269
     * @return string
270
     */
271
    public static function sanify_path(string $path): string
272
    {
273 11
        $path = rtrim(str_replace('\\', '/', $path), '/') . '/';
274 11
        do {
275 11
            $old_path = $path;
276 11
            $path = str_replace('//', '/', str_replace('/./', '/', $path));
277
        } while ($path != $old_path);
278 11
        do {
279
            $path = preg_replace('_/([^/]+?)/\\.\\./_', '/', $path, -1, $count);
280 11
        } while ($count > 0);
281 1
        $path = rtrim($path, '/');
282
283 1
        return $path;
284
    }
285 11
286 2
    /**
287
     * @param string $path
288 11
     * @param int    $permissions
289 9
     *
290
     * @throws \NokitaKaze\Mutex\MutexException
291
     */
292
    public static function create_folders_in_path(string $path, $permissions = 7 << 6)
293
    {
294
        if (file_exists($path)) {
295
            if (!is_dir($path)) {
296
                throw new MutexException($path . ' is not a directory');
297
            }
298
299
            return;
300
        }
301
302
        if (!@mkdir($path, $permissions, true)) {
303
            throw new MutexException('Can not create folder: ' . self::get_last_php_error_as_string());
304
        }
305
    }
306
307
    public static function getpid(): int
308
    {
309
        return function_exists('posix_getpid') ? posix_getpid() : getmypid();
310
    }
311
312
    public static function getuid(): int
313
    {
314
        return function_exists('posix_getuid') ? posix_getuid() : getmyuid();
315
    }
316
}
317