Completed
Push — master ( bb9ef6...39930e )
by Fabrice
02:57
created

FileLock::getLockHandle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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 $lockMethod = self::LOCK_EXTERNAL;
31
32
    /**
33
     * max number of lock attempt
34
     *
35
     * @var int
36
     */
37
    protected $maxTry = 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 $lockFile;
50
51
    /**
52
     * @var resource
53
     */
54
    protected $lockHandle;
55
56
    /**
57
     * @var string fopen mode
58
     */
59
    protected $lockMode;
60
61
    /**
62
     * @var bool
63
     */
64
    protected $lockAcquired = false;
65
66
    /**
67
     * FileLock constructor.
68
     *
69
     * @param string|resource $file
70
     * @param string          $lockMethod
71
     * @param string          $mode
72
     */
73
    public function __construct($file, $lockMethod, $mode = 'wb')
74
    {
75
        $fileDir = dirname($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type resource; however, parameter $path of dirname() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

75
        $fileDir = dirname(/** @scrutinizer ignore-type */ $file);
Loading history...
76
        if (!($fileDir = realpath($fileDir))) {
77
            throw new \InvalidArgumentException('File path not valid');
78
        }
79
80
        if ($lockMethod === self::LOCK_SELF) {
81
            $this->lockMethod = self::LOCK_SELF;
82
            $this->lockMode   = $mode;
83
            $this->lockFile   = $fileDir . '/' . basename($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type resource; however, parameter $path of basename() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
            $this->lockFile   = $fileDir . '/' . basename(/** @scrutinizer ignore-type */ $file);
Loading history...
84
85
            return;
86
        }
87
88
        $fileDir        = is_writeable($fileDir) ? $fileDir . '/' : sys_get_temp_dir() . '/' . sha1($fileDir) . '_';
89
        $this->lockFile = $fileDir . basename($file) . '.lock';
90
    }
91
92
    /**
93
     * since there is no more auto unlocking
94
     */
95
    public function __destruct()
96
    {
97
        $this->releaseLock();
98
    }
99
100
    /**
101
     * @return resource
102
     */
103
    public function getLockHandle()
104
    {
105
        return $this->lockHandle;
106
    }
107
108
    /**
109
     * @return string
110
     */
111
    public function getLockMethod()
112
    {
113
        return $this->lockMethod;
114
    }
115
116
    /**
117
     * @return bool
118
     */
119
    public function isLocked()
120
    {
121
        return $this->lockAcquired;
122
    }
123
124
    /**
125
     * obtain a lock with retries
126
     *
127
     * @return $this
128
     */
129
    public function obtainLock()
130
    {
131
        $tries = 0;
132
        $uWait = (int) ($this->lockWait * 1000000);
133
        do {
134
            if ($this->setLock()->isLocked()) {
135
                return $this;
136
            }
137
138
            ++$tries;
139
            usleep($uWait);
140
        } while ($tries < $this->maxTry);
141
142
        return $this;
143
    }
144
145
    /**
146
     * @param bool $blocking
147
     *
148
     * @return $this
149
     */
150
    public function setLock($blocking = false)
151
    {
152
        if ($this->lockAcquired) {
153
            return $this;
154
        }
155
156
        $this->lockMode   = $this->lockMode ?: (is_file($this->lockFile) ? 'rb' : 'wb');
157
        $this->lockHandle = fopen($this->lockFile, $this->lockMode) ?: null;
158
        if (
159
            $this->lockMethod === self::LOCK_EXTERNAL &&
160
            $this->lockMode === 'wb' &&
161
            $this->lockHandle === false
0 ignored issues
show
introduced by
The condition $this->lockHandle === false is always false.
Loading history...
162
        ) {
163
            // if another process won the race at creating lock file
164
            $this->lockMode   = 'rb';
165
            $this->lockHandle = fopen($this->lockFile, $this->lockMode) ?: null;
166
        }
167
168
        $this->lockAcquired = $this->lockHandle ? flock($this->lockHandle, $blocking ? LOCK_EX : LOCK_EX | LOCK_NB) : false;
169
170
        return $this;
171
    }
172
173
    /**
174
     * release the lock
175
     */
176
    public function releaseLock()
177
    {
178
        if (is_resource($this->lockHandle)) {
179
            fflush($this->lockHandle);
180
            flock($this->lockHandle, LOCK_UN);
181
            fclose($this->lockHandle);
182
        }
183
184
        $this->lockAcquired = false;
185
        $this->lockHandle   = null;
186
187
        return $this;
188
    }
189
190
    /**
191
     * @param int $number
192
     *
193
     * @return $this
194
     */
195
    public function setMaxTry($number)
196
    {
197
        $this->maxTry = max(1, (int) $number);
198
199
        return $this;
200
    }
201
202
    /**
203
     * @param float $float
204
     *
205
     * @return $this
206
     */
207
    public function setLockWait($float)
208
    {
209
        $this->lockWait = max(0.001, $float);
210
211
        return $this;
212
    }
213
}
214