Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
 
3
/*
4
 * This file is part of the Monolog package.
5
 *
6
 * (c) Jordi Boggiano <j.boggiano@seld.be>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Monolog\Handler;
13
 
14
use Monolog\Logger;
15
 
16
/**
17
 * Stores to STDIN of any process, specified by a command.
18
 *
19
 * Usage example:
20
 * <pre>
21
 * $log = new Logger('myLogger');
22
 * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
23
 * </pre>
24
 *
25
 * @author Kolja Zuelsdorf <koljaz@web.de>
26
 */
27
class ProcessHandler extends AbstractProcessingHandler
28
{
29
    /**
30
     * Holds the process to receive data on its STDIN.
31
     *
32
     * @var resource|bool|null
33
     */
34
    private $process;
35
 
36
    /**
37
     * @var string
38
     */
39
    private $command;
40
 
41
    /**
42
     * @var string|null
43
     */
44
    private $cwd;
45
 
46
    /**
47
     * @var resource[]
48
     */
49
    private $pipes = [];
50
 
51
    /**
52
     * @var array<int, string[]>
53
     */
54
    protected const DESCRIPTOR_SPEC = [
55
 
56
        1 => ['pipe', 'w'],  // STDOUT is a pipe that the child will write to
57
        2 => ['pipe', 'w'],  // STDERR is a pipe to catch the any errors
58
    ];
59
 
60
    /**
61
     * @param  string                    $command Command for the process to start. Absolute paths are recommended,
62
     *                                            especially if you do not use the $cwd parameter.
63
     * @param  string|null               $cwd     "Current working directory" (CWD) for the process to be executed in.
64
     * @throws \InvalidArgumentException
65
     */
66
    public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null)
67
    {
68
        if ($command === '') {
69
            throw new \InvalidArgumentException('The command argument must be a non-empty string.');
70
        }
71
        if ($cwd === '') {
72
            throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.');
73
        }
74
 
75
        parent::__construct($level, $bubble);
76
 
77
        $this->command = $command;
78
        $this->cwd = $cwd;
79
    }
80
 
81
    /**
82
     * Writes the record down to the log of the implementing handler
83
     *
84
     * @throws \UnexpectedValueException
85
     */
86
    protected function write(array $record): void
87
    {
88
        $this->ensureProcessIsStarted();
89
 
90
        $this->writeProcessInput($record['formatted']);
91
 
92
        $errors = $this->readProcessErrors();
93
        if (empty($errors) === false) {
94
            throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors));
95
        }
96
    }
97
 
98
    /**
99
     * Makes sure that the process is actually started, and if not, starts it,
100
     * assigns the stream pipes, and handles startup errors, if any.
101
     */
102
    private function ensureProcessIsStarted(): void
103
    {
104
        if (is_resource($this->process) === false) {
105
            $this->startProcess();
106
 
107
            $this->handleStartupErrors();
108
        }
109
    }
110
 
111
    /**
112
     * Starts the actual process and sets all streams to non-blocking.
113
     */
114
    private function startProcess(): void
115
    {
116
        $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd);
117
 
118
        foreach ($this->pipes as $pipe) {
119
            stream_set_blocking($pipe, false);
120
        }
121
    }
122
 
123
    /**
124
     * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any.
125
     *
126
     * @throws \UnexpectedValueException
127
     */
128
    private function handleStartupErrors(): void
129
    {
130
        $selected = $this->selectErrorStream();
131
        if (false === $selected) {
132
            throw new \UnexpectedValueException('Something went wrong while selecting a stream.');
133
        }
134
 
135
        $errors = $this->readProcessErrors();
136
 
137
        if (is_resource($this->process) === false || empty($errors) === false) {
138
            throw new \UnexpectedValueException(
139
                sprintf('The process "%s" could not be opened: ' . $errors, $this->command)
140
            );
141
        }
142
    }
143
 
144
    /**
145
     * Selects the STDERR stream.
146
     *
147
     * @return int|bool
148
     */
149
    protected function selectErrorStream()
150
    {
151
        $empty = [];
152
        $errorPipes = [$this->pipes[2]];
153
 
154
        return stream_select($errorPipes, $empty, $empty, 1);
155
    }
156
 
157
    /**
158
     * Reads the errors of the process, if there are any.
159
     *
160
     * @codeCoverageIgnore
161
     * @return string Empty string if there are no errors.
162
     */
163
    protected function readProcessErrors(): string
164
    {
165
        return (string) stream_get_contents($this->pipes[2]);
166
    }
167
 
168
    /**
169
     * Writes to the input stream of the opened process.
170
     *
171
     * @codeCoverageIgnore
172
     */
173
    protected function writeProcessInput(string $string): void
174
    {
175
        fwrite($this->pipes[0], $string);
176
    }
177
 
178
    /**
179
     * {@inheritDoc}
180
     */
181
    public function close(): void
182
    {
183
        if (is_resource($this->process)) {
184
            foreach ($this->pipes as $pipe) {
185
                fclose($pipe);
186
            }
187
            proc_close($this->process);
188
            $this->process = null;
189
        }
190
    }
191
}