climate->arguments->add(Constants::COMPILER_OPTIONS); $this->preprocessArgvAdvanced(); $this->climate->arguments->parse(); // 只读取命令行参数,不立即应用(等待 YAML 解析后再应用) // 这样可以确保优先级:命令行 > YAML > 默认值 $this->internalFunctions = array_flip(get_defined_functions()['internal']); unset($this->internalFunctions[self::ENTRY_FUNCTION]); $this->internalConstants = get_defined_constants(); if ($this->climate->arguments->defined('help')) { $this->showUsage(); exit(0); } if ($this->climate->arguments->defined('version')) { $this->showVersion(); exit(0); } // 检测操作系统、编译器以及 Windows 平台的 PHP lib 文件 $this->detectPlatform(); } /** * 检测操作系统、编译器以及 Windows 平台的 PHP lib 文件 */ protected function detectPlatform(): void { try { $this->platform = PlatformFactory::create(); $this->cppCompiler = CompilerFactory::detectCompilerName($this->platform); if ($this->platform instanceof Windows) { $libInfo = $this->platform->detectPhpLibs($this->getPhpDir()); $this->windowsPhpEmbedLib = $libInfo['embed']; $this->windowsPhpCoreLib = $libInfo['core']; $this->isPhpZts = $libInfo['is_zts']; $this->platform = new Windows( phpLibs: [$this->windowsPhpCoreLib, $this->windowsPhpEmbedLib], isZts: $this->isPhpZts, phpSdkPath: $this->getPhpDir() . '\\SDK' ); } $this->compilerBackend = CompilerFactory::createByName($this->cppCompiler, $this->platform); $this->climate->info( "Initialized platform/backend: {$this->platform->getName()} + {$this->compilerBackend->getName()} ({$this->compilerBackend->getCompilerCommand()})" ); } catch (\Throwable $e) { $this->error($e->getMessage()); } } public function parseArgv(array $argv) { $path = null; for ($i = 1; $i < count($argv); $i++) { if ($argv[$i] !== '' && $argv[$i][0] !== '-') { $path = $argv[$i]; break; } } if (empty($path)) { $this->showUsage(); exit(1); } return $path; } public function showUsage(): void { $climate = $this->climate; $this->showVersion(); $climate->br(); global $argv; $cmd = $argv[0]; $climate->bold('USAGE:'); $climate->tab()->out($cmd . ' [options]'); $climate->br(); $climate->bold('ARGUMENTS:'); $climate->tab()->out(' Input PHP file/directory/project.yml to compile'); $climate->br(); $climate->bold('OPTIONS:'); $climate->tab()->out('-O Optimization level (0-3, default: 0)'); $climate->tab()->out('--profile Enable performance profiling (adds -lprofiler, forces recompile)'); $climate->tab()->out('-d, --debug Enable debug mode (auto-disable optimizations, add debug symbols)'); $climate->tab()->out('--cxx-std C++ standard version (c++17, c++20, etc.)'); $climate->tab()->out('-o, --output Output binary name (default: input basename)'); $climate->tab()->out('-v, --version Show version'); $climate->tab()->out('-h, --help Show this help message'); $climate->tab()->out('-f, --force Force recompile phpx misc files (ignore cache)'); $climate->tab()->out('-m, --mode Compilation mode, -m bin(binary) or -m ext(extension), default: bin'); $climate->tab()->out('-r, --run Run the compiled binary after build'); $climate->tab()->out('-j, --job Number of parallel compilation jobs (default: 4)'); $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); $climate->tab()->out('--no-console Hide console window (Windows only, GUI application)'); $climate->tab()->out('--sanitize Enable sanitizers (address, undefined, etc.)'); $climate->br(); $climate->bold('EXAMPLES:'); $climate->tab()->out($cmd . ' hello.php'); $climate->tab()->out($cmd . ' bench.php -O2'); $climate->tab()->out($cmd . ' project/config.yml -O2'); $climate->tab()->out($cmd . ' my-ext/ -O2 -o myapp -m ext'); $climate->tab()->out($cmd . ' app.php -O3 -o myapp -v'); $climate->tab()->out($cmd . ' gui-app.php --no-console (Windows GUI app, no console)'); $climate->tab()->out($cmd . ' app.php --sanitize=address (Enable AddressSanitizer)'); $climate->tab()->out($cmd . ' app.php --cxx-std=c++17 (Use C++17 standard)'); $climate->tab()->out($cmd . ' app.php --no-literal-strings (Disable string optimization)'); $climate->tab()->out($cmd . ' app.php -r -O2 -- --flag1 value1'); $climate->br(); } /** * 应用命令行参数(在 YAML 解析后调用,确保命令行参数优先级最高) */ protected function applyCommandLineArguments(): void { // 优化级别 if ($this->climate->arguments->defined('optimize')) { $this->optimizeLevel = $this->climate->arguments->get('optimize'); } // 构建模式 if ($this->climate->arguments->defined('mode')) { $this->buildMode = $this->climate->arguments->get('mode'); } // 调试行号 if ($this->climate->arguments->defined('debug-line')) { $this->debugLine = intval($this->climate->arguments->get('debug-line')); } // 最大并行任务数 if ($this->climate->arguments->defined('job')) { $this->maxJob = intval($this->climate->arguments->get('job')); } // 调试模式 if ($this->climate->arguments->defined('debug')) { $this->debug = true; } // 禁用字面量字符串优化 if ($this->climate->arguments->defined('no-literal-strings')) { $this->noLiteralStrings = true; } // 启用性能分析(需强制重编译 misc 文件以确保 PPROF_ON 宏生效,仅 Linux 支持) if ($this->climate->arguments->defined('profile')) { if (!$this->isLinux()) { $this->climate->error('--profile is only supported on Linux (requires gperftools)'); exit(1); } $this->enableProfiler = true; } // 隐藏控制台窗口 if ($this->climate->arguments->defined('no-console')) { $this->noConsole = true; } // Sanitizer if ($this->climate->arguments->defined('sanitize')) { $this->sanitize = $this->climate->arguments->get('sanitize'); } // C++ 标准版本 if ($this->climate->arguments->defined('cxx-std')) { $this->cxxStd = $this->climate->arguments->get('cxx-std'); } } private function showVersion(): void { $this->climate->bold()->out(self::APP_NAME . ' v' . self::VERSION); } protected function formatCppCode(string $file): void { if (!$this->formatCode) { return; } $cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file; $this->climate->info('format: ' . $this->getRelativePath($file)); $this->climate->comment($cmd); shell_exec($cmd); } public function save(string $code, string $file): void { $this->writeFile($file, $code); $this->formatCppCode($file); } public function convertFile(string $file): string { $file = realpath($file); $phpCode = $this->loadFile($file); $this->localHeaders = []; while (true) { try { $cppCode = $this->doConvert($phpCode); $cppFile = $this->getCppFile($file); $this->save($cppCode, $cppFile); $this->phpSrcFiles[] = $file; // 生成 stub 文件,依赖 convert 阶段的 use 等信息 $this->genStubFile($this->file); return $cppFile; } catch (Redo $e) { continue; } } } public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string { return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); } /** * 初始化新的 Platform 和 Backend 抽象层 * 这是一个渐进式迁移,保持向后兼容 */ protected function initializeNewArchitecture(): void { try { $platform = $this->platform ?? PlatformFactory::create(); $this->platform = $platform; // 自动检测平台和编译器 $result = CompilerFactory::autoDetect($this->cppCompiler, $platform); $this->platform = $result['platform']; $this->compilerBackend = $result['compiler']; $this->climate->info( "Initialized new architecture: {$this->platform->getName()} + {$this->compilerBackend->getName()}" ); } catch (\Exception $e) { // 如果初始化失败,回退到旧逻辑 $this->climate->warning( "Failed to initialize new architecture: {$e->getMessage()}. Using legacy mode." ); $this->platform = null; $this->compilerBackend = null; } } /** * 设置 C++ 编译器(从配置文件读取) */ public function setCppCompiler(string $compiler): void { $this->cppCompiler = $compiler; $this->climate->info("Using compiler from config: {$this->cppCompiler}"); // 重新初始化 Backend $this->initializeNewArchitecture(); } public function setBuildMode(string $mode): void { $this->buildMode = $mode; } public function setTargetName(string $name): void { if ($this->climate->arguments->defined('output')) { $name = $this->climate->arguments->get('output'); } $name = str_replace(['-', '*'], '_', $name); if (!preg_match('/^[a-zA-Z0-9_]+$/', $name)) { $this->climate->red('The target name `' . $name . '` must be a valid identifier'); exit(1); } if (in_array($name, Constants::CPP_RESERVED_NAMES)) { $this->climate->red('The target name `' . $name . '` must not be a reserved keyword'); exit(1); } $realTargetPath = $this->rootPath . '/' . $name; if (is_dir($realTargetPath)) { $this->climate->red('The target name `' . $name . '` must not be a directory'); exit(1); } $this->targetName = $name; } public function addFiles(array $files): void { $this->sourceDirs = array_merge($this->sourceDirs, $files); } public function getFiles(string $path): array { $realpath = realpath($path); if ($realpath === false) { $this->error("path not exists: {$path}"); } $path = $realpath; if (is_dir($path)) { // 目录模式:不解析 YAML $list = $this->getFilesFromDir($path); $targetName = basename($path); $this->setTargetName($targetName); $this->sourceDirs[] = $path; } else { $ext = pathinfo($path, PATHINFO_EXTENSION); if ($ext === 'yml') { // YAML 配置模式:先解析 YAML $list = $this->parseProjectYaml($path); } elseif ($ext === 'php') { // 单文件模式:不解析 YAML $list = [$path]; $targetName = FileScanner::getFileName($path); $this->setTargetName($targetName); $this->sourceDirs[] = dirname($path); } else { $this->error('Unsupported file type: ' . $path); } } // 在所有配置加载完成后,应用命令行参数(确保优先级最高) $this->applyCommandLineArguments(); return $list; } public function prepare(string $path): array { // 预先添加一些函数符号,在编译阶段部分语法会转为函数调用 $this->funcSymbols['shell_exec'] = $this->getFuncPtr('shell_exec'); $this->funcSymbols['define'] = $this->getFuncPtr('define'); // 根据平台检查库文件(仅在构建二进制文件时需要) if ($this->isBuildModeBin()) { foreach ($this->getPlatform()->getBuildLibraryWarnings($this->getPhpDir(), $this->getPhpxDir(), $this->buildMode) as $message) { $this->climate->warning($message['warning']); if (!empty($message['info'])) { $this->climate->info($message['info']); } } } $clangFormatVersion = shell_exec('clang-format --version'); if (empty($clangFormatVersion)) { $this->formatCode = false; } $files = $this->getFiles($path); // 应用 ignorePaths 过滤 if (!empty($this->ignorePaths)) { $files = array_filter($files, function ($file) { foreach ($this->ignorePaths as $ignorePath) { if ($file === $ignorePath) { return false; } if (is_dir($ignorePath) && str_starts_with($file, $ignorePath . DIRECTORY_SEPARATOR)) { return false; } } return true; }); } // 分析 PHP 文件,预处理 foreach ($files as $k => $file) { if (FileScanner::isPhpFile($file)) { try { $this->prepareFile($file); } catch (Unsupported $e) { $this->output(' unsupported syntax: ' . $e->getMessage() . "\n" . ' skip: ' . $file . "\n", 'error'); unset($files[$k]); } catch (SyntaxError $e) { $this->output(' syntax error: ' . $e->getMessage() . "\n" . ' skip: ' . $file . "\n", 'error'); unset($files[$k]); } } } $this->sortFiles($files); return $files; } public function convert(array $files): array { $sourceFiles = []; // 生成 C++ 文件 foreach ($files as $k => $file) { try { if (FileScanner::isPhpFile($file)) { $cppFile = $this->convertFile($file); } elseif (FileScanner::isNativeSourceFile($file)) { $cppFile = $file; } else { continue; } $sourceFiles[] = $cppFile; } catch (Unsupported $e) { echo ' unsupported syntax: ' . $e->getMessage() . "\n"; echo ' skip: ' . $file . "\n"; unset($files[$k]); } } if (empty($sourceFiles)) { $this->stop('No valid source file found'); } return $sourceFiles; } public function preprocessArgvAdvanced(): void { global $argv; $processed = [$argv[0]]; for ($i = 1; $i < count($argv); $i++) { $arg = $argv[$i]; if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { $option = $matches[1]; $value = $matches[2]; $processed[] = "-{$option}"; $processed[] = $value; } elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { $options = str_split($matches[1]); foreach ($options as $opt) { $processed[] = "-{$opt}"; } } else { $processed[] = $arg; } } $argv = $processed; } public function genExternGlobalVars(string $file): void { $lines[] = '#include '; $lines[] = PHP_EOL; foreach ($this->globalVars as $name => $type) { $lines[] = 'extern ' . self::TYPE_VAR . ' ' . $this->escapeGlobalVar($name) . ';'; } $literalStringsCount = count($this->literalStrings); $lines[] = 'extern ' . self::TYPE_STR . ' ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; // 确保数组大小至少为 1,避免 C/C++ 编译错误 $classCount = max(1, count($this->classMap)); $lines[] = 'extern zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . $classCount . '];' . PHP_EOL; $funcCount = max(1, count($this->funcMap)); $lines[] = 'extern zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . $funcCount . '];' . PHP_EOL; $propCount = max(1, count($this->propMap)); $lines[] = 'extern uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . $propCount . '];' . PHP_EOL; foreach ($this->classes as $classDef) { foreach ($classDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY) { $constName = self::PREFIX . $this->getNativeName($constant->name, $classDef->namespace, $classDef->name); $lines[] = 'extern ' . self::TYPE_VAR . ' ' . $constName . ';' . PHP_EOL; } } } $code = implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; $this->writeFile($file, $code); } public function genExtension(): string { if ($this->isBuildModeBin()) { if (!$this->hasFunction(self::ENTRY_FUNCTION)) { $this->climate->red('When the build mode is a binary executable file, the `main()` function must be defined'); exit(1); } } $file = $this->getBuildDir() . '/extension-' . $this->targetName . '.cc'; $this->localHeaders = $this->argInfoHeaderFiles; $this->genClassCeList(); $this->indentLevel++; $code = $this->genIncludeHeaderFiles(); if ($this->isBuildModeBin()) { $cliHeaders = [ '#include "php_cli_process_title.h"', '#include "php_cli_process_title_arginfo.h"', ]; $code .= 'extern "C" {' . PHP_EOL; $code .= implode(PHP_EOL, $cliHeaders) . PHP_EOL; $code .= '}' . PHP_EOL; } $code .= "// global vars \n"; foreach ($this->globalVars as $name => $type) { $code .= self::TYPE_VAR . ' ' . $this->escapeGlobalVar($name) . ';' . PHP_EOL; } $code .= "// class register functions \n"; foreach ($this->classCeList as $ce) { $code .= 'zend_class_entry *' . $ce . ';' . PHP_EOL; } $code .= "// class entry \n"; // 确保数组大小至少为 1,避免 C/C++ 编译错误 $code .= 'zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . max(1, count($this->classMap)) . '];' . PHP_EOL; $code .= "// func \n"; $code .= 'zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . max(1, count($this->funcMap)) . '];' . PHP_EOL; $code .= "// property \n"; $code .= 'uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . max(1, count($this->propMap)) . '];' . PHP_EOL; $code .= <<<'CODE' zend_class_entry *php_get_class(int class_id, const php::Str &class_name) { if (UNEXPECTED(php_class_map[class_id] == nullptr)) { php_class_map[class_id] = php::getClassEntrySafe(class_name); } return php_class_map[class_id]; } zend_function *php_get_func(int func_id, const php::Str &func_name) { if (UNEXPECTED(php_func_map[func_id] == nullptr)) { php_func_map[func_id] = php::getFunction(func_name); } return php_func_map[func_id]; } zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name) { if (UNEXPECTED(php_func_map[func_id] == nullptr)) { auto ce = php_get_class(class_id, class_name); php_func_map[func_id] = php::getMethod(ce, method_name); } return php_func_map[func_id]; } uint32_t php_get_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name) { if (UNEXPECTED(php_property_map[prop_id] == 0)) { php_property_map[prop_id] = php::getPropertyOffset(class_name, prop_name) + 1024; } return php_property_map[prop_id] - 1024; } CODE; $code .= "\n\n"; $code .= "// literal strings \n"; $code .= self::TYPE_STR . ' ' . self::LITERAL_STRINGS . '[] = {' . PHP_EOL; foreach ($this->literalStrings as $str => $index) { $code .= self::TYPE_STR . '{ZEND_STRL("' . $this->escapeString($str) . '"), true}, // [' . $index . ']' . PHP_EOL; } $code .= '};' . PHP_EOL . PHP_EOL; $code .= "// constants \n"; foreach ($this->constants as $name => $const) { $code .= $const->type . ' ' . $name . ";\n"; } $code .= "// class \n"; foreach ($this->classes as $classDef) { if ($classDef->requireCtor) { $code .= 'static zend_object* (*create_object_' . $classDef->getNamespacedName() . ")(zend_class_entry *class_type);\n"; } foreach ($classDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY) { $constName = self::PREFIX . $this->getNativeName($constant->name, $classDef->namespace, $classDef->name); $code .= self::TYPE_VAR . ' ' . $constName . ";\n"; } } } $code .= "// clang-format off\n"; $code .= "static const zend_function_entry ext_functions[] = {\n"; if ($this->isBuildModeBin()) { $code .= $this->getIndent() . "PHP_FE(cli_set_process_title, arginfo_cli_set_process_title)\n"; $code .= $this->getIndent() . "PHP_FE(cli_get_process_title, arginfo_cli_get_process_title)\n"; } foreach ($this->functions as $functionDef) { if ($this->isBuildModeExt() and $functionDef->name === self::ENTRY_FUNCTION) { continue; } if ($functionDef->method) { continue; } $fullName = $functionDef->getNamespacedName(); $zifName = $this->escapeZendFnName($fullName); if ($functionDef->namespace) { $code .= $this->getIndent() . 'ZEND_NAMED_FE("' . $this->escapeString($fullName) . '", ZEND_FN(' . $zifName . '), arginfo_' . $zifName . ')' . PHP_EOL; } else { $code .= $this->getIndent() . 'ZEND_FE(' . $zifName . ', arginfo_' . $zifName . ')' . PHP_EOL; } } $code .= $this->getIndent() . "ZEND_FE_END\n};\n// clang-format on" . PHP_EOL . PHP_EOL; // minit begin $code .= 'PHP_MINIT_FUNCTION(' . $this->getModuleName() . ') {' . PHP_EOL; $code .= 'zend_try {' . PHP_EOL; $code .= '// class/interface class entries' . PHP_EOL; $code .= $this->genClassPropertyInit() . PHP_EOL; $code .= '// register symbols' . PHP_EOL; foreach ($this->registerSymbols as $registerSymbolFn) { $code .= $registerSymbolFn . '(module_number);' . PHP_EOL; } $code .= '} zend_end_try();' . PHP_EOL; $code .= 'return SUCCESS;' . PHP_EOL; $code .= '}' . PHP_EOL . PHP_EOL; // minit end // php_app_init begin $code .= 'void php_app_init() {' . PHP_EOL; $code .= '// register constants' . PHP_EOL; foreach ($this->constants as $name => $const) { $code .= "{$name} = {$const->value};\n"; $code .= 'php::call(' . $this->funcSymbols['define'] . ', { ' . $this->genCharPtr($const->name, true) . ', ' . $name . ' });' . PHP_EOL; } $code .= '// global vars ' . PHP_EOL; foreach ($this->globalVars as $name => $type) { $code .= 'php::initGlobal(' . $this->genCharPtr($name) . ', ' . $this->escapeGlobalVar($name) . ');' . PHP_EOL; } $code .= '// static property ' . PHP_EOL; foreach ($this->defaultStaticPropertyList as $prop) { $code .= 'php::setStaticProperty(' . $this->genCharPtr($prop->class, true) . ', ' . $this->genCharPtr($prop->name) . ', ' . $prop->default . ');' . PHP_EOL; } $code .= '// class array constants' . PHP_EOL; $code .= $this->genClassArrayConstants(); $code .= '}' . PHP_EOL . PHP_EOL; // php_app_init end // php_app_clean begin $code .= 'void php_app_clean() {' . PHP_EOL; foreach ($this->globalVars as $name => $type) { $code .= $this->escapeGlobalVar($name) . '.unset();' . PHP_EOL; $code .= 'php::unsetGlobal("' . $name . '");' . PHP_EOL; } foreach ($this->constants as $name => $const) { if ($const->type !== self::TYPE_VAR) { continue; } $code .= $name . '.unset();' . PHP_EOL; } $code .= '// class array constants' . PHP_EOL; foreach ($this->classes as $classDef) { foreach ($classDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY) { $constName = self::PREFIX . $this->getNativeName($constant->name, $classDef->namespace, $classDef->name); $code .= $constName . ".unset();\n"; $classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true); $classConstStr = $this->genCharPtr($constant->name); $code .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n"; } } } // Clean up inherited array constants from child classes foreach ($this->classes as $className => $classDef) { $ownConstNames = []; foreach ($classDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY) { $ownConstNames[$constant->name] = true; } } $parentName = $this->escapeClass($classDef->extends); while ($parentName && isset($this->classes[$parentName])) { $parentDef = $this->classes[$parentName]; foreach ($parentDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY && !isset($ownConstNames[$constant->name])) { $ownConstNames[$constant->name] = true; $classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true); $classConstStr = $this->genCharPtr($constant->name); $code .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n"; } } $parentName = $this->escapeClass($parentDef->extends); } } $code .= '}' . PHP_EOL . PHP_EOL; // php_app_clean end $moduleName = $this->getModuleName(); // rinit begin $code .= 'PHP_RINIT_FUNCTION(' . $moduleName . ') {' . PHP_EOL; $code .= 'php::request_init();' . PHP_EOL; $code .= 'php_app_init();' . PHP_EOL; if ($this->isBuildModeBin()) { if (count($this->functions[self::ENTRY_FUNCTION]->argInfoList) == 2) { $code .= 'php::eval("global $argc, $argv; main($argc, $argv);");' . PHP_EOL; } else { $code .= 'php::eval("main();");' . PHP_EOL; } } $code .= 'return SUCCESS;' . PHP_EOL; $code .= '}' . PHP_EOL . PHP_EOL; // rinit end $code .= <<isBuildModeExt()) { $code .= "ZEND_GET_MODULE({$moduleName});\n"; } else { $code .= 'zend_module_entry *' . self::PREFIX . 'embed_get_module() {' . PHP_EOL; $code .= $this->getIndent() . 'return &' . $moduleName . '_module_entry;' . PHP_EOL; $code .= '}' . PHP_EOL; } $this->indentLevel--; $this->writeFile($file, $code); $this->formatCppCode($file); $this->localHeaders = []; return $file; } public function getModuleName(): string { return self::MODULE_NAME_PREFIX . $this->targetName; } /** * 检查 phpx/src/misc/ 下的源文件是否已有有效缓存,始终生效(除非指定 --force)。 * 仅检查 .o 文件是否比源文件和 phpx 头文件更新。 */ public function hasMiscObjectFileCache(string $cppFile): bool { if ($this->climate->arguments->defined('force') || $this->enableProfiler) { return false; } $objectFile = $this->getObjectFile($cppFile); if (!file_exists($objectFile)) { return false; } $objectMtime = filemtime($objectFile); if ($objectMtime <= filemtime($cppFile)) { return false; } $phpxDir = $this->getPhpxDir(); $headerDirs = [$phpxDir . '/include', $phpxDir . '/src/misc']; foreach ($headerDirs as $dir) { if (!is_dir($dir)) { continue; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if ($file->getExtension() === 'h' && $file->getMTime() > $objectMtime) { return false; } } } return true; } public function isPhpxMiscFile(string $cppFile): bool { $miscDir = $this->getPhpxDir() . '/src/misc/'; return str_starts_with($cppFile, $miscDir); } /** * 判断文件是否为 C++ 源文件 */ protected function isCppFile(string $filePath): bool { $extension = pathinfo($filePath, PATHINFO_EXTENSION); return in_array($extension, ['cc', 'cpp', 'cxx'], true); } /** * 根据文件扩展名获取语言类型标识(用于 -x 参数). * * @return string|null 语言标识(c, assembler, objective-c, objective-c++), * 或 null 表示使用默认检测(C++ 文件) */ protected function getLanguageFromExtension(string $filePath): ?string { $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); return match ($ext) { 'c' => 'c', 's', 'S' => 'assembler', 'm' => 'objective-c', 'mm' => 'objective-c++', 'cc', 'cpp', 'cxx' => null, default => null, }; } /** * 判断文件是否为原生编译型源文件(C/C++/汇编/ObjC 等). */ protected function isNativeSourceFile(string $filePath): bool { return FileScanner::isNativeSourceFile($filePath); } public function compileFile(string $cppFile, string $objectFile, bool $parallel = false): void { if ($this->isPhpxMiscFile($cppFile) && $this->hasMiscObjectFileCache($cppFile)) { if (!$parallel) { $this->climate->darkGray('[cache] skip: ' . $cppFile); } return; } // 检测文件语言类型 $language = $this->getLanguageFromExtension($cppFile); // 使用 Backend 层构建编译命令 if ($language === null) { // C++ 文件:使用标准的编译命令构建 $cmd = $this->getCompilerBackend()->buildCompileCommand( $cppFile, $objectFile, $this->getCompileCommandOptions() ); } elseif ($language === 'c') { // C 文件:使用 buildCCompileCommand,后端自动添加 -x c 或 /TC $cmd = $this->getCompilerBackend()->buildCCompileCommand( $cppFile, $objectFile, $this->getCCompileCommandOptions() ); } else { // 其他原生源文件(assembler, objective-c, objective-c++) // 使用 buildNativeCompileCommand,传入语言类型以添加 -x 标志 $cmd = $this->getCompilerBackend()->buildNativeCompileCommand( $cppFile, $objectFile, $this->getNativeCompileCommandOptions($language), $language ); } if (!$parallel) { $this->climate->comment($cmd); } // 在并行模式下,抑制 passthru 的输出 if ($parallel) { exec($cmd . ' 2>&1', $output, $ret); } else { passthru($cmd, $ret); } if ($ret !== 0) { if ($parallel && !empty($output)) { foreach ($output as $line) { $this->climate->red($line); } } $this->error('compile failed: ' . $cppFile); } } public function compile(array $sourceFiles): array { $job = $this->maxJob; // 生成所有函数声明、全局变量声明的头文件 $this->genFunctionDeclaration($this->getIncludeDir() . "/php_{$this->targetName}_func_decl.h"); $this->genExternGlobalVars($this->getIncludeDir() . "/php_{$this->targetName}_global_var_decl.h"); // 生成扩展模块的源文件 $sourceFiles[] = $this->genExtension(); // embed 需要 main 函数,以及 cli 的内置函数定义 if ($this->isBuildModeBin()) { $sourceFiles[] = $this->getPhpxDir() . '/src/misc/main.cc'; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/php_cli_process_title.c'; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c'; } // Windows 平台:编译资源文件(图标、版本信息等) $this->compileResourceFile(); if (!$this->getPlatform()->supportsPcntlParallelCompile() or $job <= 1) { return $this->compileSourceFile($sourceFiles); } // Unix/Linux/macOS 使用 pcntl 并行编译 return $this->compileWithPcntl($sourceFiles, $job); } protected function compileSourceFile(array $sourceFiles): array { $objectFiles = []; $totalFiles = count($sourceFiles); $failedFiles = []; $this->climate->lightBlue("Starting compilation for {$totalFiles} files"); foreach ($sourceFiles as $cppFile) { $objectFile = $this->getObjectFile($cppFile); try { $this->compileFile($cppFile, $objectFile, false); if (is_file($objectFile)) { $objectFiles[] = $objectFile; } else { $failedFiles[] = $cppFile; $this->climate->red("Compilation failed: {$cppFile}"); } } catch (\Throwable $e) { $failedFiles[] = $cppFile; $this->climate->red("Compilation error: {$cppFile} - " . $e->getMessage()); } } if (!empty($failedFiles)) { throw new \Exception('Compilation failed for: ' . implode(', ', $failedFiles)); } $this->climate->green("Successfully compiled {$totalFiles} files"); return $objectFiles; } /** * Unix/Linux/macOS 平台并行编译(使用 pcntl) */ protected function compileWithPcntl(array $sourceFiles, int $job): array { // 检查 pcntl 扩展是否可用 if (!function_exists('pcntl_fork')) { $this->climate->warning('pcntl extension not available, using sequential compilation'); return $this->compileSourceFile($sourceFiles); } $objectFiles = []; $totalFiles = count($sourceFiles); $runningProcesses = 0; $processPipes = []; $fileQueue = $sourceFiles; $compiledCount = 0; $failedFiles = []; $this->climate->lightBlue("Starting parallel compilation with {$job} jobs for {$totalFiles} files"); while ($compiledCount < $totalFiles) { // 启动新进程,直到达到最大并发数 while ($runningProcesses < $job && !empty($fileQueue)) { $cppFile = array_shift($fileQueue); $objectFile = $this->getObjectFile($cppFile); $pid = pcntl_fork(); if ($pid == -1) { throw new \Exception('Failed to fork process'); } if ($pid === 0) { // 子进程:执行编译 try { $this->compileFile($cppFile, $objectFile, true); if (!is_file($objectFile)) { exit(1); } exit(0); } catch (\Throwable $e) { // 在子进程中不抛出异常,直接退出 exit(1); } } else { // 父进程:记录子进程 $processPipes[$pid] = ['file' => $cppFile, 'object' => $objectFile]; $runningProcesses++; } } // 等待任意一个子进程完成 if ($runningProcesses > 0) { $status = null; $pid = pcntl_wait($status); if ($pid > 0) { $processInfo = $processPipes[$pid] ?? null; unset($processPipes[$pid]); $runningProcesses--; $exitCode = pcntl_wexitstatus($status); if ($exitCode !== 0) { $failedFile = $processInfo['file'] ?? 'unknown'; $this->climate->red("Compilation failed: {$failedFile}"); $failedFiles[] = $failedFile; } else { if ($processInfo) { $objectFiles[] = $processInfo['object']; } } $compiledCount++; } } } // 确保所有子进程都已结束 while ($runningProcesses > 0) { $status = null; $pid = pcntl_wait($status); if ($pid > 0) { unset($processPipes[$pid]); $runningProcesses--; $compiledCount++; } } if (!empty($failedFiles)) { throw new \Exception('Compilation failed for: ' . implode(', ', $failedFiles)); } $this->climate->green("Successfully compiled {$totalFiles} files"); return $objectFiles; } public function output(string $message, string $style = 'out'): void { $this->climate->{$style}($message); } // ======================================================================== // Windows 资源文件支持 // ======================================================================== /** * 检查是否配置了 Windows 资源信息 */ public function hasResourceFile(): bool { if (!$this->isWindows()) { return false; } $generator = $this->createResourceGenerator(); return $generator !== null && $generator->hasResource(); } /** * 获取 .rc 资源文件路径 */ public function getResourceRcFile(): string { return $this->getBuildDir() . DIRECTORY_SEPARATOR . 'app_resource.rc'; } /** * 获取 .res 编译后的资源文件路径 */ public function getResourceResFile(): string { return $this->getBuildDir() . DIRECTORY_SEPARATOR . 'app_resource.res'; } /** * 创建资源文件生成器 */ protected function createResourceGenerator(): ?ResourceFileGenerator { if (empty($this->resourceConfig)) { return null; } $projectDir = $this->resourceConfig['_projectDir'] ?? getcwd(); return new ResourceFileGenerator($this->resourceConfig, $projectDir); } /** * 编译 Windows 资源文件 * * 如果配置了 resource 选项,生成 .rc 文件并使用 rc.exe 编译为 .res * .res 文件会在 build 阶段被链接到最终的 exe 中 */ protected function compileResourceFile(): void { if (!$this->isWindows()) { return; } $generator = $this->createResourceGenerator(); if ($generator === null || !$generator->hasResource()) { return; } // 生成 .rc 文件 $rcFile = $this->getResourceRcFile(); $rcContent = $generator->generate(); // 写入 UTF-8 BOM,确保 rc.exe 正确识别编码,避免中文乱码 $this->writeFile($rcFile, "\xEF\xBB\xBF" . $rcContent); $this->climate->info('Generated resource file: ' . $rcFile); // 使用 MSVC 的 rc.exe 编译 .rc -> .res $backend = $this->getCompilerBackend(); if ($backend instanceof \PhpAot\Php\Backend\Msvc) { $resFile = $this->getResourceResFile(); $cmd = $backend->compileResourceFile($rcFile, $resFile); $this->climate->comment($cmd); exec($cmd . ' 2>&1', $output, $ret); if (!empty($output)) { foreach ($output as $line) { $this->climate->out($line); } } if ($ret !== 0) { $this->error('Resource compilation failed: ' . $rcFile); } if (!file_exists($resFile)) { $this->error('Resource file not generated: ' . $resFile); } $this->climate->green('Resource compiled: ' . $resFile); } else { $this->climate->warning('Resource files are only supported with MSVC backend on Windows'); } } protected function getCompileCommandOptions(): array { return [ 'include_paths' => $this->getIncludePaths(), 'optimize' => $this->optimizeLevel, 'debug' => $this->debug, 'sanitize' => $this->sanitize, 'cpp_std' => $this->cxxStd, 'is_zts' => $this->isPhpZts, 'build_mode' => $this->buildMode, 'enable_profiler' => $this->enableProfiler, 'prof_output' => $this->targetName . '.prof', 'suppressed_warnings' => Constants::MSVC_SUPPRESSED_WARNINGS ?? [], 'cxxflags' => $this->cxxFlags, ]; } protected function getCCompileCommandOptions(): array { return [ 'include_paths' => $this->getIncludePaths(), 'optimize' => 0, 'debug' => $this->debug, 'is_zts' => $this->isPhpZts, 'suppressed_warnings' => ['4244', '4146'], ]; } /** * 获取原生源文件(汇编/ObjC 等)的编译选项,不含 C++ 特定标志. * * @param string $language 语言标识(assembler, objective-c, objective-c++) */ protected function getNativeCompileCommandOptions(string $language = ''): array { return [ 'include_paths' => $this->getIncludePaths(), 'optimize' => $this->optimizeLevel, 'debug' => $this->debug, 'sanitize' => $this->sanitize, 'is_zts' => $this->isPhpZts, 'build_mode' => $this->buildMode, 'enable_profiler' => $this->enableProfiler, 'suppressed_warnings' => Constants::MSVC_SUPPRESSED_WARNINGS ?? [], ]; } protected function getLinkCommandOptions(): array { $ldflags = $this->ldflags; if ($this->enableProfiler) { $ldflags .= ' -lprofiler'; } $options = [ 'library_paths' => $this->getLibraryPaths(), 'libraries' => $this->getLibraries(), 'ldflags' => $ldflags, 'debug' => $this->debug, 'no_console' => $this->noConsole, 'build_mode' => $this->buildMode, 'sanitize' => $this->sanitize, ]; $rpaths = $this->getPlatform()->getDefaultRpaths( $this->getPhpxDir(), $this->getPhpDir() ); if (!empty($rpaths)) { $options['rpath'] = $rpaths; } return $options; } protected function buildLinkCommand(array $objectFiles, string $targetFile): string { return $this->getCompilerBackend()->buildLinkCommand( $objectFiles, $targetFile, $this->getLinkCommandOptions() ); } public function build(array $objectFiles): string { $targetFile = $this->getTargetFileName(); // Windows 平台:将 .res 资源文件加入链接 if ($this->isWindows() && $this->hasResourceFile()) { $resFile = $this->getResourceResFile(); if (file_exists($resFile)) { $objectFiles[] = $resFile; } } $linkCmd = $this->buildLinkCommand($objectFiles, $targetFile); $this->climate->comment($linkCmd); // 执行链接并捕获输出 exec($linkCmd . ' 2>&1', $output, $ret); // 显示输出(如果有) if (!empty($output)) { foreach ($output as $line) { $this->climate->out($line); } } // 检查链接是否成功 if ($ret !== 0) { $this->error('link failed: ' . $targetFile); } // 验证目标文件是否生成 if (!file_exists($targetFile)) { $this->error('target file not generated: ' . $targetFile); } $this->climate->green('Build successful: ' . $targetFile); return $targetFile; } public function isRunRequested(): bool { return $this->climate->arguments->defined('run'); } public function run(string $targetFile): never { if ($this->buildMode !== self::BUILD_MODE_BIN) { $this->climate->error('--run is only supported in binary mode (-m bin), not extension mode (-m ext)'); exit(1); } if (DIRECTORY_SEPARATOR !== '\\' && !str_starts_with($targetFile, '/')) { $targetFile = './' . $targetFile; } $targetArgs = $this->getTargetArgs(); $command = escapeshellcmd($targetFile); if (!empty($targetArgs)) { $command .= ' ' . implode(' ', array_map('escapeshellarg', $targetArgs)); } fwrite(STDERR, "Running: {$command}\n"); passthru($command, $exitCode); exit($exitCode); } public function getTargetArgs(): array { return $this->climate->arguments->trailingArray() ?? []; } public function genFunctionDeclaration(string $file): void { $code = '#include ' . PHP_EOL; // 函数的默认值可能会使用字符串字面量,需要提前声明 $literalStringsCount = count($this->literalStrings); $code .= 'extern ' . self::TYPE_STR . ' ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; foreach ($this->functions as $name => $func) { $code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '('; $list = []; if ($func->method) { $list[] = self::TYPE_OBJECT . ' &this_'; } $argInfoList = $func->argInfoList; if ($argInfoList) { foreach ($argInfoList as $argInfo) { if ($argInfo->variadic) { $arg = self::TYPE_ARRAY . ' ' . $argInfo->name . ' = {}'; } else { $arg = $this->genArgumentDeclaration($argInfo); if ($argInfo->default) { $arg .= ' = ' . $argInfo->default; } } $list[] = $arg; } } $code .= implode(', ', $list); $code .= ');' . PHP_EOL; } $code .= PHP_EOL; foreach ($this->constants as $name => $constant) { $code .= 'extern ' . $constant->type . ' ' . $name . ';' . PHP_EOL; } $this->writeFile($file, $code); } public function getBuildMode(): string { return $this->buildMode; } public function getArgInfoStubFilename(string $stubFile): string { $rs = str_replace(['.stub.php', '.php'], '', $stubFile); return str_replace('-', '_', $rs); } public function getArgInfoHeaderFile(string $file, bool $relative = false): string { $filePath = $this->getRelativePath(str_replace(['.stub.php', '.php'], '', $file)); $filename = self::PREFIX . str_replace(['/', '\\'], '_', $filePath); $filename = $this->escapeFileName($filename); $absPath = $this->getIncludeDir() . "/{$filename}_arginfo.h"; if ($relative) { return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); } return $absPath; } public function genIncludeHeaderFiles(): string { $headers = array_merge($this->globalHeaders, [ "php_{$this->targetName}_func_decl.h", "php_{$this->targetName}_global_var_decl.h", ], $this->localHeaders); $lines = []; foreach ($headers as $header) { $lines[] = '#include <' . $header . '>'; } return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; } public function genClassPropertyInit(): string { $code = ''; foreach ($this->classCeList as $ce) { $info = $this->classCeInfo[$ce] ?? $this->getInternalCeInfo($ce); $code .= "{$ce} = {$info['func']}({$info['args']});\n"; $classDef = !empty($info['classDef']) ? $info['classDef'] : null; /** * @var ClassDef $classDef */ if ($classDef and $classDef->requireCtor) { $className = $classDef->getNamespacedName(); $code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; $code .= $classDef->ctorInit; $code .= "auto obj = create_object_{$className}(class_type);\n"; foreach ($classDef->properties as $property) { $fullPropName = $classDef->getNamespacedName(false) . '::' . $property->name; if (isset($this->defaultPropertyList[$fullPropName])) { $code .= "do {\n"; $code .= "auto value = {$this->defaultPropertyList[$fullPropName]};\n"; $code .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; $code .= "} while(0);\n"; } } $code .= $classDef->ctorClean; $code .= "return obj;\n};\n"; } } return $code; } public function getDefinedConstants(): array { return $this->internalConstants; } /** * 仅用于 gen_stub 脚本 * @throws \Exception */ public function getClassConstValue(NodeAbstract $expr, string $_class, string $name, string $currentClass = ''): mixed { $namespace = $this->namespace; if (!$namespace and $currentClass and !str_contains($_class, '\\')) { $namespace = $this->getNamespaceOfClass($currentClass); } $class = $this->getNamespacedClassName($_class, $namespace); $nativeConst = $this->findNativeClassConst($expr, $class, $name); if ($nativeConst and $expr->hasAttribute('nativeConst')) { $constDef = $expr->getAttribute('nativeConst'); return $this->genCValue($constDef->valueExpr->value); } if ($this->isInternalClass($class)) { $constName = $class . '::' . $name; if (defined($constName)) { return $this->genCValue(constant($constName)); } } $this->fatalError($expr, "Class constant `{$class}::{$name}` not found"); } public function getConstValue(string $name): mixed { if ($this->isInternalConstant($name)) { $value = $this->internalConstants[$name]; if (is_int($value)) { $expr = strval($value); if ($value === PHP_INT_MIN) { $expr = 'LONG_MIN'; } elseif ($value === PHP_INT_MAX) { $expr = 'LONG_MAX'; } else { $expr = $expr . 'L'; } } elseif (is_float($value)) { return $value; } elseif (is_bool($value)) { return $value ? 1 : 0; } elseif (is_string($value)) { return $this->genCharPtr($value); } else { $this->error('Unsupported constant type: ' . gettype($value)); } return $expr; } throw new \Exception('Constant ' . $name . ' not found'); } protected function getRegisterClassFunction(string $name): string { return self::PREFIX . 'register_class_' . $name; } protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array { $list = []; $parentCe = $this->getParentClassCe($classDef); if ($parentCe !== '') { $list = [$parentCe]; } // interface 没有 implements if ($classDef instanceof InterfaceDef) { return $list; } $implements = $this->getImplementCe($classDef); return array_merge($list, $implements); } protected function getClassCe(ClassLikeDef $classDef): string { return self::PREFIX . 'class_entry_' . $this->escapeCeName($classDef->getNamespacedName()); } protected function getFilesFromDir(string $path): array { $scanner = new FileScanner($path); return $scanner->scan(); } protected function genClassArrayConstants(): string { $code = ''; foreach ($this->classes as $classDef) { foreach ($classDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY) { $constName = self::PREFIX . $this->getNativeName($constant->name, $classDef->namespace, $classDef->name); $code .= "do {\n"; $code .= $constant->arrayExpr; $code .= $constName . ' = ' . $constant->value . ";\n"; $classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true); $classConstStr = $this->genCharPtr($constant->name); $code .= "php::updateConstant($classNameStr, $classConstStr, {$constant->value});\n"; $code .= "} while(0);\n"; } } } // Propagate array constants to child classes that don't override them foreach ($this->classes as $className => $classDef) { $ownConstNames = []; foreach ($classDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY) { $ownConstNames[$constant->name] = true; } } $parentName = $this->escapeClass($classDef->extends); while ($parentName && isset($this->classes[$parentName])) { $parentDef = $this->classes[$parentName]; foreach ($parentDef->constants as $constant) { if ($constant->type === self::TYPE_ARRAY && !isset($ownConstNames[$constant->name])) { $ownConstNames[$constant->name] = true; $constName = self::PREFIX . $this->getNativeName($constant->name, $parentDef->namespace, $parentDef->name); $classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true); $classConstStr = $this->genCharPtr($constant->name); $code .= "php::updateConstant($classNameStr, $classConstStr, {$constName});\n"; } } $parentName = $this->escapeClass($parentDef->extends); } } return $code; } protected function getAbsolutePath(string $path, string $projectDir): string { $path = trim($path); if ($path === '') { $this->error('Source path must not be empty'); } if ($path[0] !== '/') { $absPath = $projectDir . '/' . $path; } else { $absPath = $path; } return realpath($absPath); } protected function parseProjectYaml(string $path): array { $cfg = Yaml::parseFile($path); $projectDir = dirname($path); if (!empty($cfg['sources'])) { $sources = $cfg['sources']; if (!is_array($sources)) { $this->error('`sources` must be array'); } $list = []; foreach ($sources as $src) { $realPath = $this->getAbsolutePath($src, $projectDir); if (!$realPath) { $this->error('Source file not exists: `' . $src . '`'); } if (is_file($realPath)) { $list[] = $realPath; $this->sourceDirs[] = basename($realPath); } else { $tmp = $this->getFilesFromDir($realPath); $list = array_merge($list, $tmp); $this->sourceDirs[] = $realPath; } } } else { $list = $this->getFilesFromDir($projectDir); } // 读取 cxx-flags(支持中横线和下划线) $cxxFlags = $cfg['cxx-flags'] ?? $cfg['cxxflags'] ?? null; if (!empty($cxxFlags)) { if (is_array($cxxFlags)) { $this->cxxFlags = implode(' ', $cxxFlags); } else { $this->cxxFlags = str_replace("\n", ' ', $cxxFlags); } } // 读取 C++ 标准版本(支持中横线和下划线) $cxxStd = $cfg['cxx-std'] ?? $cfg['cxx_std'] ?? null; if (!empty($cxxStd)) { $this->cxxStd = $cxxStd; } // 读取 ldflags(支持中横线和下划线) $ldflags = $cfg['ld-flags'] ?? $cfg['ldflags'] ?? null; if (!empty($ldflags)) { if (is_array($ldflags)) { $this->ldflags = implode(' ', $ldflags); } else { $this->ldflags = str_replace("\n", ' ', $ldflags); } } // 读取 name if (!empty($cfg['name'])) { $this->setTargetName($cfg['name']); } // 读取 cpp-compiler(支持中横线和下划线) $cppCompiler = $cfg['cpp-compiler'] ?? $cfg['cpp_compiler'] ?? null; if (!empty($cppCompiler)) { $this->setCppCompiler($cppCompiler); } // 读取 type/build-mode(支持中横线和下划线) $buildMode = $cfg['build-mode'] ?? $cfg['type'] ?? null; if (!empty($buildMode)) { // 映射常见的类型名称到内部 buildMode $modeMap = [ 'extension' => self::BUILD_MODE_EXT, 'ext' => self::BUILD_MODE_EXT, 'binary' => self::BUILD_MODE_BIN, 'bin' => self::BUILD_MODE_BIN, 'cli' => self::BUILD_MODE_BIN, ]; $mappedMode = $modeMap[strtolower($buildMode)] ?? $buildMode; $this->setBuildMode($mappedMode); } // 读取 ignore(支持中横线和下划线) $ignore = $cfg['ignore'] ?? null; if (!empty($ignore)) { if (!is_array($ignore)) { $this->error('`ignore` must be array'); } foreach ($ignore as $src) { if (preg_match('/ext-([a-z0-9_]+)/i', $src, $matches)) { $this->ignoreExtensions[] = $matches[1]; continue; } $realPath = $this->getAbsolutePath($src, $projectDir); if (!$realPath) { $this->error('Source file not exists: `' . $src . '`'); } $this->ignorePaths[] = $realPath; } } // 读取 resource(Windows 资源配置:图标、版本信息等) $resource = $cfg['resource'] ?? null; if (!empty($resource)) { if (!is_array($resource)) { $this->error('`resource` must be array'); } // 验证图标文件是否存在 if (!empty($resource['icon'])) { $iconPath = $resource['icon']; if (!preg_match('/^[A-Za-z]:\\|^\//', $iconPath)) { $iconPath = $projectDir . DIRECTORY_SEPARATOR . $iconPath; } if (!file_exists($iconPath)) { $this->error('Icon file not exists: `' . $resource['icon'] . '`'); } } $this->resourceConfig = $resource; $this->resourceConfig['_projectDir'] = $projectDir; } return $list; } protected function getInternalCeInfo(string $ce): array { return [ 'func' => Symbol::getClassEntrySafe(), 'args' => '"' . substr($ce, strlen(self::PREFIX . 'class_entry_')) . '"', ]; } protected function getParentClassCe(ClassLikeDef $classDef): string { if (!$classDef->extends) { return ''; } return self::PREFIX . 'class_entry_' . $this->escapeCeName($classDef->extends); } protected function doConvert(string $phpCode): string { $this->climate->info('convert: ' . $this->getRelativePath($this->file)); $ast = $this->parser->parse($phpCode); $traverser = new NodeTraverser(); $traverser->addVisitor(new Visitor()); $stmts = $traverser->traverse($ast); $this->resetFile(); $this->resetNamespace(); $this->resetClass(); $this->resetMethod(); $this->resetFunction(); $cppCode = ''; foreach ($stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_Declare': $this->parseDeclare($v); break; case 'Stmt_Namespace': $cppCode .= $this->parseNamespace($v); break; case 'Stmt_Class': case 'Stmt_Trait': case 'Stmt_Enum': $cppCode .= $this->parseClass($v); break; case 'Stmt_Use': $cppCode .= $this->parseUse($v) . PHP_EOL; break; case 'Stmt_GroupUse': $this->parseGroupUse($v); break; case 'Stmt_Function': $cppCode .= $this->parseFunction($v) . PHP_EOL; break; case 'Stmt_Const': $this->parseConstDef($v); break; case 'Stmt_Interface': break; case 'Stmt_Nop': break; default: abort($v); break; } } foreach ($this->classesDefineInFile as $classDef) { $cppCode .= $this->genClassWrapper($classDef); } foreach ($this->interfacesDefineInFile as $interfaceDef) { $cppCode .= $this->genClassWrapper($interfaceDef); } foreach ($this->functionDefineInFile as $functionDef) { $cppCode .= $this->genFunctionWrapper($functionDef); } $constDataCode = ''; foreach ($this->constData as $name => $data) { $constDataCode .= 'static const unsigned char ' . $name . '[] = {' . $data . '};' . PHP_EOL; } $constDataCode .= PHP_EOL; return $this->genIncludeHeaderFiles() . $constDataCode . $cppCode; } protected function genClassCeList(): void { if (empty($this->interfaces) and empty($this->classes)) { return; } $sorter = new StringSort(); foreach ($this->interfaces as $interfaceDef) { $parent = $interfaceDef->extends; $ce = $this->getClassCe($interfaceDef); $deps = []; if ($parent) { // 不存在的接口,说明可能是内置接口 $tmpCe = $this->getParentClassCe($interfaceDef); if (!isset($this->interfaces[$parent])) { $sorter->add($tmpCe); } $deps[] = $tmpCe; } $this->classCeInfo[$ce] = [ 'deps' => $deps, 'func' => $this->getRegisterClassFunction($interfaceDef->getNamespacedName()), 'args' => $this->getRegisterClassFunctionArgs($interfaceDef), 'argDef' => $this->getRegisterClassFunctionArgDef($interfaceDef), ]; $sorter->add($ce, $deps); } foreach ($this->classes as $classDef) { $ce = $this->getClassCe($classDef); $deps = []; $parent = $classDef->extends; if ($parent) { // 不存在的父类,说明可能是内置类 $tmpCe = $this->getParentClassCe($classDef); if (!isset($this->classes[$parent])) { $sorter->add($tmpCe); } $deps[] = $tmpCe; } $implements = $classDef->implements; if ($implements) { foreach ($implements as $interface) { $tmpCe = self::PREFIX . 'class_entry_' . $this->escapeCeName($interface); if (!isset($this->interfaces[$interface])) { $sorter->add($tmpCe); } $deps[] = $tmpCe; } } $this->classCeInfo[$ce] = [ 'classDef' => $classDef, 'deps' => $deps, 'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()), 'args' => $this->getRegisterClassFunctionArgs($classDef), 'argDef' => $this->getRegisterClassFunctionArgDef($classDef), ]; $sorter->add($ce, $deps); } $this->classCeList = $sorter->sort(); } protected function getNativeMethodName(ClassDef $classDef, MethodDef $methodDef): string { return $this->getNativeName($methodDef->name, $classDef->namespace, $classDef->name); } protected function parseDeclare(mixed $v): void { $declares = $v->declares; foreach ($declares as $declare) { $key = $this->parseIdentifier($declare->key); $value = $this->parseIdentifier($declare->value); if ($key === 'ticks') { $this->fatalError($v, 'declare(ticks=1) is not supported'); } elseif ($key === 'encoding') { if (strtolower($value) !== 'utf-8') { $this->fatalError($v, 'declare(encoding="' . $value . '") is not supported, only UTF-8 is supported'); } } elseif ($key === 'strict_types') { if (!($declare->value instanceof Node\Scalar\Int_) or $declare->value->value !== 1) { $this->fatalError($v, 'declare(strict_types=0) is not allowed, only strict_types=1 is supported'); } } else { $this->fatalError($v, 'declare(' . $key . '=' . $value . ') is not supported'); } } } protected function parseNamespace(Node\Stmt\Namespace_ $node): string { $ns = $node->name ? $this->parseIdentifier($node->name) : ''; $code = ''; $this->resetNamespace(); $this->resetClass(); $this->resetMethod(); $this->resetFunction(); $this->namespace = $ns; $ns_end = ''; foreach ($node->stmts as $v2) { $type2 = $v2->getType(); switch ($type2) { case 'Stmt_Class': case 'Stmt_Trait': case 'Stmt_Enum': $code .= $this->parseClass($v2); break; case 'Stmt_Const': $this->parseConstDef($v2); break; case 'Stmt_Function': $code .= $this->parseFunction($v2) . PHP_EOL; break; case 'Stmt_Use': $code .= $this->parseUse($v2) . PHP_EOL; break; case 'Stmt_GroupUse': $this->parseGroupUse($v2); break; case 'Stmt_Interface': break; default: abort($v2); break; } } $code .= $ns_end; $this->resetNamespace(); return $code; } protected function genStubFile(string $file): void { $headerFile = $this->getArgInfoHeaderFile($file, true); $this->climate->info('generate arginfo file: ' . $this->getRelativePath($file)); generateStubFile($file, $this->getIncludeDir() . '/' . $headerFile, true); if ($this->useRegisterSymbolsFn) { preg_match('/php_(.*)_arginfo.h/', $headerFile, $matches); $registerSymbolFn = 'register_' . $matches[1] . '_symbols'; $registerSymbol = PHP_EOL . 'static void ' . $registerSymbolFn . '(int module_number)' . PHP_EOL; if (str_contains(file_get_contents($this->getBuildDir() . '/include/' . $headerFile), $registerSymbol)) { $this->registerSymbols[] = $registerSymbolFn; } } $this->argInfoHeaderFiles[] = $headerFile; } public function parseTraitUseForStub(Node\Stmt\ClassLike $stmt, Node\Name $className): void { $methods = []; $constants = []; $properties = []; $traitMethods = []; $traitConstants = []; $traitProperties = []; $classDef = $this->getClass($className->toString()); foreach ($stmt->stmts as $classStmt) { if ($classStmt instanceof Node\Stmt\ClassMethod) { $name = strtolower($classStmt->name->toString()); $methods[$name] = $classStmt; } if ($classStmt instanceof Node\Stmt\Property) { foreach ($classStmt->props as $prop) { $name = strtolower($prop->name->toString()); $properties[$name] = $prop; } } if ($classStmt instanceof Node\Stmt\ClassConst) { foreach ($classStmt->consts as $const) { $name = strtolower($const->name->toString()); $constants[$name] = $const; } } } foreach ($stmt->stmts as $classStmt) { if (!$classStmt instanceof Node\Stmt\TraitUse) { continue; } foreach ($classStmt->traits as $trait1) { $traitFullName = $this->getNamespacedClassName($trait1); if (!$this->hasClass($traitFullName)) { $this->fatalError($classStmt, "Trait `{$traitFullName}` not found"); } $traitDef = $this->getClass($traitFullName); if (!$traitDef->trait) { $this->fatalError($classStmt, "Trait `{$traitFullName}` not found"); } $traitAst = clone $traitDef->trait; $traitStmts = $traitAst->stmts; foreach ($traitStmts as $k1 => $traitStmt) { if ($traitStmt instanceof Node\Stmt\ClassMethod) { $methodName = strtolower($traitStmt->name->toString()); if (isset($traitMethods[$methodName])) { $this->fatalError($classStmt, "Trait `{$traitFullName}` method `{$methodName}` already exists"); } $fullMethodName = $this->getFullMethodName($traitFullName, $methodName); if (isset($classDef->traitAliases[$fullMethodName])) { $alias = $classDef->traitAliases[$fullMethodName]; $methodName = $alias['newName']; $traitStmt->name = new Node\Identifier($methodName); if ($alias['newModifier']) { $traitStmt->flags = $alias['newModifier']; } } if (isset($classDef->traitIgnored[$fullMethodName])) { unset($traitStmts[$k1]); continue; } if (isset($methods[$methodName])) { unset($traitStmts[$k1]); } $traitMethods[$methodName] = $traitStmt; } if ($traitStmt instanceof Node\Stmt\ClassConst) { foreach ($traitStmt->consts as $k2 => $const) { $constName = strtolower($const->name->toString()); if (isset($constants[$constName])) { unset($traitStmts[$k1][$k2]); } if (isset($traitConstants[$constName])) { $this->fatalError($classStmt, "Trait `{$traitFullName}` constant `{$constName}` already exists"); } $traitConstants[$constName] = $const; } } if ($traitStmt instanceof Node\Stmt\Property) { foreach ($traitStmt->props as $k2 => $prop) { $propName = strtolower($prop->name->toString()); if (isset($properties[$propName])) { unset($traitStmts[$k1][$k2]); } if (isset($traitProperties[$propName])) { $this->fatalError($classStmt, "Trait `{$traitFullName}` property `{$propName}` already exists"); } $traitProperties[$propName] = $prop; } } } $stmt->stmts = array_merge($stmt->stmts, $traitStmts); } } } protected function parseClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string { $this->class = $this->parseIdentifier($class->name); $fullName = $this->getFullClassName(); if (!$this->hasClass($fullName)) { $this->fatalError($class, "class {$fullName} not found"); } $this->classDef = $this->getClass($fullName); // 如果不是继承自内置类,需要检查父类是否存在,在预处理阶段只需检查了是否继承内置类 // 目前不允许继承自动态加载的自定义类 if ($this->classDef->extends and !$this->classDef->inheritedFromInternalClass) { $parentClass = $this->getParentClass($class->extends); if ($this->hasClass($parentClass)) { $parent = $this->getClass($parentClass); // 父类是 final 无法继承 if ($parent->flags & Modifiers::FINAL) { $this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$parentClass}`"); } } else { $this->fatalError($class, "Class `{$this->class}` inherits from a non-existent class `{$parentClass}`"); } } if (is_array($this->classDef->implements)) { foreach ($this->classDef->implements as $interfaceName) { if (!$this->hasInterface($interfaceName) and !$this->isInternalInterface($interfaceName)) { $this->fatalError($class, "Class `{$this->class}` implements a non-existent interface `{$interfaceName}`"); } } } $className = $this->classDef->getNamespacedName(); $this->classesDefineInFile[$className] = $this->classDef; $methodCodes = []; foreach ($class->stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_ClassConst': case 'Stmt_Property': case 'Stmt_Nop': case 'Stmt_EnumCase': break; case 'Stmt_ClassMethod': $this->parseClassMethod($v, $methodCodes); break; case 'Stmt_TraitUse': $this->parseTraitUse($v, $methodCodes); break; default: abort($v); break; } } $code = $this->genNativeMethod($methodCodes); $oriCtx = $this->context; $this->context = $this->classDef->propertyContext; $this->classDef->ctorInit .= $this->genScopeVarDecl() . $this->parseBeforeStmtLines(); $this->classDef->ctorClean .= $this->parseAfterStmtLines(); $this->context = $oriCtx; $this->resetClass(); return $code; } protected function genNativeMethod(array $methodCodes): string { $code = ''; $classDef = $this->classDef; foreach ($classDef->methods as $method) { $code .= $methodCodes[$method->name] . PHP_EOL; } $code .= PHP_EOL; return $code; } protected function genWrapperFunctionArgs(string $fn, FunctionDef $functionDef): string { $cppCode = ''; $callParams = ''; foreach ($functionDef->argInfoList as $k => $argInfo) { $var = 'arg_' . $argInfo->name; if ($argInfo->variadic) { $cppCode .= $this->getIndent() . self::TYPE_ARRAY . ' ' . $var . ';' . PHP_EOL; $cppCode .= $this->getIndent() . 'for (uint32_t i = ' . $k . '; i < php::getCallArgNum(); i++) {' . PHP_EOL; $this->indentLevel++; $cppCode .= $this->getIndent() . $var . '.append(php::getCallArg(i));' . PHP_EOL; $this->indentLevel--; $cppCode .= '}' . PHP_EOL; } else { if ($argInfo->default) { if ($argInfo->byRef) { $argExpr = 'php::getCallArgByRef(' . $k . ', ' . $argInfo->default . ')'; } else { $argExpr = 'php::getCallArg(' . $k . ', ' . $argInfo->default . ')'; } } else { if ($argInfo->byRef) { $argExpr = 'php::getCallArgByRef(' . $k . ')'; } elseif ($argInfo->nullable) { $argExpr = 'php::getCallArg(' . $k . ', php::null)'; } else { $argExpr = 'php::getCallArg(' . $k . ')'; } } $cppType = ($argInfo->type === self::TYPE_STREAM || $argInfo->type === self::TYPE_BOX) ? self::TYPE_VAR : $argInfo->type; $expr = $this->convertExprFromType($argInfo->type, $argExpr); $cppCode .= $this->getIndent() . $cppType . ' ' . $var . ' = ' . $expr . ';' . PHP_EOL; } $callParams .= 'arg_' . $argInfo->name . ','; } if ($functionDef->method) { $callParams = $functionDef->argInfoList ? 'this_, ' . rtrim($callParams, ',') : 'this_'; } else { $callParams = $functionDef->argInfoList ? rtrim($callParams, ',') : ''; } if ($functionDef->returnType !== self::TYPE_VOID) { $cppCode .= $this->getIndent() . 'auto retval = ' . $fn . '(' . $callParams . ');' . PHP_EOL; $cppCode .= $this->getIndent() . 'php::move(retval, return_value);' . PHP_EOL; $cppCode .= $this->getIndent() . 'php::deref(return_value);' . PHP_EOL; } else { $cppCode .= $this->getIndent() . $fn . '(' . $callParams . ');' . PHP_EOL; } $cppCode .= '}' . PHP_EOL . PHP_EOL; return $cppCode; } protected function genMethodWrapper(ClassDef $classDef, MethodDef $methodDef): string { $name = $classDef->getNamespacedName(); $cppCode = 'ZEND_METHOD(' . $name . ', ' . $methodDef->name . '){' . PHP_EOL; $cppCode .= $this->getIndent() . self::TYPE_OBJECT . ' this_(&execute_data->This);' . PHP_EOL; $fn = self::PREFIX . $this->getNativeMethodName($classDef, $methodDef); $cppCode .= $this->genWrapperFunctionArgs($fn, $methodDef->functionDef); return $cppCode; } protected function getClassRegisterCeFunc(ClassDef|InterfaceDef $classDef): string { $cppCode = ''; $name = $classDef->getNamespacedName(); $argsDef = $this->getRegisterClassFunctionArgDef($classDef); $param = $this->getRegisterClassFunctionArgs($classDef); $cppCode .= 'zend_class_entry *' . $this->getRegisterClassFunction($name) . '(' . $argsDef . ') {' . PHP_EOL; $cppCode .= $this->getIndent() . 'return register_class_' . $name . '(' . $param . ');' . PHP_EOL; $cppCode .= '}' . PHP_EOL . PHP_EOL; return $cppCode; } protected function genClassWrapper(ClassDef|InterfaceDef $classDef): string { $cppCode = ''; // 接口没有方法实体 if ($classDef instanceof ClassDef) { $arrayPropCount = 0; foreach ($classDef->properties as $property) { if ($property->type === self::TYPE_ARRAY and $property->default and $property->default !== self::TYPE_ARRAY . '{}') { $fullClassName = $classDef->getNamespacedName(false); $fullPropName = $fullClassName . '::' . $property->name; if ($property->isStatic()) { $prop = new \stdClass(); $prop->class = $fullClassName; $prop->name = $property->name; $prop->default = $property->default; $this->defaultStaticPropertyList[$fullPropName] = $prop; } else { $this->defaultPropertyList[$fullPropName] = $property->default; $arrayPropCount++; } } } if ($arrayPropCount > 0) { $classDef->requireCtor = true; } $methods = $classDef->methods; foreach ($methods as $methodDef) { $cppCode .= $this->genMethodWrapper($classDef, $methodDef); } } return $cppCode; } /** * @param array $list */ protected function genClassConstantList(array $list): string { $code = ''; foreach ($list as $const) { $code .= $this->getIndent() . $this->genClassConstant($const); } return $code; } protected function genClassConstant(ConstantDef $const): string { return 'static const ' . $const->type . ' ' . $const->name . ';' . PHP_EOL; } /** * @param array $list */ protected function genClassPropertyList(array $list): string { $code = ''; foreach ($list as $prop) { $code .= $this->getIndent() . $this->genClassProperty($prop); } return $code; } protected function genClassProperty(PropertyDef $prop): string { $code = $prop->type . ' ' . $prop->name; if ($prop->default !== null) { $code .= ' = ' . $prop->default; } return $code . ';' . PHP_EOL; } protected function genFunction(string $name, string $returnType, array $args = [], array $lines = []): string { $_args = []; foreach ($args as $arg => $type) { $_args[] = $type . ' ' . $arg; } $code = $returnType . ' ' . $name . '(' . implode(', ', $_args) . ') {' . PHP_EOL; $code .= implode(PHP_EOL, $lines) . PHP_EOL; $code .= '}' . PHP_EOL; return $code; } /** * Build type-check descriptor array from UnionType or NullableType AST node. * Returns ['check' => array, 'typeStr' => string] or empty check array if no check needed. */ /** * Generate a C++ boolean expression for a single type descriptor entry. */ /** * Generate C++ runtime type-check block for a function parameter with union/nullable type. */ /** * Generate C++ runtime type-check block for a function return value with union/nullable type. */ /** * @throws \Exception */ protected function parseFunction(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): string { $this->resetFunction(); $name = $this->getFunctionName($v); $this->function = $this->parseIdentifier($v->name); if (!$this->hasFunction($name)) { $this->fatalError($v, 'Function `' . $name . '` not found'); } $this->functionDef = $this->getFunction($name); // 类方法不要保存到 functions 中 if ($this->methodDef) { $this->methodDef->functionDef = $this->functionDef; } else { $this->functionDefineInFile[$name] = $this->functionDef; } // stub 函数,没有函数的具体实现,只有声明,实现在 C++ 或者 .so 中定义 if ($this->functionDef->stub) { $this->resetFunction(); return ''; } if ($this->class) { $this->addArgument('this_', self::TYPE_OBJECT); } foreach ($this->functionDef->argInfoList as $argInfo) { $this->addArgument($argInfo->name, $argInfo->variadic ? self::TYPE_ARRAY : $argInfo->type); if (!$argInfo->variadic and $argInfo->class) { $this->addObject($argInfo->name, $argInfo->class); } } // Build SSA/e-SSA analysis for this function if ($v->stmts) { $this->context->ssaBuilder = new SsaBuilder($v->stmts, $this->functionDef->argInfoList); $this->context->ssaBuilder->build(); // Narrow local variable types based on SSA analysis $this->optimizeVarTypes(); // Narrow range-proven loop counters independent of native_types $this->optimizeLoopVars(); // Analyze object stability for property reference hoisting $this->optimizeObjectProps(); } $stmts = ''; if ($v->stmts) { $this->indentLevel++; try { $stmts = $this->parseStmts($v->stmts); if (!$this->isReturnStmtInLastLine($v->stmts)) { $stmts .= $this->genReturnCode(); } } catch (Skip) { $this->climate->cyan('Skip function ' . $name); } $this->indentLevel--; } else { $stmts = $this->genReturnCode(); } $functionDeclCode = $this->getReturnType() . ' ' . self::PREFIX . $name . '('; if ($this->class) { $functionDeclCode .= self::TYPE_OBJECT . ' &this_'; if ($this->functionDef->params) { $functionDeclCode .= ', '; } } $functionDeclCode .= $this->functionDef->params . ')'; $code = $functionDeclCode . ' {' . PHP_EOL; $this->indentLevel++; $code .= $this->genScopeVarDecl(); $code .= "\n"; // Constructor Property Promotion foreach ($this->functionDef->argInfoList as $argInfo) { if (!$argInfo->property) { continue; } $code .= $this->genPropertyPromotion($argInfo); } // Runtime union/nullable parameter type checks foreach ($this->functionDef->argInfoList as $i => $argInfo) { if (!empty($argInfo->typeCheck)) { $code .= $this->genUnionParamCheck($argInfo, $i); } } $this->indentLevel--; // 构建 PHP 级别的函数名用于 debug backtrace if ($this->class) { $debugName = $this->class . '::' . $this->function; } else { $debugName = $this->function; if ($this->namespace) { $debugName = $this->namespace . '\\' . $debugName; } } $code .= $this->genDebugInfo(null, $debugName, $v->getStartLine()); // 函数中存在动态调用的函数,需要在运行时动态切换作用域 if ($this->methodDef and $this->methodDef->hasDynamicCall) { $code .= $this->genScopeSwitchCode(); } $code .= $stmts; $code .= "}\n"; $this->resetFunction(); return $code; } /** * 检查父类方法是否可以被重写,私有方法不能被重写 */ protected function checkParentMethodCanBeOverridden(Node\Stmt\ClassMethod $v, string $name): void { $classDef = $this->classDef; while (true) { $extends = $classDef->extends; if (!$extends) { break; } // 父类是内置类 if ($classDef->inheritedFromInternalClass) { if (Reflection::getClassMethodModifiers($extends, $name) & \ReflectionMethod::IS_PRIVATE) { goto _error; } break; } $classDef = $this->getClass($extends); if ($classDef->hasMethod($this->method)) { $methodDef = $classDef->getMethod($this->method); if ($methodDef->flags & Modifiers::PRIVATE) { _error: $this->fatalError($v, 'Cannot override private method `' . $extends . '::' . $this->method . '()`'); } } } } protected function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void { $name = $this->getMethodName($v); $this->method = $name; $flags = $this->parseModifiers($v->flags); if (!($flags & Modifiers::ABSTRACT)) { $this->methodDef = $this->classDef->getMethod($name); // 预处理阶段没有父类的信息,只能在实现阶段检查 $this->checkParentMethodCanBeOverridden($v, $name); $methodCodes[$name] = $this->parseFunction($v); } $this->resetMethod(); } protected function parseTraitUse(Node\Stmt\TraitUse $v, array &$methodCodes): void { $classDef = $this->classDef; foreach ($v->traits as $trait) { $traitName = $this->parseIdentifier($trait); $traitFullName = $this->getNamespacedClassName($traitName); if (!$this->hasClass($traitFullName)) { $this->fatalError($v, $traitFullName . ' not found'); } $traitDef = $this->getClass($traitFullName); // 将 Trait 中定义的 常量、静态常量、属性、方法、静态属性复制到当前类中 foreach ($traitDef->constants as $const) { if ($classDef->hasConstant($const->name)) { continue; } $classDef->constants[$const->name] = $const; } foreach ($traitDef->properties as $prop) { if ($classDef->hasProperty($prop->name)) { continue; } $classDef->properties[$prop->name] = $prop; } foreach ($traitDef->methods as $methodDef) { $classMethodName = $traitMethodName = $methodDef->name; $fullMethodName = $this->getFullMethodName($traitFullName, $traitMethodName); // Trait 设置了别名 if (isset($classDef->traitAliases[$fullMethodName])) { $alias = $classDef->traitAliases[$fullMethodName]; $methodDef = clone $methodDef; $classMethodName = $methodDef->name = $alias['newName']; if ($alias['newModifier']) { $methodDef->flags = $this->parseModifiers($alias['newModifier']); } } // 设置了 insteadof 选项,此 Trait 的方法将不会被使用 if (isset($classDef->traitIgnored[$fullMethodName])) { continue; } // 类中已经有同名方法,则不使用 Trait 中的方法 if ($classDef->hasMethod($classMethodName)) { continue; } $classDef->addMethod($methodDef); $traitMethodNativeName = $this->getNativeName($traitMethodName, $traitDef->namespace, $traitDef->name); $classMethodNativeName = $this->getNativeName($classMethodName, $classDef->namespace, $classDef->name); $argList = ['this_']; foreach ($methodDef->functionDef->argInfoList as $argInfo) { $argList[] = $argInfo->name; } $argv = implode(', ', $argList); $code = $methodDef->getReturnType() . ' ' . self::PREFIX . $classMethodNativeName . '('; if ($this->class) { $code .= self::TYPE_OBJECT . ' &this_'; if ($methodDef->functionDef->params) { $code .= ', '; } } $this->addFunction($classMethodNativeName, $methodDef->functionDef); $code .= $methodDef->functionDef->params . ')'; $code .= '{' . PHP_EOL; $this->indentLevel++; $methodCall = self::PREFIX . $traitMethodNativeName . '(' . $argv . ')'; if ($methodDef->getReturnType() !== self::TYPE_VOID) { $methodCall = 'return ' . $methodCall; } $code .= $this->getIndent() . $methodCall . ';' . PHP_EOL; $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; $methodCodes[$classMethodName] = $code; } } } protected function parseForeachObject(Foreach_ $node): string { $obj = $this->parseIdentifier($node->expr); $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_OBJECT); $tmpArrayVar = $this->genTmpVarName(); $this->addLocalVar($tmpArrayVar, self::TYPE_ARRAY); $code = 'if (' . $obj . '.instanceOf("IteratorAggregate")) {' . PHP_EOL; $code .= $this->getIndent() . $tmpVar . ' = ' . $obj . '.call("getIterator");' . PHP_EOL . '}' . PHP_EOL; $code .= 'else if (' . $obj . '.instanceOf("Iterator")) {' . PHP_EOL; $code .= $this->getIndent() . $tmpVar . ' = ' . $obj . ';' . PHP_EOL . '}' . PHP_EOL; $code .= 'if (' . $tmpVar . ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->getIndent() . $tmpVar . '.call("rewind");' . PHP_EOL; $code .= $this->getIndent() . 'for (;' . $tmpVar . '.call("valid"); ' . $tmpVar . '.call("next")) {' . PHP_EOL; $this->indentLevel++; if ($node->valueVar instanceof List_) { $listTmpVar = $this->genTmpVarName(); $this->addLocalVar($listTmpVar, self::TYPE_VAR); $code .= $this->getIndent() . ' ' . $listTmpVar . ' = ' . $tmpVar . '.call("current");' . PHP_EOL; if ($node->keyVar) { $keyVar = $this->parseIdentifier($node->keyVar); $this->checkVar($node, $keyVar); $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call("key");' . PHP_EOL; } $code .= $this->parseForeachItemAsList($listTmpVar, $node->valueVar->items); } else { $valueVar = $this->parseIdentifier($node->valueVar); $this->checkVar($node, $valueVar); $code .= $this->getIndent() . ' ' . $valueVar . ' = ' . $tmpVar . '.call("current");' . PHP_EOL; if ($node->keyVar) { $keyVar = $this->parseIdentifier($node->keyVar); $this->checkVar($node, $keyVar); $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call("key");' . PHP_EOL; } } $code .= $this->parseStmts($node->stmts); $code .= $this->genLoopEndFlagCheck(); $code .= '}' . PHP_EOL; $this->indentLevel--; $code .= $this->getIndent() . '} else {' . PHP_EOL; $code .= $this->getIndent() . $tmpArrayVar . ' = php::call("get_object_vars", {' . $obj . '});' . PHP_EOL; $code .= $this->parseForeachArray($node, $tmpArrayVar); $this->indentLevel--; $code .= '}' . PHP_EOL; return $code; } private function getRegisterClassFunctionArgDef(ClassDef|InterfaceDef $classDef): string { $depsCeList = $this->getRegisterClassFunctionCeList($classDef); if (empty($depsCeList)) { return ''; } return 'zend_class_entry *' . implode(', zend_class_entry *', $depsCeList); } private function getImplementCe(ClassDef $classDef): array { $list = []; foreach ($classDef->implements as $interface) { $list[] = self::PREFIX . 'class_entry_' . $this->escapeCeName($interface); } return $list; } private function genFunctionWrapper(FunctionDef $functionDef): string { $name = $this->escapeZendFnName($functionDef->getNamespacedName()); $cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL; $fn = self::PREFIX . $this->getNativeName($functionDef->name, $functionDef->namespace); $cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef); return $cppCode; } private function genClassNative(): string { $code = 'class ' . $this->class . ' { '; $publicMethods = []; $protectedMethods = []; $privateMethods = []; $publicConstants = []; $protectedConstants = []; $privateConstants = []; $publicProperties = []; $protectedProperties = []; $privateProperties = []; foreach ($this->classDef->constants as $const) { if ($const->flags & Modifiers::PUBLIC) { $publicConstants[] = $const; } if ($const->flags & Modifiers::PROTECTED) { $protectedConstants[] = $const; } if ($const->flags & Modifiers::PRIVATE) { $privateConstants[] = $const; } } foreach ($this->classDef->methods as $method) { if ($method->flags & Modifiers::PUBLIC) { $publicMethods[] = $method; } if ($method->flags & Modifiers::PROTECTED) { $protectedMethods[] = $method; } if ($method->flags & Modifiers::PRIVATE) { $privateMethods[] = $method; } } foreach ($this->classDef->properties as $property) { if ($property->flags & Modifiers::PUBLIC) { $publicProperties[] = $property; } if ($property->flags & Modifiers::PROTECTED) { $protectedProperties[] = $property; } if ($property->flags & Modifiers::PRIVATE) { $privateProperties[] = $property; } } if ($privateConstants) { $code .= 'private:' . PHP_EOL; $code .= $this->genClassConstantList($privateConstants); } if ($protectedConstants) { $code .= 'protected:' . PHP_EOL; $code .= $this->genClassConstantList($protectedConstants); } if ($publicConstants) { $code .= 'public:' . PHP_EOL; $code .= $this->genClassConstantList($publicConstants); } if ($privateProperties) { $code .= 'private:' . PHP_EOL; $code .= $this->genClassPropertyList($privateProperties); } if ($protectedProperties) { $code .= 'protected:' . PHP_EOL; $code .= $this->genClassPropertyList($protectedProperties); } if ($publicProperties) { $code .= 'public:' . PHP_EOL; $code .= $this->genClassPropertyList($publicProperties); } $code .= '};' . PHP_EOL . PHP_EOL; return $code; } }