Passed
Push — master ( b9bc63...f1e2bc )
by Michiel
06:23
created

ExecTask   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 715
Duplicated Lines 0 %

Test Coverage

Coverage 88.07%

Importance

Changes 0
Metric Value
wmc 83
eloc 187
dl 0
loc 715
ccs 192
cts 218
cp 0.8807
rs 2
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A setResolveExecutable() 0 3 1
A addEnv() 0 3 1
A getOs() 0 3 1
A setReturnProperty() 0 3 1
A setLogoutput() 0 3 1
A setOsFamily() 0 3 1
C buildCommand() 0 51 12
A setPassthru() 0 3 1
A setOutputProperty() 0 3 1
A setExitValue() 0 3 1
A isValidOs() 0 21 5
A setOs() 0 3 1
B cleanup() 0 27 7
C resolveExecutable() 0 44 13
A setDir() 0 3 1
A maybeSetReturnPropertyValue() 0 4 2
A setCheckreturn() 0 3 1
A setError() 0 3 1
A getResolveExecutable() 0 3 1
A __construct() 0 5 1
A setExecutable() 0 7 3
A isPath() 0 3 2
A main() 0 18 3
A setOutput() 0 3 1
A isFailure() 0 7 2
A prepare() 0 33 6
A createArg() 0 3 1
A executeCommand() 0 16 2
A setSpawn() 0 3 1
A getExitValue() 0 3 1
A getPath() 0 12 3
A getOsFamily() 0 3 1
A setSearchPath() 0 3 1
A setCommand() 0 8 1
A setEscape() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ExecTask often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExecTask, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Task\System;
21
22
use InvalidArgumentException;
23
use Phing\Exception\BuildException;
24
use Phing\Exception\NullPointerException;
25
use Phing\Io\File;
26
use Phing\Io\FileUtils;
27
use Phing\Io\IOException;
28
use Phing\Phing;
29
use Phing\Project;
30
use Phing\Task;
31
use Phing\Task\System\Condition\OsCondition;
32
use Phing\Task\System\Element\LogLevelAware;
33
use Phing\Type\Commandline;
34
use Phing\Type\CommandlineArgument;
35
use Phing\Type\Environment;
36
use Phing\Type\EnvVariable;
37
use Phing\Type\Path;
38
use Phing\Util\StringHelper;
39
40
/**
41
 * Executes a command on the shell.
42
 *
43
 * @author  Andreas Aderhold <[email protected]>
44
 * @author  Hans Lellelid <[email protected]>
45
 * @author  Christian Weiske <[email protected]>
46
 * @package phing.tasks.system
47
 */
