mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-07 16:55:38 +08:00
v3 base
This commit is contained in:
162
src/StaticPHP/Util/SourcePatcher.php
Normal file
162
src/StaticPHP/Util/SourcePatcher.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util;
|
||||
|
||||
use StaticPHP\Attribute\PatchDescription;
|
||||
use StaticPHP\Exception\PatchException;
|
||||
|
||||
/**
|
||||
* SourcePatcher provides static utility methods for patching source files.
|
||||
*/
|
||||
class SourcePatcher
|
||||
{
|
||||
/**
|
||||
* Use existing patch file for patching.
|
||||
*
|
||||
* @param string $patch_name Patch file name in src/globals/patch/ or absolute path
|
||||
* @param string $cwd Working directory for patch command
|
||||
* @param bool $reverse Reverse patches (default: false)
|
||||
* @return bool True if patch was applied or already applied
|
||||
*/
|
||||
public static function patchFile(string $patch_name, string $cwd, bool $reverse = false): bool
|
||||
{
|
||||
logger()->debug('Applying ' . ($reverse ? 'reverse ' : '') . "patch [{$patch_name}] at [{$cwd}]");
|
||||
if (FileSystem::isRelativePath($patch_name)) {
|
||||
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}";
|
||||
} else {
|
||||
$patch_file = $patch_name;
|
||||
}
|
||||
|
||||
if (!file_exists($patch_file)) {
|
||||
throw new PatchException($patch_name, "Patch file [{$patch_file}] does not exist");
|
||||
}
|
||||
|
||||
$patch_str = FileSystem::convertPath($patch_file);
|
||||
if (!file_exists($patch_str)) {
|
||||
throw new PatchException($patch_name, "Patch file [{$patch_str}] does not exist");
|
||||
}
|
||||
|
||||
// Copy patch from phar
|
||||
if (str_starts_with($patch_str, 'phar://')) {
|
||||
$filename = pathinfo($patch_file, PATHINFO_BASENAME);
|
||||
file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file));
|
||||
$patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}");
|
||||
}
|
||||
|
||||
// Detect if patch is already applied (reverse detection)
|
||||
$detect_reverse = !$reverse;
|
||||
$detect_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
|
||||
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
|
||||
. ' | patch --dry-run -p1 -s -f ' . ($detect_reverse ? '-R' : '')
|
||||
. ' > ' . (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null') . ' 2>&1';
|
||||
exec($detect_cmd, $output, $detect_status);
|
||||
|
||||
if ($detect_status === 0) {
|
||||
// Patch already applied
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply patch
|
||||
$apply_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
|
||||
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
|
||||
. ' | patch -p1 ' . ($reverse ? '-R' : '');
|
||||
|
||||
exec($apply_cmd, $apply_output, $apply_status);
|
||||
if ($apply_status !== 0) {
|
||||
throw new PatchException($patch_name, "Patch file [{$patch_name}] failed to apply");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch hardcoded INI values into PHP SAPI files.
|
||||
*
|
||||
* @param string $php_source_dir PHP source directory path
|
||||
* @param array $ini Associative array of INI key-value pairs
|
||||
* @return bool True if patch was applied successfully
|
||||
*/
|
||||
#[PatchDescription('Patch hardcoded INI values into PHP SAPI files')]
|
||||
public static function patchHardcodedINI(string $php_source_dir, array $ini = []): bool
|
||||
{
|
||||
$sapi_files = [
|
||||
'cli' => "{$php_source_dir}/sapi/cli/php_cli.c",
|
||||
'micro' => "{$php_source_dir}/sapi/micro/php_micro.c",
|
||||
'embed' => "{$php_source_dir}/sapi/embed/php_embed.c",
|
||||
];
|
||||
|
||||
// Build patch string
|
||||
$find_str = 'const char HARDCODED_INI[] =';
|
||||
$patch_str = '';
|
||||
foreach ($ini as $key => $value) {
|
||||
$patch_str .= "\"{$key}={$value}\\n\"\n";
|
||||
}
|
||||
$patch_str = "const char HARDCODED_INI[] =\n{$patch_str}";
|
||||
|
||||
// Detect and restore from backup if exists
|
||||
$has_backup = false;
|
||||
foreach ($sapi_files as $file) {
|
||||
if (file_exists("{$file}.bak")) {
|
||||
$has_backup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($has_backup) {
|
||||
self::unpatchHardcodedINI($php_source_dir);
|
||||
}
|
||||
|
||||
// Backup and patch each SAPI file
|
||||
$result = true;
|
||||
foreach ($sapi_files as $file) {
|
||||
if (!file_exists($file)) {
|
||||
continue;
|
||||
}
|
||||
// Backup
|
||||
$result = $result && file_put_contents("{$file}.bak", file_get_contents($file)) !== false;
|
||||
// Patch
|
||||
FileSystem::replaceFileStr($file, $find_str, $patch_str);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore PHP SAPI files from backup (unpatch hardcoded INI).
|
||||
*
|
||||
* @param string $php_source_dir PHP source directory path
|
||||
* @return bool True if backup was restored successfully
|
||||
*/
|
||||
public static function unpatchHardcodedINI(string $php_source_dir): bool
|
||||
{
|
||||
$sapi_files = [
|
||||
'cli' => "{$php_source_dir}/sapi/cli/php_cli.c",
|
||||
'micro' => "{$php_source_dir}/sapi/micro/php_micro.c",
|
||||
'embed' => "{$php_source_dir}/sapi/embed/php_embed.c",
|
||||
];
|
||||
|
||||
$has_backup = false;
|
||||
foreach ($sapi_files as $file) {
|
||||
if (file_exists("{$file}.bak")) {
|
||||
$has_backup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_backup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = true;
|
||||
foreach ($sapi_files as $file) {
|
||||
$backup = "{$file}.bak";
|
||||
if (file_exists($backup)) {
|
||||
$result = $result && file_put_contents($file, file_get_contents($backup)) !== false;
|
||||
@unlink($backup);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user