mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-10-23 13:45:06 +00:00
143 lines
6.5 KiB
PHP
143 lines
6.5 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file system/App/Common/CliRuntime.php
|
|
| @author AVE.cms <support@ave-cms.ru>
|
|
| @copyright 2007-2026 (c) AVE.cms
|
|
| @link https://ave-cms.ru
|
|
| @version 3.3
|
|
*/
|
|
|
|
namespace App\Common;
|
|
|
|
defined('BASEPATH') || die('Direct access to this location is not allowed.');
|
|
|
|
class CliRuntime
|
|
{
|
|
public static function php(array $preferred = array(), $minimumVersion = 70300)
|
|
{
|
|
if (!function_exists('proc_open')) { throw new \RuntimeException('proc_open недоступен: запуск PHP CLI невозможен.'); }
|
|
$runtime = array(
|
|
'binary' => defined('PHP_BINARY') ? PHP_BINARY : '',
|
|
'bindir' => defined('PHP_BINDIR') ? PHP_BINDIR : '',
|
|
'ini' => php_ini_loaded_file() ?: '',
|
|
'path' => (string) getenv('PATH'),
|
|
'windows' => DIRECTORY_SEPARATOR === '\\',
|
|
'version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
|
|
);
|
|
$preferred[] = trim((string) getenv('PHP_CLI_BINARY'));
|
|
foreach (self::phpCandidates($preferred, $runtime) as $candidate) {
|
|
if (!self::isPhpName($candidate) || !is_file($candidate) || (!$runtime['windows'] && !is_executable($candidate))) { continue; }
|
|
if (self::probe($candidate) >= (int) $minimumVersion) { return $candidate; }
|
|
}
|
|
|
|
$version = (int) floor((int) $minimumVersion / 10000) . '.' . (int) floor(((int) $minimumVersion % 10000) / 100);
|
|
throw new \RuntimeException('Консольный PHP ' . $version . ' или новее не найден. Укажите полный путь к php или php.exe в PHP_CLI_BINARY.');
|
|
}
|
|
|
|
public static function findCommand($name, array $preferred = array())
|
|
{
|
|
if (!preg_match('/^[A-Za-z0-9_.-]+$/', (string) $name)) { throw new \InvalidArgumentException('Invalid command name'); }
|
|
$windows = DIRECTORY_SEPARATOR === '\\';
|
|
$extensions = $windows ? explode(';', (string) getenv('PATHEXT')) : array();
|
|
$paths = explode($windows ? ';' : ':', (string) getenv('PATH'));
|
|
foreach (self::commandCandidates($name, $paths, $extensions, $windows, $preferred) as $candidate) {
|
|
if (is_file($candidate) && ($windows || is_executable($candidate))) { return $candidate; }
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
protected static function phpCandidates(array $preferred, array $runtime)
|
|
{
|
|
$windows = !empty($runtime['windows']);
|
|
$separator = $windows ? '\\' : '/';
|
|
$name = $windows ? 'php.exe' : 'php';
|
|
$out = $preferred;
|
|
if (self::isPhpName($runtime['binary'])) { $out[] = $runtime['binary']; }
|
|
foreach (array($runtime['bindir'], dirname(str_replace('\\', '/', (string) $runtime['ini'])), dirname(str_replace('\\', '/', (string) $runtime['binary']))) as $directory) {
|
|
if ($directory === '' || $directory === '.') { continue; }
|
|
$directory = $windows ? str_replace('/', '\\', $directory) : $directory;
|
|
$out[] = rtrim($directory, '/\\') . $separator . $name;
|
|
$out[] = rtrim($directory, '/\\') . $separator . 'bin' . $separator . $name;
|
|
}
|
|
|
|
$paths = explode($windows ? ';' : ':', (string) $runtime['path']);
|
|
$out = array_merge($out, self::commandCandidates($name, $paths, array(), $windows));
|
|
if ($windows && preg_match('~^(.*/modules)/~i', str_replace('\\', '/', (string) $runtime['binary']), $match)) {
|
|
$found = array();
|
|
foreach (array('/php/*/php.exe', '/php/*/bin/php.exe', '/PHP*/php.exe', '/PHP*/bin/php.exe') as $suffix) {
|
|
$found = array_merge($found, glob($match[1] . $suffix) ?: array());
|
|
}
|
|
|
|
$version = isset($runtime['version']) ? preg_quote($runtime['version'], '~') : '';
|
|
if ($version !== '') {
|
|
$version = str_replace('\\.', '[._-]', $version);
|
|
usort($found, function ($a, $b) use ($version) { return (int) preg_match('~' . $version . '~', $b) - (int) preg_match('~' . $version . '~', $a); });
|
|
}
|
|
|
|
$out = array_merge($out, $found);
|
|
}
|
|
|
|
return array_values(array_unique(array_filter($out, function ($candidate) { return trim((string) $candidate) !== ''; })));
|
|
}
|
|
|
|
protected static function commandCandidates($name, array $paths, array $extensions, $windows, array $preferred = array())
|
|
{
|
|
$suffixes = array('');
|
|
if ($windows && pathinfo((string) $name, PATHINFO_EXTENSION) === '') {
|
|
foreach (array_merge($extensions, array('.EXE', '.COM', '.BAT', '.CMD')) as $extension) {
|
|
$extension = trim((string) $extension);
|
|
if (preg_match('/^\.[A-Za-z0-9]+$/', $extension)) { $suffixes[] = strtolower($extension); }
|
|
}
|
|
}
|
|
|
|
$out = $preferred;
|
|
foreach ($paths as $directory) {
|
|
$directory = trim((string) $directory, " \t\n\r\0\x0B\"");
|
|
if ($directory === '') { continue; }
|
|
foreach (array_unique($suffixes) as $suffix) { $out[] = rtrim($directory, '/\\') . ($windows ? '\\' : '/') . $name . $suffix; }
|
|
}
|
|
|
|
return array_values(array_unique($out));
|
|
}
|
|
|
|
protected static function isPhpName($path)
|
|
{
|
|
return preg_match('/^php(?:[0-9]+(?:\.[0-9]+)*)?(?:\.exe)?$/i', basename(str_replace('\\', '/', (string) $path))) === 1;
|
|
}
|
|
|
|
protected static function probe($binary)
|
|
{
|
|
$command = escapeshellarg((string) $binary) . ' -n -r ' . escapeshellarg('echo PHP_SAPI,chr(124),PHP_VERSION_ID;');
|
|
$pipes = array();
|
|
try {
|
|
$process = @proc_open($command, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes, null, null, array('bypass_shell' => DIRECTORY_SEPARATOR === '\\'));
|
|
} catch (\Throwable $e) { return 0; }
|
|
if (!is_resource($process)) { return 0; }
|
|
fclose($pipes[0]);
|
|
stream_set_blocking($pipes[1], false);
|
|
stream_set_blocking($pipes[2], false);
|
|
$start = microtime(true); $stdout = ''; $stderr = ''; $exitCode = null; $timedOut = false;
|
|
do {
|
|
$stdout .= (string) stream_get_contents($pipes[1]);
|
|
$stderr .= (string) stream_get_contents($pipes[2]);
|
|
$status = proc_get_status($process);
|
|
if (!$status['running']) { $exitCode = (int) $status['exitcode']; break; }
|
|
if (microtime(true) - $start >= 2 || strlen($stdout) + strlen($stderr) > 8192) { $timedOut = true; proc_terminate($process, 9); break; }
|
|
usleep(20000);
|
|
} while (true);
|
|
$stdout .= (string) stream_get_contents($pipes[1]);
|
|
$stderr .= (string) stream_get_contents($pipes[2]);
|
|
fclose($pipes[1]); fclose($pipes[2]);
|
|
$closed = proc_close($process);
|
|
$exitCode = $exitCode !== null && $exitCode >= 0 ? $exitCode : $closed;
|
|
if ($timedOut || $exitCode !== 0 || trim($stderr) !== '' || !preg_match('/^cli\|([0-9]{5,6})$/', trim($stdout), $match)) { return 0; }
|
|
return (int) $match[1];
|
|
}
|
|
}
|