48
class ExecTask extends Task
49
{
50
    use LogLevelAware;
51
52
    public const INVALID = PHP_INT_MAX;
53
54
    private $exitValue = self::INVALID;
55
56
    /**
57
     * Command to be executed
58
     *
59
     * @var string
60
     */
61
    protected $realCommand;
62
63
    /**
64
     * Commandline managing object
65
     *
66
     * @var Commandline
67
     */
68
    protected $commandline;
69
70
    /**
71
     * Working directory.
72
     *
73
     * @var File
74
     */
75
    protected $dir;
76
77
    protected $currdir;
78
79
    /**
80
     * Operating system.
81
     *
82
     * @var string
83
     */
84
    protected $os;
85
86
    /**
87
     * Whether to escape shell command using escapeshellcmd().
88
     *
89
     * @var boolean
90
     */
91
    protected $escape = false;
92
93
    /**
94
     * Where to direct output.
95
     *
96
     * @var File
97
     */
98
    protected $output;
99
100
    /**
101
     * Whether to use PHP's passthru() function instead of exec()
102
     *
103
     * @var boolean
104
     */
105
    protected $passthru = false;
106
107
    /**
108
     * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE
109
     *
110
     * @var boolean
111
     */
112
    protected $logOutput = false;
113
114
    /**
115
     * Where to direct error output.
116
     *
117
     * @var File
118
     */
119
    protected $error;
120
121
    /**
122
     * If spawn is set then [unix] programs will redirect stdout and add '&'.
123
     *
124
     * @var boolean
125
     */
126
    protected $spawn = false;
127
128
    /**
129
     * Property name to set with return value from exec call.
130
     *
131
     * @var string
132
     */
133
    protected $returnProperty;
134
135
    /**
136
     * Property name to set with output value from exec call.
137
     *
138
     * @var string
139
     */
140
    protected $outputProperty;
141
142
    /**
143
     * Whether to check the return code.
144
     *
145
     * @var boolean
146
     */
147
    protected $checkreturn = false;
148
149
    private $osFamily;
150
    private $executable;
151
    private $resolveExecutable = false;
152
    private $searchPath = false;
153
    private $env;
154
155
    /**
156
     * @throws BuildException
157
     */
158 87
    public function __construct()
159
    {
160 87
        parent::__construct();
161 87
        $this->commandline = new Commandline();
162 87
        $this->env = new Environment();
163 87
    }
164
165
    /**
166
     * Main method: wraps execute() command.
167
     *
168
     * @throws BuildException
169
     */
170 32
    public function main()
171
    {
172 32
        if (!$this->isValidOs()) {
173 1
            return null;
174
        }
175
176
        try {
177 30
            $this->commandline->setExecutable($this->resolveExecutable($this->executable, $this->searchPath));
178
        } catch (IOException | NullPointerException $e) {
179
            throw new BuildException($e);
180
        }
181
182 30
        $this->prepare();
183 28
        $this->buildCommand();
184 28
        [$return, $output] = $this->executeCommand();
185 28
        $this->cleanup($return, $output);
186
187 27
        return $return;
188
    }
189
190
    /**
191
     * Prepares the command building and execution, i.e.
192
     * changes to the specified directory.
193
     *
194
     * @throws BuildException
195
     * @return void
196
     */
197 30
    protected function prepare()
198
    {
199 30
        if ($this->dir === null) {
200 27
            $this->dir = $this->getProject()->getBasedir();
201
        }
202
203 30
        if ($this->commandline->getExecutable() === null) {
0 ignored issues
show
introduced by
The condition $this->commandline->getExecutable() === null is always false.
Loading history...
204 1
            throw new BuildException(
205 1
                'ExecTask: Please provide "executable"'
206
            );
207
        }
208
209
        // expand any symbolic links first
210
        try {
211 29
            if (!$this->dir->getCanonicalFile()->exists()) {
212 1
                throw new BuildException(
213 1
                    "The directory '" . (string) $this->dir . "' does not exist"
214
                );
215
            }
216 28
            if (!$this->dir->getCanonicalFile()->isDirectory()) {
217
                throw new BuildException(
218 28
                    "'" . (string) $this->dir . "' is not a directory"
219
                );
220
            }
221 1
        } catch (IOException $e) {
222
            throw new BuildException(
223
                "'" . (string) $this->dir . "' is not a readable directory"
224
            );
225
        }
226 28
        $this->currdir = getcwd();
227 28
        @chdir($this->dir->getPath());
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

227
        /** @scrutinizer ignore-unhandled */ @chdir($this->dir->getPath());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
228
229 28
        $this->commandline->setEscape($this->escape);
230 28
    }
231
232
    /**
233
     * @param int $exitValue
234
     * @return bool
235
     */
236 1
    public function isFailure($exitValue = null)
237
    {
238 1
        if ($exitValue === null) {
239
            $exitValue = $this->getExitValue();
240
        }
241
242 1
        return $exitValue !== 0;
243
    }
244
245
    /**
246
     * Builds the full command to execute and stores it in $command.
247
     *
248
     * @throws BuildException
249
     * @return void
250
     * @uses   $command
251
     */
252 28
    protected function buildCommand()
253
    {
254 28
        if ($this->error !== null) {
255 1
            $this->realCommand .= ' 2> ' . escapeshellarg($this->error->getPath());
256 1
            $this->log(
257 1
                'Writing error output to: ' . $this->error->getPath(),
258 1
                $this->logLevel
259
            );
260
        }
261
262 28
        if ($this->output !== null) {
263 1
            $this->realCommand .= ' 1> ' . escapeshellarg($this->output->getPath());
264 1
            $this->log(
265 1
                'Writing standard output to: ' . $this->output->getPath(),
266 1
                $this->logLevel
267
            );
268 27
        } elseif ($this->spawn) {
269 1
            $this->realCommand .= ' 1>/dev/null';
270 1
            $this->log('Sending output to /dev/null', $this->logLevel);
271
        }
272
273
        // If neither output nor error are being written to file
274
        // then we'll redirect error to stdout so that we can dump
275
        // it to screen below.
276
277 28
        if ($this->output === null && $this->error === null && $this->passthru === false) {
278 25
            $this->realCommand .= ' 2>&1';
279
        }
280
281
        // we ignore the spawn boolean for windows
282 28
        if ($this->spawn) {
283 1
            $this->realCommand .= ' &';
284
        }
285
286 28
        $envString = '';
287 28
        $environment = $this->env->getVariables();
288 28
        if ($environment !== null) {
0 ignored issues
show
introduced by
The condition $environment !== null is always true.
Loading history...
289 3
            foreach ($environment as $variable) {
290 3
                if ($this->isPath($variable)) {
291 1
                    continue;
292
                }
293 2
                $this->log('Setting environment variable: ' . $variable, Project::MSG_VERBOSE);
294 2
                if (OsCondition::isOS(OsCondition::FAMILY_WINDOWS)) {
295
                    $envString .= 'set ' . $variable . '& ';
296
                } else {
297 2
                    $envString .= 'export ' . $variable . '; ';
298
                }
299
            }
300
        }
301
302 28
        $this->realCommand = $envString . $this->commandline . $this->realCommand;
303 28
    }
304
305
    /**
306
     * Executes the command and returns return code and output.
307
     *
308
     * @return array array(return code, array with output)
309
     * @throws BuildException
310
     */
311 46
    protected function executeCommand()
312
    {
313 46
        $cmdl = $this->realCommand;
314
315 46
        $this->log('Executing command: ' . $cmdl, $this->logLevel);
316
317 46
        $output = [];
318 46
        $return = null;
319
320 46
        if ($this->passthru) {
321 2
            passthru($cmdl, $return);
322
        } else {
323 44
            exec($cmdl, $output, $return);
324
        }
325
326 46
        return [$return, $output];
327
    }
328
329
    /**
330
     * Runs all tasks after command execution:
331
     * - change working directory back
332
     * - log output
333
     * - verify return value
334
     *
335
     * @param integer $return Return code
336
     * @param array $output Array with command output
337
     *
338
     * @throws BuildException
339
     * @return void
340
     */
341 28
    protected function cleanup($return, $output): void
342
    {
343 28
        if ($this->dir !== null) {
344 28
            @chdir($this->currdir);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

344
            /** @scrutinizer ignore-unhandled */ @chdir($this->currdir);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
345
        }
346
347 28
        $outloglevel = $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE;
348 28
        foreach ($output as $line) {
349 21
            $this->log($line, $outloglevel);
350
        }
351
352 28
        $this->maybeSetReturnPropertyValue($return);
353
354 28
        if ($this->outputProperty) {
355 5
            $this->project->setProperty(
356 5
                $this->outputProperty,
357 5
                implode("\n", $output)
358
            );
359
        }
360
361 28
        $this->setExitValue($return);
362
363 28
        if ($return !== 0) {
364 11
            if ($this->checkreturn) {
365 1
                throw new BuildException($this->getTaskType() . ' returned: ' . $return, $this->getLocation());
366
            }
367 10
            $this->log('Result: ' . $return, Project::MSG_ERR);
368
        }
369 27
    }
370
371
    /**
372
     * Set the exit value.
373
     *
374
     * @param int $value exit value of the process.
375
     */
376 28
    protected function setExitValue($value): void
377
    {
378 28
        $this->exitValue = $value;
379 28
    }
380
381
    /**
382
     * Query the exit value of the process.
383
     *
384
     * @return int the exit value or self::INVALID if no exit value has
385
     *             been received.
386
     */
387
    public function getExitValue(): int
388
    {
389
        return $this->exitValue;
390
    }
391
392
    /**
393
     * The command to use.
394
     *
395
     * @param string $command String or string-compatible (e.g. w/ __toString()).
396
     *
397
     * @return void
398
     * @throws BuildException
399
     */
400 32
    public function setCommand($command): void
401
    {
402 32
        $this->log(
403 32
            "The command attribute is deprecated.\nPlease use the executable attribute and nested arg elements.",
404 32
            Project::MSG_WARN
405
        );
406 32
        $this->commandline = new Commandline($command);
407 32
        $this->executable = $this->commandline->getExecutable();
408 32
    }
409
410
    /**
411
     * The executable to use.
412
     *
413
     * @param string|bool $value String or string-compatible (e.g. w/ __toString()).
414
     *
415
     * @return void
416
     */
417 54
    public function setExecutable($value): void
418
    {
419 54
        if (is_bool($value)) {
420 3
            $value = $value === true ? 'true' : 'false';
421
        }
422 54
        $this->executable = $value;
423 54
        $this->commandline->setExecutable($value);
424 54
    }
425
426
    /**
427
     * Whether to use escapeshellcmd() to escape command.
428
     *
429
     * @param boolean $escape If the command shall be escaped or not
430
     *
431
     * @return void
432
     */
433 7
    public function setEscape(bool $escape): void
434
    {
435 7
        $this->escape = $escape;
436 7
    }
437
438
    /**
439
     * Specify the working directory for executing this command.
440
     *
441
     * @param File $dir Working directory
442
     *
443
     * @return void
444
     */
445 9
    public function setDir(File $dir): void
446
    {
447 9
        $this->dir = $dir;
448 9
    }
449
450
    /**
451
     * Specify OS (or multiple OS) that must match in order to execute this command.
452
     *
453
     * @param string $os Operating system string (e.g. "Linux")
454
     *
455
     * @return void
456
     */
457 6
    public function setOs($os): void
458
    {
459 6
        $this->os = (string) $os;
460 6
    }
461
462
    /**
463
     * List of operating systems on which the command may be executed.
464
     */
465
    public function getOs(): string
466
    {
467
        return $this->os;
468
    }
469
470
    /**
471
     * Restrict this execution to a single OS Family
472
     *
473
     * @param string $osFamily the family to restrict to.
474
     */
475 2
    public function setOsFamily($osFamily): void
476
    {
477 2
        $this->osFamily = strtolower($osFamily);
478 2
    }
479
480
    /**
481
     * Restrict this execution to a single OS Family
482
     */
483
    public function getOsFamily()
484
    {
485
        return $this->osFamily;
486
    }
487
488
    /**
489
     * File to which output should be written.
490
     *
491
     * @param File $f Output log file
492
     *
493
     * @return void
494
     */
495 6
    public function setOutput(File $f): void
496
    {
497 6
        $this->output = $f;
498 6
    }
499
500
    /**
501
     * File to which error output should be written.
502
     *
503
     * @param File $f Error log file
504
     *
505
     * @return void
506
     */
507 4
    public function setError(File $f): void
508
    {
509 4
        $this->error = $f;
510 4
    }
511
512
    /**
513
     * Whether to use PHP's passthru() function instead of exec()
514
     *
515
     * @param boolean $passthru If passthru shall be used
516
     *
517
     * @return void
518
     */
519 4
    public function setPassthru($passthru): void
520
    {
521 4
        $this->passthru = $passthru;
522 4
    }
523
524
    /**
525
     * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE
526
     *
527
     * @param boolean $logOutput If output shall be logged visibly
528
     *
529
     * @return void
530
     */
531 1
    public function setLogoutput($logOutput): void
532
    {
533 1
        $this->logOutput = $logOutput;
534 1
    }
535
536
    /**
537
     * Whether to suppress all output and run in the background.
538
     *
539
     * @param boolean $spawn If the command is to be run in the background
540
     *
541
     * @return void
542
     */
543 4
    public function setSpawn($spawn): void
544
    {
545 4
        $this->spawn = $spawn;
546 4
    }
547
548
    /**
549
     * Whether to check the return code.
550
     *
551
     * @param boolean $checkreturn If the return code shall be checked
552
     *
553
     * @return void
554
     */
555 13
    public function setCheckreturn($checkreturn): void
556
    {
557 13
        $this->checkreturn = $checkreturn;
558 13
    }
559
560
    /**
561
     * The name of property to set to return value from exec() call.
562
     *
563
     * @param string $prop Property name
564
     *
565
     * @return void
566
     */
567 5
    public function setReturnProperty($prop): void
568
    {
569 5
        $this->returnProperty = $prop;
570 5
    }
571
572 46
    protected function maybeSetReturnPropertyValue(int $return)
573
    {
574 46
        if ($this->returnProperty) {
575 3
            $this->getProject()->setNewProperty($this->returnProperty, $return);
576
        }
577 46
    }
578
579
    /**
580
     * The name of property to set to output value from exec() call.
581
     *
582
     * @param string $prop Property name
583
     *
584
     * @return void
585
     */
586 9
    public function setOutputProperty($prop): void
587
    {
588 9
        $this->outputProperty = $prop;
589 9
    }
590
591
    /**
592
     * Add an environment variable to the launched process.
593
     *
594
     * @param EnvVariable $var new environment variable.
595
     */
596 3
    public function addEnv(EnvVariable $var)
597
    {
598 3
        $this->env->addVariable($var);
599 3
    }
600
601
    /**
602
     * Creates a nested <arg> tag.
603
     *
604
     * @return CommandlineArgument Argument object
605
     */
606 25
    public function createArg()
607
    {
608 25
        return $this->commandline->createArgument();
609
    }
610
611
    /**
612
     * Is this the OS the user wanted?
613
     *
614
     * @return boolean.
0 ignored issues
show
Documentation Bug introduced by
The doc comment boolean. at position 0 could not be parsed: Unknown type name 'boolean.' at position 0 in boolean..
Loading history...
615
     * <ul>
616
     * <li>
617
     * <li><code>true</code> if the os and osfamily attributes are null.</li>
618
     * <li><code>true</code> if osfamily is set, and the os family and must match
619
     * that of the current OS, according to the logic of
620
     * {@link Os#isOs(String, String, String, String)}, and the result of the
621
     * <code>os</code> attribute must also evaluate true.
622
     * </li>
623
     * <li>
624
     * <code>true</code> if os is set, and the system.property os.name
625
     * is found in the os attribute,</li>
626
     * <li><code>false</code> otherwise.</li>
627
     * </ul>
628
     */
629 51
    protected function isValidOs(): bool
630
    {
631
        //hand osfamily off to OsCondition class, if set
632 51
        if ($this->osFamily !== null && !OsCondition::isFamily($this->osFamily)) {
633
            return false;
634
        }
635
        //the Exec OS check is different from Os.isOs(), which
636
        //probes for a specific OS. Instead it searches the os field
637
        //for the current os.name
638 50
        $myos = Phing::getProperty("os.name");
639 50
        $this->log("Current OS is " . $myos, Project::MSG_VERBOSE);
640 50
        if (($this->os !== null) && (strpos($this->os, $myos) === false)) {
641
            // this command will be executed only on the specified OS
642 2
            $this->log(
643 2
                "This OS, " . $myos
644 2
                . " was not found in the specified list of valid OSes: " . $this->os,
645 2
                Project::MSG_VERBOSE
646
            );
647 2
            return false;
648
        }
649 48
        return true;
650
    }
651
652
    /**
653
     * Set whether to attempt to resolve the executable to a file.
654
     *
655
     * @param bool $resolveExecutable if true, attempt to resolve the
656
     * path of the executable.
657
     */
658 1
    public function setResolveExecutable($resolveExecutable): void
659
    {
660 1
        $this->resolveExecutable = $resolveExecutable;
661 1
    }
662
663
    /**
664
     * Set whether to search nested, then
665
     * system PATH environment variables for the executable.
666
     *
667
     * @param bool $searchPath if true, search PATHs.
668
     */
669 1
    public function setSearchPath($searchPath): void
670
    {
671 1
        $this->searchPath = $searchPath;
672 1
    }
673
674
    /**
675
     * Indicates whether to attempt to resolve the executable to a
676
     * file.
677
     *
678
     * @return bool the resolveExecutable flag
679
     */
680
    public function getResolveExecutable(): bool
681
    {
682
        return $this->resolveExecutable;
683
    }
684
685
    /**
686
     * The method attempts to figure out where the executable is so that we can feed
687
     * the full path. We first try basedir, then the exec dir, and then
688
     * fallback to the straight executable name (i.e. on the path).
689
     *
690
     * @param string $exec the name of the executable.
691
     * @param bool $mustSearchPath if true, the executable will be looked up in
692
     *                               the PATH environment and the absolute path
693
     *                               is returned.
694
     *
695
     * @return string the executable as a full path if it can be determined.
696
     * @throws BuildException
697
     * @throws IOException
698
     * @throws NullPointerException
699
     */
700 30
    protected function resolveExecutable($exec, $mustSearchPath): ?string
701
    {
702 30
        if (!$this->resolveExecutable) {
703 29
            return $exec;
704
        }
705
        // try to find the executable
706 1
        $executableFile = $this->getProject()->resolveFile($exec);
707 1
        if ($executableFile->exists()) {
708
            return $executableFile->getAbsolutePath();
709
        }
710
        // now try to resolve against the dir if given
711 1
        if ($this->dir !== null) {
712
            $executableFile = (new FileUtils())->resolveFile($this->dir, $exec);
713
            if ($executableFile->exists()) {
714
                return $executableFile->getAbsolutePath();
715
            }
716
        }
717
        // couldn't find it - must be on path
718 1
        if ($mustSearchPath) {
719 1
            $p = null;
720 1
            $environment = $this->env->getVariables();
721 1
            if ($environment !== null) {
0 ignored issues
show
introduced by
The condition $environment !== null is always true.
Loading history...
722 1
                foreach ($environment as $env) {
723 1
                    if ($this->isPath($env)) {
724 1
                        $p = new Path($this->getProject(), $this->getPath($env));
725 1
                        break;
726
                    }
727
                }
728
            }
729 1
            if ($p === null) {
730
                $p = new Path($this->getProject(), getenv('path'));
731
            }
732 1
            if ($p !== null) {
733 1
                $dirs = $p->listPaths();
734 1
                foreach ($dirs as $dir) {
735 1
                    $executableFile = (new FileUtils())->resolveFile(new File($dir), $exec);
736 1
                    if ($executableFile->exists()) {
737 1
                        return $executableFile->getAbsolutePath();
738
                    }
739
                }
740
            }
741
        }
742
743
        return $exec;
744
    }
745
746 3
    private function isPath($line)
747
    {
748 3
        return StringHelper::startsWith('PATH=', $line) || StringHelper::startsWith('Path=', $line);
749
    }
750
751 1
    private function getPath($value)
752
    {
753 1
        if (is_string($value)) {
754 1
            return StringHelper::substring($value, strlen("PATH="));
755
        }
756
757
        if (is_array($value)) {
758
            $p = $value['PATH'];
759
            return $p ?? $value['Path'];
760
        }
761
762
        throw new InvalidArgumentException('$value should be of type array or string.');
763
    }
764
}
765