Passed
Push — master ( 78f1df...3f31a9 )
by Cesar
01:53
created

Rotation::runCompress()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 14
rs 10
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 $thenCallback = null;
21
22
    public function __construct(array $options = [])
23
    {
24
        $this->processor = new RotativeProcessor();
25
26
        $this->methodsOptionables([
27
            'compress',
28
            'minSize',
29
            'files',
30
            'then',
31
            'catch',
32
        ]);
33
34
        $this->options($options);
35
    }
36
37
    /**
38
     * Log files are rotated count times before being removed.
39
     */
40
    public function files(int $count): self
41
    {
42
        $this->processor->files($count);
43
44
        return $this;
45
    }
46
47
    /**
48
     * Old versions of log files are compressed.
49
     */
50
    public function compress(bool $compress = true): self
51
    {
52
        $this->_compress = $compress;
53
54
        if ($compress) {
55
            $this->processor->addExtension('gz');
56
        } else {
57
            $this->processor->removeExtention('gz');
58
        }
59
60
        return $this;
61
    }
62
63
    /**
64
     * Log files are rotated when they grow bigger than size bytes.
65
     */
66
    public function minSize(int $bytes): self
67
    {
68
        $this->_minSize = $bytes;
69
70
        return $this;
71
    }
72
73
    /**
74
     * Function that will be executed when the rotation is successful.
75
     * The first argument will be the name of the destination file and
76
     * the second the name of the rotated file.
77
     */
78
    public function then(callable $callable): self
79
    {
80
        $this->thenCallback = $callable;
81
82
        return $this;
83
    }
84
85
    /**
86
     * Rotate file.
87
     *
88
     * @return bool true if rotated was successful
89
     */
90
    public function rotate(string $filename): bool
91
    {
92
        if (!$this->canRotate($filename)) {
93
            return false;
94
        }
95
96
        $filenameRotated = $this->runProcessor(
97
            $filename,
98
            $this->moveContentToTempFile($filename)
99
        );
100
101
        $filenameRotated = is_null($filenameRotated)
102
            ? $filenameRotated
103
            : $this->runCompress($filenameRotated);
104
105
        $this->sucessfull($filename, $filenameRotated);
106
107
        return !empty($filenameRotated);
108
    }
109
110
    /**
111
     * Run processor.
112
     */
113
    private function runProcessor(string $filenameSource, ?string $filenameTarget): ?string
114
    {
115
        $this->initProcessorFile($filenameSource);
116
117
        if (!$filenameTarget) {
118
            return null;
119
        }
120
121
        return $this->processor->handler($filenameTarget);
122
    }
123
124
    private function runCompress(string $filename): ?string
125
    {
126
        if (!$this->_compress) {
127
            return $filename;
128
        }
129
130
        $gz = new Gz();
131
132
        try {
133
            return $gz->handler($filename);
134
        } catch (Exception $error) {
135
            $this->exception($error);
136
137
            return null;
138
        }
139
    }
140
141
    private function sucessfull(string $filenameSource, ?string $filenameRotated): void
142
    {
143
        if (is_null($this->thenCallback) || is_null($filenameRotated)) {
144
            return;
145
        }
146
147
        call_user_func($this->thenCallback, $filenameRotated, $filenameSource);
148
    }
149
150
    /**
151
     * check if file need rotate.
152
     */
153
    private function canRotate(string $filename): bool
154
    {
155
        if (!$this->fileIsValid($filename)) {
156
            $this->exception(
157
                new Exception(sprintf('the file %s not is valid.', $filename), 10)
158
            );
159
160
            return false;
161
        }
162
163
        return filesize($filename) > ($this->_minSize > 0 ? $this->_minSize : 0);
164
    }
165
166
    /**
167
     * Set original File to processor.
168
     */
169
    private function initProcessorFile(string $filename): void
170
    {
171
        $this->processor->setFilenameSource($filename);
172
    }
173
174
    /**
175
     * check if file is valid to rotate.
176
     */
177
    private function fileIsValid(?string $filename): bool
178
    {
179
        return $filename && is_file($filename) && is_writable($filename);
180
    }
181
182
    /**
183
     * move data to temp file and truncate.
184
     */
185
    private function moveContentToTempFile(string $filename): ?string
186
    {
187
        clearstatcache();
188
189
        $filenameTarget = tempnam(dirname($filename), 'LOG');
190
191
        $fd = fopen($filename, 'r+');
192
193
        if ($fd === false) {
194
            $this->exception(
195
                new Exception(sprintf('the file %s not can open.', $filename), 20)
196
            );
197
198
            return null;
199
        }
200
201
        if (!flock($fd, LOCK_EX)) {
202
            fclose($fd);
203
204
            $this->exception(
205
                new Exception(sprintf('the file %s not can lock.', $filename), 21)
206
            );
207
208
            return null;
209
        }
210
211
        if (!copy($filename, $filenameTarget)) {
212
            fclose($fd);
213
214
            $this->exception(
215
                new Exception(
216
                    sprintf('the file %s not can copy to temp file %s.', $filename, $filenameTarget),
217
                    22
218
                )
219
            );
220
221
            return null;
222
        }
223
224
        if (!ftruncate($fd, 0)) {
225
            fclose($fd);
226
227
            unlink($filenameTarget);
228
229
            $this->exception(
230
                new Exception(sprintf('the file %s not can truncate.', $filename), 23)
231
            );
232
233
            return null;
234
        }
235
236
        flock($fd, LOCK_UN);
237
238
        fflush($fd);
239
240
        fclose($fd);
241
242
        return $filenameTarget;
243
    }
244
}
245