Passed
Pull Request — master (#8)
by
unknown
02:53 queued 43s
created

Operation::getLoggingFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Covert;
4
5
use Closure;
6
use Covert\Utils\FunctionReflection;
7
use Covert\Utils\OperatingSystem;
8
use Exception;
9
10
class Operation
11
{
12
    /**
13
     * The absolute path to the autoload.php file.
14
     *
15
     * @var string
16
     */
17
    private $autoload;
18
19
    /**
20
     * The absolute path to the output log file.
21
     *
22
     * @var bool|string
23
     */
24
    private $logging;
25
26
    /**
27
     * The process ID (pid) of the background task.
28
     *
29
     * @var int|null
30
     */
31
    private $processId;
32
33
    /**
34
     * Create a new operation instance.
35
     *
36
     * @param null $processId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $processId is correct as it would always require null to be passed?
Loading history...
37
     *
38
     * @throws \Exception
39
     */
40
    public function __construct($processId = null)
41
    {
42
        $this->setAutoloadFile(__DIR__.'/../../../autoload.php');
43
        $this->setLoggingFile(false);
44
        $this->processId = $processId;
45
    }
46
47
    /**
48
     * Statically create an instance of an operation from an existing
49
     * process ID.
50
     *
51
     * @param $processId
52
     *
53
     * @throws \Exception
54
     *
55
     * @return self
56
     */
57
    public static function withId($processId)
58
    {
59
        return new self($processId);
60
    }
61
62
    /**
63
     * Execute the process.
64
     *
65
     * @param \Closure $closure The anonymous function to execute.
66
     *
67
     * @throws \ReflectionException
68
     *
69
     * @return self
70
     */
71
    public function execute(Closure $closure)
72
    {
73
        $temporaryFile = tempnam(sys_get_temp_dir(), 'covert');
74
75
        $temporaryContent = '<?php'.PHP_EOL.PHP_EOL;
76
77
        if ($this->autoload !== false) {
0 ignored issues
show
introduced by
The condition $this->autoload !== false is always true.
Loading history...
78
            $temporaryContent .= "require('$this->autoload');".PHP_EOL.PHP_EOL;
79
        }
80
81
        $temporaryContent .= FunctionReflection::toString($closure).PHP_EOL.PHP_EOL;
82
        $temporaryContent .= 'unlink(__FILE__);'.PHP_EOL.PHP_EOL;
83
        $temporaryContent .= 'exit;';
84
85
        file_put_contents($temporaryFile, $temporaryContent);
86
87
        $this->processId = $this->executeFile($temporaryFile);
88
89
        return $this;
90
    }
91
92
    /**
93
     * Check the operating system call appropriate execution method.
94
     *
95
     * @param string $file The absolute path to the executing file.
96
     *
97
     * @throws \Exception
98
     *
99
     * @return int
100
     */
101
    private function executeFile($file)
102
    {
103
        if (OperatingSystem::isWindows()) {
104
            return $this->runCommandForWindows($file);
105
        }
106
107
        return $this->runCommandForNix($file);
108
    }
109
110
    /**
111
     * Execute the shell process for the Windows platform.
112
     *
113
     * @param string $file The absolute path to the executing file.
114
     *
115
     * @throws \Exception
116
     *
117
     * @return int
118
     */
119
    private function runCommandForWindows($file)
120
    {
121
        if ($this->getLoggingFile()) {
122
            $stdoutPipe = ['file', $this->getLoggingFile(), 'w'];
123
            $stderrPipe = ['file', $this->getLoggingFile(), 'w'];
124
        } else {
125
            $stdoutPipe = fopen('NUL', 'c');
126
            $stderrPipe = fopen('NUL', 'c');
127
        }
128
129
        $desc = [
130
            ['pipe', 'r'],
131
            $stdoutPipe,
132
            $stderrPipe,
133
        ];
134
135
        $cmd = "START /b php {$file}";
136
137
        $handle = proc_open(
138
            $cmd,
139
            $desc,
140
            [],
0 ignored issues
show
Bug introduced by
array() cannot be passed to proc_open() as the parameter $pipes expects a reference. ( Ignorable by Annotation )

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

140
            /** @scrutinizer ignore-type */ [],
Loading history...
141
            getcwd()
142
        );
143
144
        if (!is_resource($handle)) {
145
            throw new Exception('Could not create a background resource. Try using a better operating system.');
146
        }
147
148
        $pid = proc_get_status($handle)['pid'];
149
150
        try {
151
            proc_close($handle);
152
            $resource = array_filter(explode(' ', shell_exec("wmic process get parentprocessid, processid | find \"$pid\"")));
153
            array_pop($resource);
154
            $pid = end($resource);
155
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
156
        }
157
158
        return $pid;
159
    }
160
161
    /**
162
     * Execute the shell process for the *nix platform.
163
     *
164
     * @param string $file The absolute path to the executing file.
165
     *
166
     * @return int
167
     */
168
    private function runCommandForNix($file)
169
    {
170
        $cmd = "php {$file} ";
171
172
        if (!$this->getLoggingFile()) {
173
            $cmd .= '> /dev/null 2>&1 & echo $!';
174
        } else {
175
            $cmd .= "> {$this->getLoggingFile()} & echo $!";
176
        }
177
178
        return (int) shell_exec($cmd);
179
    }
180
181
    /**
182
     * Set a custom path to the autoload.php file.
183
     *
184
     * @param $autoload
185
     *
186
     * @throws \Exception
187
     *
188
     * @return self
189
     */
190
    public function setAutoloadFile($autoload)
191
    {
192
        if ($autoload !== false && !file_exists($autoload)) {
193
            throw new Exception("The autoload path '{$autoload}' doesn't exist.");
194
        }
195
196
        $this->autoload = realpath($autoload);
197
198
        return $this;
199
    }
200
201
    /**
202
     * Set a custom path to the output logging file.
203
     *
204
     * @param string|bool $logging The absolute path to the output logging file.
205
     *
206
     * @return self
207
     */
208
    public function setLoggingFile($logging)
209
    {
210
        $this->logging = $logging;
211
212
        return $this;
213
    }
214
215
    /**
216
     * Get a custom path to the output logging file.
217
     *
218
     * @return string|bool
219
     */
220
    public function getLoggingFile()
221
    {
222
        return $this->logging;
223
    }
224
225
    /**
226
     * Get the process ID of the task running as a system process.
227
     *
228
     * @return int|null
229
     */
230
    public function getProcessId()
231
    {
232
        return $this->processId;
233
    }
234
235
    /**
236
     * Returns true if the process ID is still active.
237
     *
238
     * @return bool
239
     */
240
    public function isRunning()
241
    {
242
        $processId = $this->getProcessId();
243
244
        if (OperatingSystem::isWindows()) {
245
            $pids = shell_exec("wmic process get processid | find \"{$processId}\"");
246
            $resource = array_filter(explode(' ', $pids));
247
248
            $isRunning = count($resource) > 0 && $processId == reset($resource);
249
        } else {
250
            $isRunning = (bool) posix_getsid($processId);
251
        }
252
253
        return $isRunning;
254
    }
255
256
    /**
257
     * Kill the current operation process if it is running.
258
     *
259
     * @return self
260
     */
261
    public function kill()
262
    {
263
        if ($this->isRunning()) {
264
            $processId = $this->getProcessId();
265
266
            if (OperatingSystem::isWindows()) {
267
                $cmd = "taskkill /pid {$processId} -t -f";
268
            } else {
269
                $cmd = "kill -9 {$processId}";
270
            }
271
272
            shell_exec($cmd);
273
        }
274
275
        return $this;
276
    }
277
}
278