| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- <?php
- declare(strict_types=1);
- namespace SixShop\MakerBundle\Generator;
- use Symfony\Component\Console\Style\SymfonyStyle;
- class ComposerGenerator
- {
- private string $templatePath;
- public function __construct()
- {
- $this->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;
- }
- }
- }
|