Completed
Push — master ( 622c37...dca855 )
by Christian
04:12
created

SelfTestCliRuntime   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 24
c 5
b 0
f 1
lcom 1
cbo 5
dl 0
loc 191
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A doTest() 0 14 2
A isBinaryAvailableInPath() 0 9 2
A isAnyBinaryValid() 0 19 4
C findBinaries() 0 39 8
A filterBaseDir() 0 11 3
A testCliRuntime() 0 21 3
A getDefaultPaths() 0 15 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\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
        if ($this->isBinaryAvailableInPath()) {
52
            return;
53
        }
54
55
        $this->markFailed(
56
            'Could not find any PHP CLI executable, running tenside tasks will not work. ' . $this->log->fetch()
57
        );
58
    }
59
60
    /**
61
     * Check if any php executable is in the path.
62
     *
63
     * @return bool
64
     */
65
    private function isBinaryAvailableInPath()
66
    {
67
        $paths = array_filter(array_map('trim', explode(PATH_SEPARATOR, getenv('PATH'))));
68
        if (empty($paths)) {
69
            $paths = $this->getDefaultPaths();
70
        }
71
72
        return $this->isAnyBinaryValid($this->findBinaries($paths));
73
    }
74
75
    /**
76
     * Test the passed binaries.
77
     *
78
     * @param string[] $binaries The binaries to test.
79
     *
80
     * @return bool
81
     */
82
    private function isAnyBinaryValid($binaries)
83
    {
84
        foreach ($binaries as $binary) {
85
            if ($version = $this->testCliRuntime($binary)) {
86
                if (version_compare($version, '5.4', '<')) {
87
                    $this->log->writeln(sprintf('%s version is too low (%s)', $binary, $version));
88
89
                    return false;
90
                }
91
92
                $this->markSuccess('Found ' . $binary . ' (Version: ' . $version . ')');
93
                $this->getAutoConfig()->setPhpInterpreter($binary);
94
95
                return true;
96
            }
97
        }
98
99
        return false;
100
    }
101
102
    /**
103
     * Search all php binaries from the passed paths.
104
     *
105
     * @param string[] $paths     The paths to scan for binaries.
106
     *
107
     * @param string[] $fileNames Optional names of files to search for.
108
     *
109
     * @return string[]
110
     */
111
    private function findBinaries($paths, $fileNames = ['php', 'php.exe'])
112
    {
113
        // We have to work around the problem that the symfony Finder will try to follow the symlink when a file
114
        // i.e. /var/bin/foo is symlinked to /usr/bin/foo and therefore raise a warning that /var/bin is not in
115
        // the open_basedir locations.
116
        // Therefore we can not use the Finder component when open_basedir has been set.
117
        if ($baseDirs = array_filter(array_map('trim', explode(PATH_SEPARATOR, ini_get('open_basedir'))))) {
118
            $foundBinaries = [];
119
120
            foreach ($this->filterBaseDir($paths, $baseDirs) as $path) {
121
                foreach (scandir($path) as $file) {
122
                    if (in_array(basename($file), $fileNames)) {
123
                        $foundBinaries[] = new \SplFileInfo($path . DIRECTORY_SEPARATOR . $file);
124
                    }
125
                }
126
            }
127
128
            return $foundBinaries;
129
        }
130
131
        if (empty($paths)) {
132
            return [];
133
        }
134
135
        $finder = new Finder();
136
        $finder->in($paths);
137
138
        foreach ($fileNames as $name) {
139
            $finder->name($name);
140
        }
141
142
        $foundBinaries = [];
143
        foreach ($finder as $file) {
144
            /** @var \SplFileInfo $file */
145
            $foundBinaries[] = $file->getPathname();
146
        }
147
148
        return $foundBinaries;
149
    }
150
151
    /**
152
     * Filter out the paths not covered by basedir.
153
     *
154
     * @param string[] $paths    The paths to filter.
155
     *
156
     * @param string[] $baseDirs The base dir paths.
157
     *
158
     * @return string[]
159
     */
160
    private function filterBaseDir($paths, $baseDirs)
161
    {
162
        return array_filter($paths, function ($path) use ($baseDirs) {
163
            foreach ($baseDirs as $baseDir) {
164
                if (substr($baseDir, 0, strlen($path)) === $path) {
165
                    return true;
166
                }
167
            }
168
            return false;
169
        });
170
    }
171
172
    /**
173
     * Test the cli runtime for a valid version string and return either the version or null.
174
     *
175
     * @param string $binary The binary to test.
176
     *
177
     * @return null|string
178
     */
179
    private function testCliRuntime($binary)
180
    {
181
        $process = new Process(
182
            sprintf(
183
                '%s %s',
184
                escapeshellcmd($binary),
185
                escapeshellarg('--version')
186
            )
187
        );
188
189
        if (0 !== $process->run()) {
190
            return null;
191
        }
192
193
        // Version PHP 5.4.45-0+deb7u1
194
        if (!preg_match('#.*PHP ([0-9a-zA-Z\.\-\+]+) \(cli\)#', $process->getOutput(), $output)) {
195
            return null;
196
        }
197
198
        return $output[1];
199
    }
200
201
    /**
202
     * Retrieve the list of default paths for the current OS.
203
     *
204
     * @return string[]
205
     */
206
    private function getDefaultPaths()
207
    {
208
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
209
            return [
210
                'C:\php',
211
                'C:\php5'
212
            ];
213
        }
214
215
        return [
216
            '/usr/local/bin',
217
            '/usr/bin',
218
            '/bin',
219
        ];
220
    }
221
}
222