templatePath = __DIR__ . '/../../templates/composer.json.tpl.php'; } /** * 获取用户输入的包信息 */ public function gatherPackageInfo(SymfonyStyle $io, ?string $targetPath = null): ?array { try { // 尝试读取现有的composer.json文件以获取默认值 $existingInfo = $this->readExistingComposerInfo($targetPath, $io); // 如果找到了完整的现有信息,询问用户是否直接使用 if ($this->hasCompleteExistingInfo($existingInfo)) { $io->section('发现现有扩展配置'); $io->table( ['字段', '现有值'], [ ['包名', $existingInfo['name'] ?? 'N/A'], ['描述', $existingInfo['description'] ?? 'N/A'], ['命名空间', $existingInfo['namespace'] ?? 'N/A'], ['扩展ID', $existingInfo['id'] ?? 'N/A'] ] ); if ($io->confirm('确定使用当前配置?', true)) { return [ 'packageName' => $existingInfo['name'], 'description' => $existingInfo['description'], 'namespace' => $existingInfo['namespace'], 'id' => $existingInfo['id'] ]; } } // 获取包名 $defaultPackageName = $existingInfo['name'] ?? null; $packageName = $io->ask( '请输入Composer包名 (例如: six-shop/hello 或sixdec/abc)', $defaultPackageName ); if (empty($packageName)) { throw new \InvalidArgumentException('包名不能为空!'); } // 验证包名格式 if (!preg_match('/^[a-z0-9-_]+\/[a-z0-9-_]+$/', $packageName)) { throw new \InvalidArgumentException('包名格式不正确! 正确格式: six-shop/hello 或sixdec/abc'); } // 获取包描述 $defaultDescription = $existingInfo['description'] ?? 'A SixShop extension package'; $description = $io->ask('请输入包描述 (可选)', $defaultDescription); // 获取命名空间 $defaultNamespace = $existingInfo['namespace'] ?? $this->generateDefaultNamespace($packageName); // 用户界面显示单反斜杠格式,用户输入也是单反斜杠 $userNamespace = $io->ask('请输入命名空间 (例如: SixShop\\Hello\\)', $defaultNamespace); // PHP代码内部使用单反斜杠存储 $namespace = rtrim($userNamespace, '\\') . '\\'; // 获取扩展ID $defaultId = $existingInfo['id'] ?? str_replace('/', '-', $packageName); $id = $io->ask('请输入扩展ID (例如: six-shop-hello)', $defaultId); return [ 'packageName' => $packageName, 'description' => $description, 'namespace' => $namespace, 'id' => $id ]; } catch (\InvalidArgumentException $e) { $io->error($e->getMessage()); return null; } } /** * 生成默认命名空间 */ private function generateDefaultNamespace(string $packageName): string { $parts = explode('/', $packageName); $vendor = $this->convertToNamespace($parts[0]); $package = $this->convertToNamespace($parts[1]); return $vendor . '\\' . $package . '\\'; } /** * 将字符串转换为命名空间格式 */ private function convertToNamespace(string $string): string { $parts = explode('-', $string); $parts = array_map('ucfirst', $parts); return implode('', $parts); } /** * 读取现有的composer.json文件以获取默认值 */ private function readExistingComposerInfo(?string $targetPath, SymfonyStyle $io): array { $existingInfo = []; if ($targetPath === null) { return $existingInfo; } if (is_dir($targetPath)) { // 首先检查目标目录下是否直接有composer.json文件 $directComposerPath = $targetPath . '/composer.json'; if (file_exists($directComposerPath)) { $existingInfo = $this->parseComposerFile($directComposerPath, $io); if (!empty($existingInfo)) { return $existingInfo; } } // 然后扫描目标目录下的所有子目录,查找composer.json文件 $directories = array_filter(glob($targetPath . '/*'), 'is_dir'); foreach ($directories as $dir) { $composerPath = $dir . '/composer.json'; if (file_exists($composerPath)) { $existingInfo = $this->parseComposerFile($composerPath, $io); if (!empty($existingInfo)) { // 只取第一个找到的文件 break; } } } } return $existingInfo; } /** * 解析composer.json文件并提取相关信息 */ private function parseComposerFile(string $composerPath, SymfonyStyle $io): array { $existingInfo = []; $composerContent = file_get_contents($composerPath); if ($composerContent !== false) { $composerData = json_decode($composerContent, true); if (json_last_error() === JSON_ERROR_NONE && is_array($composerData)) { $io->note('发现现有的composer.json文件: ' . $composerPath); // 提取相关信息 $existingInfo['name'] = $composerData['name'] ?? null; $existingInfo['description'] = $composerData['description'] ?? null; // 从 autoload PSR-4 中提取命名空间 if (isset($composerData['autoload']['psr-4'])) { $psr4 = $composerData['autoload']['psr-4']; $namespaces = array_keys($psr4); if (!empty($namespaces)) { // 取第一个命名空间,JSON已经解析为单反斜杠,保持PHP内部单反斜杠格式 $existingInfo['namespace'] = rtrim($namespaces[0], '\\') . '\\'; } } // 从 extra.sixshop 中提取ID if (isset($composerData['extra']['sixshop']['id'])) { $existingInfo['id'] = $composerData['extra']['sixshop']['id']; } } } return $existingInfo; } /** * 检查是否有完整的现有信息 */ private function hasCompleteExistingInfo(array $existingInfo): bool { return !empty($existingInfo['name']) && !empty($existingInfo['description']) && !empty($existingInfo['namespace']) && !empty($existingInfo['id']); } /** * 生成composer.json内容 */ public function generateContent(string $packageName, string $namespace, string $id, string $description = 'A SixShop extension package'): string { if (!file_exists($this->templatePath)) { throw new \RuntimeException('模板文件不存在: ' . $this->templatePath); } // 准备模板变量 $name = $packageName; // 生成composer.json内容 ob_start(); include $this->templatePath; return ob_get_clean(); } /** * 保存composer.json文件到指定目录 */ public function saveComposerFile(string $packageName, string $content, string $targetPath, SymfonyStyle $io): bool { $extensionDir = $targetPath; // 确保目录存在 if (!is_dir($extensionDir)) { if (!mkdir($extensionDir, 0755, true)) { $io->error('无法创建目录: ' . $extensionDir); return false; } } $outputPath = $extensionDir . '/composer.json'; if (file_put_contents($outputPath, $content) !== false) { $io->success('composer.json 文件已成功生成: ' . $outputPath); return true; } else { $io->error('无法写入文件: ' . $outputPath); return false; } } /** * 完整的生成和保存流程 */ public function createComposerJson(string $packageName, string $namespace, string $id, string $description, string $targetPath, SymfonyStyle $io): bool { try { // 生成内容 $content = $this->generateContent($packageName, $namespace, $id, $description); // 输出生成的内容 $io->section('生成的 composer.json 内容:'); $io->text($content); // 询问是否保存文件 if ($io->confirm('是否将内容保存到 composer.json 文件?', true)) { return $this->saveComposerFile($packageName, $content, $targetPath, $io); } return true; } catch (\Exception $e) { $io->error('生成 composer.json 时发生错误: ' . $e->getMessage()); return false; } } /** * 完整的从用户输入到生成文件的工作流 */ public function createComposerFromUserInput(SymfonyStyle $io, ?string $targetPath = null): bool { try { // 获取用户输入 $packageInfo = $this->gatherPackageInfo($io); // 如果没有提供目标路径,使用默认路径 if ($targetPath === null) { $vendorDir = dirname(__DIR__, 5); $targetPath = $vendorDir . '/runtime/extension'; } // 生成composer.json return $this->createComposerJson( $packageInfo['packageName'], $packageInfo['namespace'], $packageInfo['id'], $packageInfo['description'], $targetPath, $io ); } catch (\InvalidArgumentException $e) { $io->error($e->getMessage()); return false; } } }