Passed
Push — development ( fde271...e8788a )
by Peter
01:51
created

processEntry()   B

Complexity

Conditions 10
Paths 97

Size

Total Lines 38
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 10
eloc 25
c 1
b 1
f 0
nc 97
nop 1
dl 0
loc 38
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#!/usr/bin/env php
2
<?php
3
declare(strict_types=1);
4
5
/**
6
 * Scans the application's PHP error logfile for entries and sends them by email to the configured logmessage receivers.
7
 * If no receivers are configured mail is sent to the system user running the script. Processed log entries are removed
8
 * from the file.
9
 */
10
use rosasurfer\ministruts\Application;
11
use rosasurfer\ministruts\config\ConfigInterface as Config;
12
use rosasurfer\ministruts\core\assert\Assert;
13
use rosasurfer\ministruts\util\PHPMailer;
14
use rosasurfer\ministruts\util\PHP;
15
16
use function rosasurfer\ministruts\echof;
17
use function rosasurfer\ministruts\realpath;
18
use function rosasurfer\ministruts\stderr;
19
use function rosasurfer\ministruts\strStartsWith;
20
21
use const rosasurfer\ministruts\CLI;
22
use const rosasurfer\ministruts\NL;
23
use const rosasurfer\ministruts\WINDOWS;
24
25
require(__DIR__.'/../app/init.php');
26
27
if (!CLI) {
28
    stderr('error: This script must be executed from the command line.');
29
    exit(1);
30
}
31
32
33
// --- Configuration --------------------------------------------------------------------------------------------------------
34
35
36
set_time_limit(0);
37
$quiet = false;                                             // e.g. for CRON
38
39
40
// --- Parse and validate command line arguments ----------------------------------------------------------------------------
41
42
43
/** @var string[] $args */
44
$args = array_slice($_SERVER['argv'], 1);
45
46
foreach ($args as $i => $arg) {
47
    if ($arg == '-h') { help(); exit(0);                           }
48
    if ($arg == '-q') { $quiet = true; unset($args[$i]); continue; }
49
50
    $msg = "invalid argument: $arg";
51
52
    if ($quiet) stderr($msg);
53
    else        help($msg);
54
    exit(1);
55
}
56
57
58
// --- Start ----------------------------------------------------------------------------------------------------------------
59
60
61
// define the location of the error log
62
$errorLog = ini_get('error_log');
63
if (empty($errorLog) || $errorLog=='syslog') {              // errors are logged elsewhere
64
    if (!$quiet) {
65
        if (empty($errorLog)) $where = 'STDERR';
66
        else                  $where = WINDOWS ? 'event log':'syslog';
67
        echof("errors are logged elsewhere ($where)");
68
    }
69
    exit(0);
70
}
71
72
// check log file for existence and process it
73
if (!is_file    ($errorLog)) { $quiet || echof('error log empty: '       .$errorLog); exit(0); }
74
if (!is_writable($errorLog)) {          stderr('cannot access log file: '.$errorLog); exit(1); }
75
$errorLog = realpath($errorLog);
76
77
// rename the file; we don't want to lock it cause doing so could block the main app
78
$tempName = tempnam(dirname($errorLog), basename($errorLog).'.');
79
if (!rename($errorLog, $tempName)) {
80
    stderr('cannot rename log file: '  .$errorLog);
81
    exit(1);
82
}
83
84
// read the log file line by line
85
PHP::ini_set('auto_detect_line_endings', 1);
86
$hFile = fopen($tempName, 'rb');
87
$line  = $entry = '';
88
$i = 0;
89
while (($line=fgets($hFile)) !== false) {
90
    $i++;
91
    $line = trim($line, "\r\n");                // PHP doesn't correctly handle EOL_NETSCAPE which is error_log() standard on Windows
92
    if (strStartsWith($line, '[')) {            // lines starting with a bracket "[" are considered the start of an entry
93
        processEntry($entry);
94
        $entry = '';
95
    }
96
    $entry .= $line.NL;
97
}
98
processEntry($entry);                           // process the last entry (if any)
99
100
// delete the processed file
101
fclose($hFile);
102
unlink($tempName);
103
104
exit(0);
105
106
107
// --- Functions ------------------------------------------------------------------------------------------------------------
108
109
110
/**
111
 * Send a single log entry to the defined error log receivers. The first line is sent as the mail subject and the full
112
 * log entry as the mail body.
113
 *
114
 * @param  string $entry - a single log entry
115
 *
116
 * @return void
117
 */
118
function processEntry($entry) {
119
    Assert::string($entry);
120
    $entry = trim($entry);
121
    if (!strlen($entry)) return;
122
123
    /** @var Config $config */
124
    $config = Application::getDi()['config'];                       // @phpstan-ignore offsetAccess.notFound
125
    $receivers = [];
126
127
    foreach (explode(',', $config->get('log.mail.receiver', '')) as $receiver) {
128
        if ($receiver = trim($receiver)) {
129
            if (filter_var($receiver, FILTER_VALIDATE_EMAIL)) {     // silently skip invalid receiver addresses
130
                $receivers[] = $receiver;
131
            }
132
        }
133
    }                                                               // without receivers mail is sent to the system user
134
    !$receivers && $receivers[] = strtolower(get_current_user().'@localhost');
135
136
    $subject = (string)strtok($entry, "\r\n");                      // that's CR or LF, not CRLF
137
    $message = $entry;
138
    $sender  = null;
139
    $headers = [];
140
141
    static $mailer; if (!$mailer) {
142
        $options = [];
143
        if (strlen($name = $config->get('log.mail.profile', ''))) {
144
            $options = $config->get('mail.profile.'.$name, []);
145
            $sender  = $config->get('mail.profile.'.$name.'.from', null);
146
            $headers = $config->get('mail.profile.'.$name.'.headers', []);
147
        }
148
        $mailer = new PHPMailer($options);
149
    }
150
151
    global $quiet;
152
    $quiet || echof(substr($subject, 0, 80).'...');
153
154
    foreach ($receivers as $receiver) {
155
        $mailer->sendMail($sender, $receiver, $subject, $message, $headers);
156
    }
157
}
158
159
160
/**
161
 * Syntax helper.
162
 *
163
 * @param  ?string $message [optional] - additional message to display (default: none)
164
 *
165
 * @return void
166
 */
167
function help($message = null) {
168
    if (isset($message)) {
169
        echo $message.NL;
170
    }
171
    $self = basename($_SERVER['PHP_SELF']);
172
    echo <<<HELP
173
174
 Syntax:  $self [options]
175
176
 Options:  -q   Quiet mode. Suppress status messages but not errors (for scripted execution, e.g. by CRON).
177
           -h   This help screen.
178
179
180
HELP;
181
}
182