BaseCommandExecutor::findBinary()   D
last analyzed

Complexity

Conditions 9
Paths 16

Size

Total Lines 42
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 10.2655

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 42
ccs 18
cts 24
cp 0.75
rs 4.909
cc 9
eloc 22
nc 16
nop 2
crap 10.2655
1
<?php
2
/**
3
 * PHPCI - Continuous Integration for PHP.
4
 *
5
 * @copyright    Copyright 2014, Block 8 Limited.
6
 * @license      https://github.com/Block8/PHPCI/blob/master/LICENSE.md
7
 *
8
 * @link         https://www.phptesting.org/
9
 */
10
11
namespace PHPCI\Helper;
12
13
use Exception;
14
use PHPCI\Logging\BuildLogger;
15
use Psr\Log\LogLevel;
16
17
/**
18
 * Handles running system commands with variables.
19
 */
20
abstract class BaseCommandExecutor implements CommandExecutor
21
{
22
    /**
23
     * @var BuildLogger
24
     */
25
    protected $logger;
26
27
    /**
28
     * @var bool
29
     */
30
    protected $quiet;
31
32
    /**
33
     * @var bool
34
     */
35
    protected $verbose;
36
37
    protected $lastOutput;
38
    protected $lastError;
39
40
    public $logExecOutput = true;
41
42
    /**
43
     * The path which findBinary will look in.
44
     *
45
     * @var string
46
     */
47
    protected $rootDir;
48
49
    /**
50
     * Current build path.
51
     *
52
     * @var string
53
     */
54
    protected $buildPath;
55
56
    /**
57
     * @param BuildLogger $logger
58
     * @param string      $rootDir
59
     * @param bool        $quiet
60
     * @param bool        $verbose
61
     */
62 7
    public function __construct(BuildLogger $logger, $rootDir, &$quiet = false, &$verbose = false)
63
    {
64 7
        $this->logger = $logger;
65 7
        $this->quiet = $quiet;
66 7
        $this->verbose = $verbose;
67 7
        $this->lastOutput = array();
68 7
        $this->rootDir = $rootDir;
69 7
    }
70
71
    /**
72
     * Executes shell commands.
73
     *
74
     * @param array $args
75
     *
76
     * @return bool Indicates success
77
     */
78 4
    public function executeCommand($args = array())
79
    {
80 4
        $this->lastOutput = array();
81
82 4
        $command = call_user_func_array('sprintf', $args);
83
84 4
        if ($this->quiet) {
85
            $this->logger->log('Executing: '.$command);
86
        }
87
88 4
        $status = 0;
89
        $descriptorSpec = array(
90 4
            0 => array('pipe', 'r'),  // stdin
91 4
            1 => array('pipe', 'w'),  // stdout
92 4
            2 => array('pipe', 'w'),  // stderr
93 4
        );
94
95 4
        $pipes = array();
96
97 4
        $process = proc_open($command, $descriptorSpec, $pipes, $this->buildPath, null);
98
99 4
        if (is_resource($process)) {
100 4
            fclose($pipes[0]);
101
102 4
            $this->lastOutput = stream_get_contents($pipes[1]);
103 4
            $this->lastError = stream_get_contents($pipes[2]);
104
105 4
            fclose($pipes[1]);
106 4
            fclose($pipes[2]);
107
108 4
            $status = proc_close($process);
109 4
        }
110
111 4
        $this->lastOutput = array_filter(explode(PHP_EOL, $this->lastOutput));
112
113 4
        $shouldOutput = ($this->logExecOutput && ($this->verbose || $status != 0));
114
115 4
        if ($shouldOutput && !empty($this->lastOutput)) {
116
            $this->logger->log($this->lastOutput);
117
        }
118
119 4
        if (!empty($this->lastError)) {
120
            $this->logger->log("\033[0;31m".$this->lastError."\033[0m", LogLevel::ERROR);
121
        }
122
123 4
        $rtn = false;
124
125 4
        if ($status == 0) {
126 3
            $rtn = true;
127 3
        }
128
129 4
        return $rtn;
130
    }
131
132
    /**
133
     * Returns the output from the last command run.
134
     */
135 2
    public function getLastOutput()
136
    {
137 2
        return implode(PHP_EOL, $this->lastOutput);
138
    }
139
140
    /**
141
     * Returns the stderr output from the last command run.
142
     */
143
    public function getLastError()
144
    {
145
        return $this->lastError;
146
    }
147
148
    /**
149
     * Find a binary required by a plugin.
150
     *
151
     * @param string $binary
152
     * @param bool   $quiet
153
     *
154
     * @return null|string
155
     */
156 3
    public function findBinary($binary, $quiet = false)
157
    {
158 3
        $composerBin = $this->getComposerBinDir(realpath($this->buildPath));
159
160 3
        if (is_string($binary)) {
161 3
            $binary = array($binary);
162 3
        }
163
164 3
        foreach ($binary as $bin) {
165 3
            $this->logger->log(Lang::get('looking_for_binary', $bin), LogLevel::DEBUG);
166
167 3
            if (is_dir($composerBin) && is_file($composerBin.'/'.$bin)) {
168
                $this->logger->log(Lang::get('found_in_path', $composerBin, $bin), LogLevel::DEBUG);
169
170
                return $composerBin.'/'.$bin;
171
            }
172
173 3
            if (is_file($this->rootDir.$bin)) {
174 1
                $this->logger->log(Lang::get('found_in_path', 'root', $bin), LogLevel::DEBUG);
175
176 1
                return $this->rootDir.$bin;
177
            }
178
179 2
            if (is_file($this->rootDir.'vendor/bin/'.$bin)) {
180
                $this->logger->log(Lang::get('found_in_path', 'vendor/bin', $bin), LogLevel::DEBUG);
181
182
                return $this->rootDir.'vendor/bin/'.$bin;
183
            }
184
185 2
            $findCmdResult = $this->findGlobalBinary($bin);
186 2
            if (is_file($findCmdResult)) {
187
                $this->logger->log(Lang::get('found_in_path', '', $bin), LogLevel::DEBUG);
188
189
                return $findCmdResult;
190
            }
191 2
        }
192
193 2
        if ($quiet) {
194 1
            return;
195
        }
196 1
        throw new Exception(Lang::get('could_not_find', implode('/', $binary)));
197
    }
198
199
    /**
200
     * Find a binary which is installed globally on the system.
201
     *
202
     * @param string $binary
203
     *
204
     * @return null|string
205
     */
206
    abstract protected function findGlobalBinary($binary);
207
208
    /**
209
     * Try to load the composer.json file in the building project
210
     * If the bin-dir is configured, return the full path to it.
211
     *
212
     * @param string $path Current build path
213
     *
214
     * @return string|null
215
     */
216 3
    public function getComposerBinDir($path)
217
    {
218 3
        if (is_dir($path)) {
219 3
            $composer = $path.'/composer.json';
220 3
            if (is_file($composer)) {
221 3
                $json = json_decode(file_get_contents($composer));
222
223 3
                if (isset($json->config->{'bin-dir'})) {
224
                    return $path.'/'.$json->config->{'bin-dir'};
225 3
                } elseif (is_dir($path.'/vendor/bin')) {
226 3
                    return $path.'/vendor/bin';
227
                }
228
            }
229
        }
230
231
        return;
232
    }
233
234
    /**
235
     * Set the buildPath property.
236
     *
237
     * @param string $path
238
     */
239
    public function setBuildPath($path)
240
    {
241
        $this->buildPath = $path;
242
    }
243
}
244