SelfTestCliRuntime   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 30
c 3
b 0
f 0
lcom 1
cbo 5
dl 0
loc 222
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A doTest() 0 19 3
A isBinaryAvailableFromConstants() 0 9 3
A isBinaryAvailableInPath() 0 14 4
A isAnyBinaryValid() 0 19 4
C findBinaries() 0 43 8
A filterBaseDir() 0 11 3
A getDefaultPaths() 0 15 2
A testCliRuntime() 0 23 3
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\SelfTest\Cli;
22
23
use Symfony\Component\Console\Output\BufferedOutput;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\Process\Process;
26
use Tenside\Core\SelfTest\AbstractSelfTest;
27
28
/**
29
 * This class tests that a valid php-cli binary is available.
30
 */
31
class SelfTestCliRuntime extends AbstractSelfTest
32
{
33
    /**
34
     * The output buffer to keep track of the detection.
35
     *
36
     * @var BufferedOutput
37
     */
38
    private $log;
39
40
    /**
41
     * Check that we have a correct CLI executable of PHP.
42
     *
43
     * @return void
44
     */
45
    public function doTest()
46
    {
47
        $this->setMessage('Check if a valid PHP CLI executable is available.');
48
49
        $this->log = new BufferedOutput();
50
51
        // Prefer compile time configuration over path environment.
52
        if ($this->isBinaryAvailableFromConstants()) {
53
            return;
54
        }
55
56
        if ($this->isBinaryAvailableInPath()) {
57
            return;
58
        }
59
60
        $this->markFailed(
61
            'Could not find any PHP CLI executable, running tenside tasks will not work. ' . $this->log->fetch()
62
        );
63
    }
64
65
    /**
66
     * Check if any usable php executable is available from the internal constants.
67
     *
68
     * @return bool
69
     */
70
    private function isBinaryAvailableFromConstants()
71
    {
72
        // Prefer the cli version of the running php instance.
73
        if (('' === PHP_BINARY) || !file_exists(PHP_BINARY)) {
74
            return false;
75
        }
76
77
        return $this->isAnyBinaryValid([PHP_BINARY]);
78
    }
79
80
    /**
81
     * Check if any php executable is in the path.
82
     *
83
     * @return bool
84
     */
85
    private function isBinaryAvailableInPath()
86
    {
87
        $paths = array_filter(array_map('trim', explode(PATH_SEPARATOR, getenv('PATH'))));
88
        if (empty($paths)) {
89
            $paths = $this->getDefaultPaths();
90
        }
91
92
        // Prefer the cli version of the running php instance.
93
        if ((PHP_BINDIR !== '') && is_dir(PHP_BINDIR)) {
94
            array_unshift($paths, PHP_BINDIR);
95
        }
96
97
        return $this->isAnyBinaryValid($this->findBinaries($paths));
98
    }
99
100
    /**
101
     * Test the passed binaries.
102
     *
103
     * @param string[] $binaries The binaries to test.
104
     *
105
     * @return bool
106
     */
107
    private function isAnyBinaryValid($binaries)
108
    {
109
        foreach ($binaries as $binary) {
110
            if ($version = $this->testCliRuntime($binary)) {
111
                if (version_compare($version, '5.6', '<')) {
112
                    $this->log->writeln(sprintf('%s version is too low (%s)', $binary, $version));
113
114
                    return false;
115
                }
116
117
                $this->markSuccess('Found ' . $binary . ' (Version: ' . $version . ')');
118
                $this->getAutoConfig()->setPhpCliBinary($binary);
119
120
                return true;
121
            }
122
        }
123
124
        return false;
125
    }
126
127
    /**
128
     * Search all php binaries from the passed paths.
129
     *
130
     * @param string[] $paths     The paths to scan for binaries.
131
     *
132
     * @param string[] $fileNames Optional names of files to search for.
133
     *
134
     * @return string[]
135
     */
136
    private function findBinaries($paths, $fileNames = ['php', 'php-cli', 'php.exe'])
137
    {
138
        // We have to work around the problem that the symfony Finder will try to follow the symlink when a file
139
        // i.e. /var/bin/foo is symlinked to /usr/bin/foo and therefore raise a warning that /var/bin is not in
140
        // the open_basedir locations.
141
        // Therefore we can not use the Finder component when open_basedir has been set.
142
        if ($baseDirs = array_filter(array_map('trim', explode(PATH_SEPARATOR, ini_get('open_basedir'))))) {
143
            $foundBinaries = [];
144
145
            foreach ($this->filterBaseDir($paths, $baseDirs) as $path) {
146
                foreach (scandir($path) as $file) {
147
                    if (in_array(basename($file), $fileNames)) {
148
                        $foundBinaries[] = new \SplFileInfo($path . DIRECTORY_SEPARATOR . $file);
149
                    }
150
                }
151
            }
152
153
            return $foundBinaries;
154
        }
155
156
        if (empty($paths)) {
157
            return [];
158
        }
159
160
        $paths = array_filter($paths, function($path) {
161
            return is_dir($path);
162
        });
163
164
        $finder = new Finder();
165
        $finder->ignoreUnreadableDirs()->in($paths);
166
167
        foreach ($fileNames as $name) {
168
            $finder->name($name);
169
        }
170
171
        $foundBinaries = [];
172
        foreach ($finder as $file) {
173
            /** @var \SplFileInfo $file */
174
            $foundBinaries[] = $file->getPathname();
175
        }
176
177
        return $foundBinaries;
178
    }
179
180
    /**
181
     * Filter out the paths not covered by basedir.
182
     *
183
     * @param string[] $paths    The paths to filter.
184
     *
185
     * @param string[] $baseDirs The base dir paths.
186
     *
187
     * @return string[]
188
     */
189
    private function filterBaseDir($paths, $baseDirs)
190
    {
191
        return array_filter($paths, function ($path) use ($baseDirs) {
192
            foreach ($baseDirs as $baseDir) {
193
                if (substr($baseDir, 0, strlen($path)) === $path) {
194
                    return true;
195
                }
196
            }
197
            return false;
198
        });
199
    }
200
201
    /**
202
     * Test the cli runtime for a valid version string and return either the version or null.
203
     *
204
     * @param string $binary The binary to test.
205
     *
206
     * @return null|string
207
     */
208
    private function testCliRuntime($binary)
209
    {
210
        $process = new Process(
211
            sprintf(
212
                '%s %s',
213
                escapeshellcmd($binary),
214
                escapeshellarg('--version')
215
            )
216
        );
217
218
        if (0 !== $process->run()) {
219
            return null;
220
        }
221
222
        // Examples for version output, add other examples here if the regex must get altered:
223
        // "PHP 5.6.22-0+deb8u1 (cli)" (obtained from Debian jessie)
224
        // "PHP 7.0.8-1~dotdeb+8.1 (cli) ( NTS )" (obtained from Debian jessie)
225
        if (!preg_match('#.*PHP ([0-9a-zA-Z\.\-\+\~]+) \(cli\)#', $process->getOutput(), $output)) {
226
            return null;
227
        }
228
229
        return $output[1];
230
    }
231
232
    /**
233
     * Retrieve the list of default paths for the current OS.
234
     *
235
     * @return string[]
236
     */
237
    private function getDefaultPaths()
238
    {
239
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
240
            return [
241
                'C:\php',
242
                'C:\php5'
243
            ];
244
        }
245
246
        return [
247
            '/usr/local/bin',
248
            '/usr/bin',
249
            '/bin',
250
        ];
251
    }
252
}
253