Passed
Pull Request — master (#8)
by
unknown
03:30 queued 01:19
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
     * @return self
54
     * @throws \Exception
55
     */
56
    public static function withId($processId)
57
    {
58
        return new self($processId);
59
    }
60
61
    /**
62
     * Execute the process.
63
     *
64
     * @param \Closure $closure The anonymous function to execute.
65
     *
66
     * @return self
67
     * @throws \ReflectionException
68
     */
69
    public function execute(Closure $closure)
70
    {
71
        $temporaryFile = tempnam(sys_get_temp_dir(), 'covert');
72
73
        $temporaryContent = '<?php'.PHP_EOL.PHP_EOL;
74
75
        if ($this->autoload !== false) {
0 ignored issues
show
introduced by
The condition $this->autoload !== false is always true.
Loading history...
76
            $temporaryContent .= "require('$this->autoload');".PHP_EOL.PHP_EOL;
77
        }
78
79
        $temporaryContent .= FunctionReflection::toString($closure).PHP_EOL.PHP_EOL;
80
        $temporaryContent .= 'unlink(__FILE__);'.PHP_EOL.PHP_EOL;
81
        $temporaryContent .= 'exit;';
82
83
        file_put_contents($temporaryFile, $temporaryContent);
84
85
        $this->processId = $this->executeFile($temporaryFile);
86
87
        return $this;
88
    }
89
90
    /**
91
     * Check the operating system call appropriate execution method.
92
     *
93
     * @param string $file The absolute path to the executing file.
94
     *
95
     * @return integer
96
     * @throws \Exception
97
     */
98
    private function executeFile($file)
99
    {
100
        if (OperatingSystem::isWindows()) {
101
            return $this->runCommandForWindows($file);
102
        }
103
104
        return $this->runCommandForNix($file);
105
    }
106
107
    /**
108
     * Execute the shell process for the Windows platform.
109
     *
110
     * @param string $file The absolute path to the executing file.
111
     *
112
     * @return integer
113
     * @throws \Exception
114
     */
115
    private function runCommandForWindows($file)
116
    {
117
        if ($this->getLoggingFile()) {
118
            $stdoutPipe = ['file', $this->getLoggingFile(), 'w'];
119
            $stderrPipe = ['file', $this->getLoggingFile(), 'w'];
120
        } else {
121
            $stdoutPipe = fopen('NUL', 'c');
122
            $stderrPipe = fopen('NUL', 'c');
123
        }
124
125
        $desc = [
126
            ['pipe', 'r'],
127
            $stdoutPipe,
128
            $stderrPipe,
129
        ];
130
131
        $cmd = "START /b php {$file}";
132
133
        $handle = proc_open(
134
            $cmd,
135
            $desc,
136
            [],
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

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