Completed
Push — master ( a20e35...2a3e96 )
by Greg
03:21
created

src/Common/ExecTrait.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Robo\Common;
4
5
use Robo\ResultData;
6
use Symfony\Component\Process\Process;
7
8
/**
9
 * Class ExecTrait
10
 * @package Robo\Common
11
 */
12
trait ExecTrait
13
{
14
    /**
15
     * @var bool
16
     */
17
    protected $background = false;
18
19
    /**
20
     * @var null|int
21
     */
22
    protected $timeout = null;
23
24
    /**
25
     * @var null|int
26
     */
27
    protected $idleTimeout = null;
28
29
    /**
30
     * @var null|array
31
     */
32
    protected $env = null;
33
34
    /**
35
     * @var Process
36
     */
37
    protected $process;
38
39
    /**
40
     * @var resource|string
41
     */
42
    protected $input;
43
44
    /**
45
     * @var boolean
46
     */
47
    protected $interactive = null;
48
49
    /**
50
     * @var bool
51
     */
52
    protected $isPrinted = true;
53
54
    /**
55
     * @var bool
56
     */
57
    protected $isMetadataPrinted = true;
58
59
    /**
60
     * @var string
61
     */
62
    protected $workingDirectory;
63
64
    /**
65
     * @return string
66
     */
67
    abstract public function getCommandDescription();
68
69
    /** Typically provided by Timer trait via ProgressIndicatorAwareTrait. */
70
    abstract public function startTimer();
71
    abstract public function stopTimer();
72
    abstract public function getExecutionTime();
73
74
    /**
75
     * Typically provided by TaskIO Trait.
76
     */
77
    abstract public function hideTaskProgress();
78
    abstract public function showTaskProgress($inProgress);
79
    abstract public function printTaskInfo($text, $context = null);
80
81
    /**
82
     * Typically provided by VerbosityThresholdTrait.
83
     */
84
    abstract public function verbosityMeetsThreshold();
85
    abstract public function writeMessage($message);
86
87
    /**
88
     * Sets $this->interactive() based on posix_isatty().
89
     *
90
     * @return $this
91
     */
92
    public function detectInteractive()
93
    {
94
        // If the caller did not explicity set the 'interactive' mode,
95
        // and output should be produced by this task (verbosityMeetsThreshold),
96
        // then we will automatically set interactive mode based on whether
97
        // or not output was redirected when robo was executed.
98
        if (!isset($this->interactive) && function_exists('posix_isatty') && $this->verbosityMeetsThreshold()) {
99
            $this->interactive = posix_isatty(STDOUT);
100
        }
101
102
        return $this;
103
    }
104
105
    /**
106
     * Executes command in background mode (asynchronously)
107
     *
108
     * @return $this
109
     */
110
    public function background($arg = true)
111
    {
112
        $this->background = $arg;
113
        return $this;
114
    }
115
116
    /**
117
     * Stop command if it runs longer then $timeout in seconds
118
     *
119
     * @param int $timeout
120
     *
121
     * @return $this
122
     */
123
    public function timeout($timeout)
124
    {
125
        $this->timeout = $timeout;
126
        return $this;
127
    }
128
129
    /**
130
     * Stops command if it does not output something for a while
131
     *
132
     * @param int $timeout
133
     *
134
     * @return $this
135
     */
136
    public function idleTimeout($timeout)
137
    {
138
        $this->idleTimeout = $timeout;
139
        return $this;
140
    }
141
142
    /**
143
     * Set a single environment variable, or multiple.
144
     */
145
    public function env($env, $value = null)
146
    {
147
        if (!is_array($env)) {
148
            $env = [$env => ($value ? $value : true)];
149
        }
150
        return $this->envVars($env);
151
    }
152
153
    /**
154
     * Sets the environment variables for the command
155
     *
156
     * @param array $env
157
     *
158
     * @return $this
159
     */
160
    public function envVars(array $env)
161
    {
162
        $this->env = $this->env ? $env + $this->env : $env;
163
        return $this;
164
    }
165
166
    /**
167
     * Pass an input to the process. Can be resource created with fopen() or string
168
     *
169
     * @param resource|string $input
170
     *
171
     * @return $this
172
     */
173
    public function setInput($input)
174
    {
175
        $this->input = $input;
176
        return $this;
177
    }
178
179
    /**
180
     * Attach tty to process for interactive input
181
     *
182
     * @param $interactive bool
183
     *
184
     * @return $this
185
     */
186
    public function interactive($interactive = true)
187
    {
188
        $this->interactive = $interactive;
189
        return $this;
190
    }
191
192
193
    /**
194
     * Is command printing its output to screen
195
     *
196
     * @return bool
197
     */
198
    public function getPrinted()
199
    {
200
        return $this->isPrinted;
201
    }
202
203
    /**
204
     * Changes working directory of command
205
     *
206
     * @param string $dir
207
     *
208
     * @return $this
209
     */
210
    public function dir($dir)
211
    {
212
        $this->workingDirectory = $dir;
213
        return $this;
214
    }
215
216
    /**
217
     * Shortcut for setting isPrinted() and isMetadataPrinted() to false.
218
     *
219
     * @param bool $arg
220
     *
221
     * @return $this
222
     */
223
    public function silent($arg)
224
    {
225
        if (is_bool($arg)) {
226
            $this->isPrinted = !$arg;
227
            $this->isMetadataPrinted = !$arg;
228
        }
229
        return $this;
230
    }
231
232
    /**
233
     * Should command output be printed
234
     *
235
     * @param bool $arg
236
     *
237
     * @return $this
238
     *
239
     * @deprecated
240
     */
241
    public function printed($arg)
242
    {
243
        $this->logger->warning("printed() is deprecated. Please use printOutput().");
244
        return $this->printOutput($arg);
245
    }
246
247
    /**
248
     * Should command output be printed
249
     *
250
     * @param bool $arg
251
     *
252
     * @return $this
253
     */
254
    public function printOutput($arg)
255
    {
256
        if (is_bool($arg)) {
257
            $this->isPrinted = $arg;
258
        }
259
        return $this;
260
    }
261
262
    /**
263
     * Should command metadata be printed. I,e., command and timer.
264
     *
265
     * @param bool $arg
266
     *
267
     * @return $this
268
     */
269
    public function printMetadata($arg)
270
    {
271
        if (is_bool($arg)) {
272
            $this->isMetadataPrinted = $arg;
273
        }
274
        return $this;
275
    }
276
277
    /**
278
     * @param Process $process
279
     * @param callable $output_callback
280
     *
281
     * @return \Robo\ResultData
282
     */
283
    protected function execute($process, $output_callback = null)
284
    {
285
        $this->process = $process;
286
287
        if (!$output_callback) {
288
            $output_callback = function ($type, $buffer) {
289
                $progressWasVisible = $this->hideTaskProgress();
290
                $this->writeMessage($buffer);
291
                $this->showTaskProgress($progressWasVisible);
292
            };
293
        }
294
295
        $this->detectInteractive();
296
297
        if ($this->isMetadataPrinted) {
298
            $this->printAction();
299
        }
300
        $this->process->setTimeout($this->timeout);
301
        $this->process->setIdleTimeout($this->idleTimeout);
302
        if ($this->workingDirectory) {
303
            $this->process->setWorkingDirectory($this->workingDirectory);
304
        }
305
        if ($this->input) {
306
            $this->process->setInput($this->input);
307
        }
308
309
        if ($this->interactive && $this->isPrinted) {
310
            $this->process->setTty(true);
311
        }
312
313
        if (isset($this->env)) {
314
            // Symfony 4 will inherit environment variables by default, but until
315
            // then, manually ensure they are inherited.
316
            if (method_exists($this->process, 'inheritEnvironmentVariables')) {
317
                $this->process->inheritEnvironmentVariables();
318
            }
319
            $this->process->setEnv($this->env);
320
        }
321
322 View Code Duplication
        if (!$this->background && !$this->isPrinted) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
323
            $this->startTimer();
324
            $this->process->run();
325
            $this->stopTimer();
326
            $output = rtrim($this->process->getOutput());
327
            return new ResultData(
328
                $this->process->getExitCode(),
329
                $output,
330
                $this->getResultData()
331
            );
332
        }
333
334 View Code Duplication
        if (!$this->background && $this->isPrinted) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
            $this->startTimer();
336
            $this->process->run($output_callback);
337
            $this->stopTimer();
338
            return new ResultData(
339
                $this->process->getExitCode(),
340
                $this->process->getOutput(),
341
                $this->getResultData()
342
            );
343
        }
