Completed
Push — master ( ab2074...f08068 )
by Stephen
01:24 queued 01:22
created

Operation::getCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
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
     * Command to run PHP.
35
     *
36
     * @var string
37
     */
38
    private $command = 'php';
39
40
    /**
41
     * Create a new operation instance.
42
     *
43
     * @return self
44
     */
45
    public function __construct($processId = null)
46
    {
47
        $this->autoload = __DIR__.'/../../../autoload.php';
48
        $this->logging = false;
49
        $this->processId = $processId;
50
    }
51
52
    /**
53
     * Statically create an instance of an operation from an existing
54
     * process ID.
55
     *
56
     * @return self
57
     */
58
    public static function withId($processId)
59
    {
60
        return new self($processId);
61
    }
62
63
    /**
64
     * Execute the process.
65
     *
66
     * @param \Closure $closure The anonymous function to execute.
67
     *
68
     * @return void
69
     */
70
    public function execute(Closure $closure)
71
    {
72
        $temporaryFile = tempnam(sys_get_temp_dir(), 'covert');
73
74
        $temporaryContent = '<?php'.PHP_EOL.PHP_EOL;
75
76
        if ($this->autoload !== false) {
0 ignored issues
show
introduced by
The condition $this->autoload !== false is always true.
Loading history...
77
            $temporaryContent .= "require('$this->autoload');".PHP_EOL.PHP_EOL;
78
        }
79
80
        $temporaryContent .= FunctionReflection::toString($closure).PHP_EOL.PHP_EOL;
81
        $temporaryContent .= 'unlink(__FILE__);'.PHP_EOL.PHP_EOL;
82
        $temporaryContent .= 'exit;';
83
84
        file_put_contents($temporaryFile, $temporaryContent);
85
86
        $this->processId = $this->executeFile($temporaryFile);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->processId is correct as $this->executeFile($temporaryFile) targeting Covert\Operation::executeFile() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Documentation Bug introduced by
It seems like $this->executeFile($temporaryFile) of type void is incompatible with the declared type integer|null of property $processId.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
87
88
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Covert\Operation which is incompatible with the documented return type void.
Loading history...
89
    }
90
91
    /**
92
     * Check the operating system call appropriate execution method.
93
     *
94
     * @param string $file The absolute path to the executing file.
95
     *
96
     * @return void
97
     */
98
    private function executeFile($file)
99
    {
100
        if (OperatingSystem::isWindows()) {
101
            return $this->runCommandForWindows($file);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->runCommandForWindows($file) targeting Covert\Operation::runCommandForWindows() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
102
        }
103
104
        return $this->runCommandForNix($file);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->runCommandForNix($file) targeting Covert\Operation::runCommandForNix() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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 void
113
     */
114
    private function runCommandForWindows($file)
115
    {
116
        if ($this->logging) {
117
            $stdoutPipe = ['file', $this->logging, 'w'];
118
            $stderrPipe = ['file', $this->logging, 'w'];
119
        } else {
120
            $stdoutPipe = fopen('NUL', 'c');
121
            $stderrPipe = fopen('NUL', 'c');
122
        }
123
124
        $desc = [
125
            ['pipe', 'r'],
126
            $stdoutPipe,
127
            $stderrPipe,
128
        ];
129
130
        $cmd = 'START /b '.$this->getCommand()." {$file}";
131
132
        $handle = proc_open(
133
            $cmd,
134
            $desc,
135
            [],
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

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