Passed
Push — master ( 3b5ae6...0cef10 )
by Никита
01:57
created

FileMutex::get_last_php_error_as_string()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

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