Completed
Push — master ( dca855...d1b9f7 )
by Christian
06:41
created

SelfTestCliRuntime   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 30
c 1
b 0
f 0
lcom 1
cbo 5
dl 0
loc 216
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 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
        // 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.4', '<')) {
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()->setPhpInterpreter($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.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
        $finder = new Finder();
161
        $finder->in($paths);
162
163
        foreach ($fileNames as $name) {
164
            $finder->name($name);
165
        }
166
167
        $foundBinaries = [];
168
        foreach ($finder as $file) {
169
            /** @var \SplFileInfo $file */
170
            $foundBinaries[] = $file->getPathname();
171
        }
172
173
        return $foundBinaries;
174
    }
175
176
    /**
177
     * Filter out the paths not covered by basedir.
178
     *
179
     * @param string[] $paths    The paths to filter.
180
     *
181
     * @param string[] $baseDirs The base dir paths.
182
     *
183
     * @return string[]
184
     */
185
    private function filterBaseDir($paths, $baseDirs)
186
    {
187
        return array_filter($paths, function ($path) use ($baseDirs) {
188
            foreach ($baseDirs as $baseDir) {
189
                if (substr($baseDir, 0, strlen($path)) === $path) {
190
                    return true;
191
                }
192
            }
193
            return false;
194
        });
195
    }
196
197
    /**
198
     * Test the cli runtime for a valid version string and return either the version or null.
199
     *
200
     * @param string $binary The binary to test.
201
     *
202
     * @return null|string
203
     */
204
    private function testCliRuntime($binary)
205
    {
206
        $process = new Process(
207
            sprintf(
208
                '%s %s',
209
                escapeshellcmd($binary),
210
                escapeshellarg('--version')
211
            )
212
        );
213
214
        if (0 !== $process->run()) {
215
            return null;
216
        }
217
218
        // Version PHP 5.4.45-0+deb7u1
219
        if (!preg_match('#.*PHP ([0-9a-zA-Z\.\-\+]+) \(cli\)#', $process->getOutput(), $output)) {
220
            return null;
221
        }
222
223
        return $output[1];
224
    }
225
226
    /**
227
     * Retrieve the list of default paths for the current OS.
228
     *
229
     * @return string[]
230
     */
231
    private function getDefaultPaths()
232
    {
233
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
234
            return [
235
                'C:\php',
236
                'C:\php5'
237
            ];
238
        }
239
240
        return [
241
            '/usr/local/bin',
242
            '/usr/bin',
243
            '/bin',
244
        ];
245
    }
246
}
247