Passed
Branch master (838456)
by Никита
04:22 queued 02:21
created

FileMutex::create_folders_in_path()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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