Completed
Push — master ( c3b4b2...02e296 )
by Christian
02:46
created

ProcessBuilder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
/**
4
 * This file is part of tenside/core.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core
18
 * @filesource
19
 */
20
21
namespace Tenside\Core\Util;
22
23
use Symfony\Component\Process\Exception\InvalidArgumentException;
24
use Symfony\Component\Process\Process;
25
use Symfony\Component\Process\ProcessUtils;
26
27
/**
28
 * This class is a process builder used in tenside.
29
 *
30
 * It is heavily based upon the ProcessBuilder by symfony but more granular and adds missing features.
31
 */
32
class ProcessBuilder
33
{
34
    /**
35
     * The CLI executable to launch.
36
     *
37
     * @var string
38
     */
39
    private $binary;
40
41
    /**
42
     * The CLI arguments to pass.
43
     *
44
     * @var string[]
45
     */
46
    private $arguments = [];
47
48
    /**
49
     * The working directory.
50
     *
51
     * @var string
52
     */
53
    private $workingDirectory;
54
55
    /**
56
     * The environment variables.
57
     *
58
     * @var array
59
     */
60
    private $environment = [];
61
62
    /**
63
     * Flag determining if the current environment shall get inherited.
64
     *
65
     * @var bool
66
     */
67
    private $inheritEnvironment = true;
68
69
    /**
70
     * The input content.
71
     *
72
     * @var resource|scalar|\Traversable|null
73
     */
74
    private $input;
75
76
    /**
77
     * The timeout value.
78
     *
79
     * @var int|null
80
     */
81
    private $timeout = 60;
82
83
    /**
84
     * The proc_open options to use.
85
     *
86
     * @var array
87
     */
88
    private $options = [];
89
90
    /**
91
     * Flag determining if the output shall be disabled.
92
     *
93
     * @var bool
94
     */
95
    private $outputDisabled = false;
96
97
    /**
98
     * Flag determining if task shall get spawned into background.
99
     *
100
     * @var bool
101
     */
102
    private $forceBackground = false;
103
104
    /**
105
     * Create a new instance.
106
     *
107
     * @param string   $binary    The binary to launch.
108
     *
109
     * @param string[] $arguments An array of arguments.
110
     *
111
     * @return ProcessBuilder
112
     */
113
    public static function create($binary, array $arguments = [])
114
    {
115
        return new static($binary, $arguments);
116
    }
117
118
    /**
119
     * Constructor.
120
     *
121
     * @param string   $binary    The binary to launch.
122
     *
123
     * @param string[] $arguments An array of arguments.
124
     */
125
    public function __construct($binary, array $arguments = [])
126
    {
127
        $this->binary = $binary;
128
        $this->setArguments($arguments);
129
    }
130
131
    /**
132
     * Adds an unescaped argument to the command string.
133
     *
134
     * @param string $argument A command argument.
135
     *
136
     * @return ProcessBuilder
137
     */
138
    public function addArgument($argument)
139
    {
140
        $this->arguments[] = (string) $argument;
141
142
        return $this;
143
    }
144
145
    /**
146
     * Adds unescaped arguments to the command string.
147
     *
148
     * @param string[] $arguments The command arguments.
149
     *
150
     * @return ProcessBuilder
151
     */
152
    public function addArguments(array $arguments)
153
    {
154
        foreach ($arguments as $argument) {
155
            $this->addArgument($argument);
156
        }
157
158
        return $this;
159
    }
160
161
    /**
162
     * Sets the arguments of the process.
163
     *
164
     * Arguments must not be escaped.
165
     * Previous arguments are removed.
166
     *
167
     * @param string[] $arguments The new arguments.
168
     *
169
     * @return ProcessBuilder
170
     */
171
    public function setArguments(array $arguments)
172
    {
173
        $this->arguments = [];
174
        if ([] !== $arguments) {
175
            $this->addArguments($arguments);
176
        }
177
178
        return $this;
179
    }
180
181
    /**
182
     * Sets the working directory.
183
     *
184
     * @param null|string $workingDirectory The working directory.
185
     *
186
     * @return ProcessBuilder
187
     *
188
     * @throws InvalidArgumentException When the working directory is non null and does not exist.
189
     */
190
    public function setWorkingDirectory($workingDirectory)
191
    {
192
        if ($workingDirectory && !is_dir($workingDirectory)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $workingDirectory of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
193
            throw new InvalidArgumentException('The working directory must exist.');
194
        }
195
196
        $this->workingDirectory = $workingDirectory;
197
198
        return $this;
199
    }
200
201
    /**
202
     * Sets whether environment variables will be inherited or not.
203
     *
204
     * @param bool $inheritEnvironment Flag if the environment shall get inherited or not (default true).
205
     *
206
     * @return ProcessBuilder
207
     */
208
    public function inheritEnvironmentVariables($inheritEnvironment = true)
209
    {
210
        $this->inheritEnvironment = $inheritEnvironment;
211
212
        return $this;
213
    }
214
215
    /**
216
     * Sets an environment variable.
217
     *
218
     * Setting a variable overrides its previous value. Use `null` to unset a
219
     * defined environment variable.
220
     *
221
     * @param string      $name  The variable name.
222
     *
223
     * @param null|string $value The variable value.
224
     *
225
     * @return ProcessBuilder
226
     */
227
    public function setEnv($name, $value)
228
    {
229
        $this->environment[$name] = $value;
230
231
        return $this;
232
    }
233
234
    /**
235
     * Adds a set of environment variables.
236
     *
237
     * Already existing environment variables with the same name will be
238
     * overridden by the new values passed to this method. Pass `null` to unset
239
     * a variable.
240
     *
241
     * @param array $variables The variables.
242
     *
243
     * @return ProcessBuilder
244
     */
245
    public function addEnvironmentVariables(array $variables)
246
    {
247
        $this->environment = array_replace($this->environment, $variables);
248
249
        return $this;
250
    }
251
252
    /**
253
     * Sets the input of the process.
254
     *
255
     * @param resource|scalar|\Traversable|null $input The input content.
256
     *
257
     * @return ProcessBuilder
258
     *
259
     * @throws InvalidArgumentException In case the argument is invalid.
260
     */
261
    public function setInput($input)
262
    {
263
        $this->input = ProcessUtils::validateInput(__METHOD__, $input);
264
265
        return $this;
266
    }
267
268
    /**
269
     * Sets the process timeout.
270
     *
271
     * To disable the timeout, set this value to null.
272
     *
273
     * @param float|null $timeout The new timeout value.
274
     *
275
     * @return ProcessBuilder
276
     *
277
     * @throws InvalidArgumentException When the timeout value is negative.
278
     */
279
    public function setTimeout($timeout)
280
    {
281
        if (null === $timeout) {
282
            $this->timeout = null;
283
284
            return $this;
285
        }
286
287
        $timeout = (float) $timeout;
288
289
        if ($timeout < 0) {
290
            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
291
        }
292
293
        $this->timeout = $timeout;
0 ignored issues
show
Documentation Bug introduced by
It seems like $timeout of type double is incompatible with the declared type integer|null of property $timeout.

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...
294
295
        return $this;
296
    }
297
298
    /**
299
     * Adds a proc_open() option.
300
     *
301
     * @param string $name  The option name.
302
     *
303
     * @param string $value The option value.
304
     *
305
     * @return ProcessBuilder
306
     */
307
    public function setOption($name, $value)
308
    {
309
        $this->options[$name] = $value;
310
311
        return $this;
312
    }
313
314
    /**
315
     * Disables fetching output and error output from the underlying process.
316
     *
317
     * @return ProcessBuilder
318
     */
319
    public function disableOutput()
320
    {
321
        $this->outputDisabled = true;
322
323
        return $this;
324
    }
325
326
    /**
327
     * Enables fetching output and error output from the underlying process.
328
     *
329
     * @return ProcessBuilder
330
     */
331
    public function enableOutput()
332
    {
333
        $this->outputDisabled = false;
334
335
        return $this;
336
    }
337
338
    /**
339
     * Set if the process execution should be forced into the background.
340
     *
341
     * @param boolean $forceBackground The new value.
342
     *
343
     * @return ProcessBuilder
344
     */
345
    public function setForceBackground($forceBackground = true)
346
    {
347
        $this->forceBackground = $forceBackground;
348
349
        return $this;
350
    }
351
352
    /**
353
     * Generate the process.
354
     *
355
     * @return Process
356
     */
357
    public function generate()
358
    {
359
        $options = $this->options;
360
361
        $arguments = array_merge([$this->binary], (array) $this->arguments);
362
        $script    = implode(' ', array_map([ProcessUtils::class, 'escapeArgument'], $arguments));
363
        $process   = new Process(
364
            $this->applyForceToBackground($script),
365
            $this->workingDirectory,
366
            $this->getEnvironmentVariables(),
367
            $this->input,
368
            $this->timeout,
369
            $options
370
        );
371
372
        if ($this->outputDisabled) {
373
            $process->disableOutput();
374
        }
375
376
        return $process;
377
    }
378
379
    /**
380
     * Retrieve the passed environment variables from the current session and return them.
381
     *
382
     * @return array
383
     *
384
     * @SuppressWarnings(PHPMD.Superglobals)
385
     * @SuppressWarnings(PHPMD.CamelCaseVariableName)
386
     */
387
    private function getEnvironmentVariables()
388
    {
389
        // Initialize from globals, allow override of special keys via putenv calls.
390
        $variables = $this->inheritEnvironment
391
            ? array_replace($_ENV, $_SERVER, $this->environment)
392
            : $this->environment;
393
394
        foreach (array_keys($variables) as $name) {
395
            if (false !== ($value = getenv($name))) {
396
                $variables[$name] = $value;
397
            }
398
        }
399
400
        return $variables;
401
    }
402
403
    /**
404
     * Apply the force to background flag to a command line.
405
     *
406
     * @param string $commandLine The current command line to execute.
407
     *
408
     * @return string
409
     */
410
    private function applyForceToBackground($commandLine)
411
    {
412
        if ($this->forceBackground) {
413
            if ('\\' === DIRECTORY_SEPARATOR) {
414
                return 'start /B ' . $commandLine;
415
            }
416
            return $commandLine . ' &';
417
        }
418
419
        return $commandLine;
420
    }
421
}
422