344
345
        try {
346
            $this->process->start();
347
        } catch (\Exception $e) {
348
            return new ResultData(
349
                $this->process->getExitCode(),
350
                $e->getMessage(),
351
                $this->getResultData()
352
            );
353
        }
354
        return new ResultData($this->process->getExitCode());
355
    }
356
357
    /**
358
     *
359
     */
360
    protected function stop()
361
    {
362
        if ($this->background && isset($this->process) && $this->process->isRunning()) {
363
            $this->process->stop();
364
            $this->printTaskInfo(
365
                "Stopped {command}",
366
                ['command' => $this->getCommandDescription()]
367
            );
368
        }
369
    }
370
371
    /**
372
     * @param array $context
373
     */
374
    protected function printAction($context = [])
375
    {
376
        $command = $this->getCommandDescription();
377
        $formatted_command = $this->formatCommandDisplay($command);
378
379
        $dir = $this->workingDirectory ? " in {dir}" : "";
380
        $this->printTaskInfo("Running {command}$dir", [
381
                'command' => $formatted_command,
382
                'dir' => $this->workingDirectory
383
            ] + $context);
384
    }
385
386
    /**
387
     * @param $command
388
     *
389
     * @return mixed
390
     */
391
    protected function formatCommandDisplay($command)
392
    {
393
        $formatted_command = str_replace("&&", "&&\n", $command);
394
        $formatted_command = str_replace("||", "||\n", $formatted_command);
395
396
        return $formatted_command;
397
    }
398
399
    /**
400
     * Gets the data array to be passed to Result().
401
     *
402
     * @return array
403
     *   The data array passed to Result().
404
     */
405
    protected function getResultData()
406
    {
407
        if ($this->isMetadataPrinted) {
408
            return ['time' => $this->getExecutionTime()];
409
        }
410
411
        return [];
412
    }
413
}
414