Passed
Push — master ( 723774...35250d )
by Cesar
02:23
created

Rotation::move()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 22
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace Cesargb\Log;
4
5
use Cesargb\Log\Compress\Gz;
6
use Cesargb\Log\Processors\RotativeProcessor;
7
use Exception;
8
9
class Rotation
10
{
11
    use Optionable;
12
    use ErrorHandler;
13
14
    private RotativeProcessor $processor;
15
16
    private bool $_compress = false;
17
18
    private int $_minSize = 0;
19
20
    private bool $_truncate = false;
21
22
    /**
23
     * @param mixed[] $options
24
     */
25
    public function __construct(array $options = [])
26
    {
27
        $this->processor = new RotativeProcessor();
28
29
        $this->methodsOptionables([
30
            'compress',
31
            'truncate',
32
            'minSize',
33
            'files',
34
            'then',
35
            'catch',
36
            'finally',
37
        ]);
38
39
        $this->options($options);
40
    }
41
42
    /**
43
     * Log files are rotated count times before being removed.
44
     */
45
    public function files(int $count): self
46
    {
47
        $this->processor->files($count);
48
49
        return $this;
50
    }
51
52
    /**
53
     * Old versions of log files are compressed.
54
     */
55
    public function compress(bool $compress = true): self
56
    {
57
        $this->_compress = $compress;
58
59
        if ($compress) {
60
            $this->processor->addExtension('gz');
61
        } else {
62
            $this->processor->removeExtention('gz');
63
        }
64
65
        return $this;
66
    }
67
68
    /**
69
     * Truncate the original log file in place after creating a copy, instead of
70
     * moving the old log file.
71
     *
72
     * It can be used when some program cannot be told to close its logfile and
73
     * thus might continue writing (appending) to the previous log file forever.
74
     */
75
    public function truncate(bool $truncate = true): self
76
    {
77
        $this->_truncate = $truncate;
78
79
        return $this;
80
    }
81
82
    /**
83
     * Log files are rotated when they grow bigger than size bytes.
84
     */
85
    public function minSize(int $bytes): self
86
    {
87
        $this->_minSize = $bytes;
88
89
        return $this;
90
    }
91
92
    /**
93
     * Rotate file.
94
     *
95
     * @return bool true if rotated was successful
96
     */
97
    public function rotate(string $filename): bool
98
    {
99
        $this->setFilename($filename);
100
101
        if (!$this->canRotate($filename)) {
102
            return false;
103
        }
104
105
        $fileTemporary = $this->_truncate
106
            ? $this->copyAndTruncate($filename)
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->copyAndTruncate($filename) targeting Cesargb\Log\Rotation::copyAndTruncate() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
107
            : $this->move($filename);
108
109
        if (is_null($fileTemporary)) {
110
            return false;
111
        }
112
113
        $fileTarget = $this->runProcessor(
114
            $filename,
115
            $fileTemporary
116
        );
117
118
        if (is_null($fileTarget)) {
119
            return false;
120
        }
121
122
        $fileTarget = $this->runCompress($fileTarget);
123
124
        $this->sucessfull($filename, $fileTarget);
125
126
        return true;
127
    }
128
129
    /**
130
     * Run processor.
131
     */
132
    private function runProcessor(string $filenameSource, ?string $filenameTarget): ?string
133
    {
134
        $this->initProcessorFile($filenameSource);
135
136
        if (!$filenameTarget) {
137
            return null;
138
        }
139
140
        return $this->processor->handler($filenameTarget);
141
    }
142
143
    private function runCompress(string $filename): ?string
144
    {
145
        if (!$this->_compress) {
146
            return $filename;
147
        }
148
149
        $gz = new Gz();
150
151
        try {
152
            return $gz->handler($filename);
153
        } catch (Exception $error) {
154
            $this->exception($error);
155
156
            return null;
157
        }
158
    }
159
160
    /**
161
     * check if file need rotate.
162
     */
163
    private function canRotate(string $filename): bool
164
    {
165
        if (!file_exists($filename)) {
166
            $this->finished(sprintf('the file %s not exists.', $filename), $filename);
167
168
            return false;
169
        }
170
171
        if (!$this->fileIsValid($filename)) {
172
            $this->exception(
173
                new Exception(sprintf('the file %s not is valid.', $filename), 10)
174
            );
175
176
            return false;
177
        }
178
179
        return filesize($filename) > ($this->_minSize > 0 ? $this->_minSize : 0);
180
    }
181
182
    /**
183
     * Set original File to processor.
184
     */
185
    private function initProcessorFile(string $filename): void
186
    {
187
        $this->processor->setFilenameSource($filename);
188
    }
189
190
    /**
191
     * check if file is valid to rotate.
192
     */
193
    private function fileIsValid(string $filename): bool
194
    {
195
        return is_file($filename) && is_writable($filename);
196
    }
197
198
    /**
199
     * copy data to temp file and truncate.
200
     */
201
    private function copyAndTruncate(string $filename): ?string
202
    {
203
        clearstatcache();
204
205
        $filenameTarget = $this->getTempFilename(dirname($filename));
206
207
        if (!$filenameTarget) {
208
            return null;
209
        }
210
211
        $fd = $this->openFileWithLock($filename);
212
213
        if (!$fd) {
0 ignored issues
show
introduced by
$fd is of type null|resource, thus it always evaluated to false.
Loading history...
214
            return null;
215
        }
216
217
        if (!copy($filename, $filenameTarget)) {
218
            fclose($fd);
219
220
            $this->exception(
221
                new Exception(
222
                    sprintf('the file %s not can copy to temp file %s.', $filename, $filenameTarget),
223
                    22
224
                )
225
            );
226
227
            return null;
228
        }
229
230
        if (!ftruncate($fd, 0)) {
231
            fclose($fd);
232
233
            unlink($filenameTarget);
234
235
            $this->exception(
236
                new Exception(sprintf('the file %s not can truncate.', $filename), 23)
237
            );
238
239
            return null;
240
        }
241
242
        flock($fd, LOCK_UN);
243
244
        fflush($fd);
245
246
        fclose($fd);
247
248
        return $filenameTarget;
249
    }
250
251
    private function move(string $filename): ?string
252
    {
253
        clearstatcache();
254
255
        $filenameTarget = $this->getTempFilename(dirname($filename));
256
257
        if (!$filenameTarget) {
258
            return null;
259
        }
260
261
        if (!rename($filename, $filenameTarget)) {
262
            $this->exception(
263
                new Exception(
264
                    sprintf('the file %s not can move to temp file %s.', $filename, $filenameTarget),
265
                    22
266
                )
267
            );
268
269
            return null;
270
        }
271
272
        return $filenameTarget;
273
    }
274
275
    private function getTempFilename(string $path): ?string
276
    {
277
        $filename = tempnam($path, 'LOG');
278
279
        if ($filename === false) {
280
            $this->exception(
281
                new Exception(sprintf('the file %s not can create temp file.', $path), 19)
282
            );
283
284
            return null;
285
        }
286
287
        return $filename;
288
    }
289
290
    /**
291
     * @return null|resource
292
     */
293
    private function openFileWithLock(string $filename)
294
    {
295
        $fd = fopen($filename, 'r+');
296
297
        if ($fd === false) {
298
            $this->exception(
299
                new Exception(sprintf('the file %s not can open.', $filename), 20)
300
            );
301
302
            return null;
303
        }
304
305
        if (!flock($fd, LOCK_EX)) {
306
            fclose($fd);
307
308
            $this->exception(
309
                new Exception(sprintf('the file %s not can lock.', $filename), 21)
310
            );
311
312
            return null;
313
        }
314
315
        return $fd;
316
    }
317
}
318