Completed
Push — master ( d10d94...1c2eb3 )
by Fabrice
01:53
created

FileLock::unLock()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 0
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of OpinHelpers.
5
 *     (c) Fabrice de Stefanis / https://github.com/fab2s/OpinHelpers
6
 * This source file is licensed under the MIT license which you will
7
 * find in the LICENSE file or at https://opensource.org/licenses/MIT
8
 */
9
10
namespace fab2s\OpinHelpers;
11
12
/**
13
 * Class FileLock
14
 */
15
class FileLock
16
{
17
    /**
18
     * external lock type actually locks "inputFile.lock"
19
     */
20
    const LOCK_EXTERNAL = 'external';
21
22
    /**
23
     * lock the file itself
24
     */
25
    const LOCK_SELF = 'self';
26
27
    /**
28
     * @var string
29
     */
30
    protected $lockType = self::LOCK_EXTERNAL;
31
32
    /**
33
     * max number of lock attempt
34
     *
35
     * @var int
36
     */
37
    protected $lockTry = 3;
38
39
    /**
40
     * The number of seconds to wait between lock attempts
41
     *
42
     * @var float
43
     */
44
    protected $lockWait = 0.1;
45
46
    /**
47
     * @var string
48
     */
49
    protected $file;
50
51
    /**
52
     * @var resource
53
     */
54
    protected $handle;
55
56
    /**
57
     * @var string fopen mode
58
     */
59
    protected $mode;
60
61
    /**
62
     * @var bool
63
     */
64
    protected $lockAcquired = false;
65
66
    /**
67
     * FileLock constructor.
68
     *
69
     * @param string $file
70
     * @param string $lockMethod
71
     * @param string $mode
72
     */
73
    public function __construct($file, $lockMethod, $mode = 'wb')
74
    {
75
        $fileDir = dirname($file);
76
        if (!($fileDir = realpath($fileDir))) {
77
            throw new \InvalidArgumentException('File path not valid');
78
        }
79
80
        if ($lockMethod === self::LOCK_SELF) {
81
            $this->lockType = self::LOCK_SELF;
82
            $this->mode     = $mode;
83
            $this->file     = $fileDir . '/' . basename($file);
84
85
            return;
86
        }
87
88
        $fileDir    = is_writeable($fileDir) ? $fileDir . '/' : sys_get_temp_dir() . '/' . sha1($fileDir) . '_';
89
        $this->file = $fileDir . basename($file) . '.lock';
90
    }
91
92
    /**
93
     * since there is no more auto unlocking
94
     */
95
    public function __destruct()
96
    {
97
        $this->unLock();
98
    }
99
100
    /**
101
     * @param string $file
102
     * @param string $mode
103
     * @param null   $maxTries 0|null for single non blocking attempt
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $lockWait is correct as it would always require null to be passed?
Loading history...
Documentation Bug introduced by
Are you sure the doc-type for parameter $maxTries is correct as it would always require null to be passed?
Loading history...
104
     *                         1 for a single blocking attempt
105
     *                         1-N Number of non blocking attempts
106
     * @param null   $lockWait
107
     *
108
     * @return bool|static
109
     */
110
    public static function open($file, $mode, $maxTries = null, $lockWait = null)
111
    {
112
        $instance = new static($file, self::LOCK_SELF, $mode);
113
        $maxTries = max(0, (int) $maxTries);
114
        if ($maxTries > 1) {
115
            $instance->setLockTry($maxTries);
116
            $lockWait = max(0, (float) $lockWait);
117
            if ($lockWait > 0) {
118
                $instance->setLockWait($lockWait);
119
            }
120
            $instance->obtainLock();
121
        } else {
122
            $instance->doLock((bool) $maxTries);
123
        }
124
125
        if ($instance->isLocked()) {
126
            return $instance;
127
        }
128
129
        $instance->unLock();
130
131
        return false;
132
    }
133
134
    /**
135
     * @return resource
136
     */
137
    public function getHandle()
138
    {
139
        return $this->handle;
140
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getLockType()
146
    {
147
        return $this->lockType;
148
    }
149
150
    /**
151
     * @return bool
152
     */
153
    public function isLocked()
154
    {
155
        return $this->lockAcquired;
156
    }
157
158
    /**
159
     * obtain a lock with retries
160
     *
161
     * @return $this
162
     */
163
    public function obtainLock()
164
    {
165
        $tries = 0;
166
        $uWait = (int) ($this->lockWait * 1000000);
167
        do {
168
            if ($this->doLock()->isLocked()) {
169
                return $this;
170
            }
171
172
            ++$tries;
173
            usleep($uWait);
174
        } while ($tries < $this->lockTry);
175
176
        return $this;
177
    }
178
179
    /**
180
     * @param bool $blocking
181
     *
182
     * @return $this
183
     */
184
    public function doLock($blocking = false)
185
    {
186
        if ($this->lockAcquired) {
187
            return $this;
188
        }
189
190
        $this->mode   = $this->mode ?: (is_file($this->file) ? 'rb' : 'wb');
191
        $this->handle = fopen($this->file, $this->mode) ?: null;
192
        if (
193
            $this->lockType === self::LOCK_EXTERNAL &&
194
            $this->mode === 'wb' &&
195
            !$this->handle
196
        ) {
197
            // if another process won the race at creating lock file
198
            $this->mode   = 'rb';
199
            $this->handle = fopen($this->file, $this->mode) ?: null;
200
        }
201
202
        $this->lockAcquired = $this->handle ? flock($this->handle, $blocking ? LOCK_EX : LOCK_EX | LOCK_NB) : false;
203
204
        if (!$this->lockAcquired) {
205
            $this->unLock();
206
        }
207
208
        return $this;
209
    }
210
211
    /**
212
     * release the lock
213
     */
214
    public function unLock()
215
    {
216
        if (is_resource($this->handle)) {
217
            fflush($this->handle);
218
            flock($this->handle, LOCK_UN);
219
            fclose($this->handle);
220
        }
221
222
        $this->lockAcquired = false;
223
        $this->handle       = null;
224
225
        return $this;
226
    }
227
228
    /**
229
     * @param int $number
230
     *
231
     * @return $this
232
     */
233
    public function setLockTry($number)
234
    {
235
        $this->lockTry = max(1, (int) $number);
236
237
        return $this;
238
    }
239
240
    /**
241
     * @param float $float
242
     *
243
     * @return $this
244
     */
245
    public function setLockWait($float)
246
    {
247
        $this->lockWait = max(0.001, $float);
248
249
        return $this;
250
    }
251
}
252