diff --git a/README.md b/README.md index 1d8ba2d..0c176c7 100644 --- a/README.md +++ b/README.md @@ -183,22 +183,26 @@ class UserController This bundle provides CLI commands to help you kickstart your documentation by generating Schemas and Request Bodies from your existing PHP classes (Entities, DTOs). -| Command | Description | Mandatory parameter | Example | -|:--------------------------------|:------------------------------------------------|-------------------------------------------------------------------|:--------------------------------------------------------------| -| `apidocbundle:component:schema` | Generates a shared **Schema** from a PHP Class. | No, an autocomplete will prompt you to choose a class | `bin/console apidocbundle:component:schema "App\Entity\User"` | -| `apidocbundle:component:body` | Generates a **Request Body** from a PHP Class. | No, an autocomplete will prompt you to choose a class | `bin/console apidocbundle:component:body "App\DTO\UserDTO"` | -| `apidocbundle:route:generate` | interactively generates a **Route** path. | Yes, at the moment, there is no autocomplete for route generation | `bin/console apidocbundle:route:generate /my/path` | +| Command | Description | Mandatory parameter | Example | +|:----------------------------------|:--------------------------------------------------|-------------------------------------------------------------------|:----------------------------------------------------------------| +| `apidocbundle:component:schema` | Generates a shared **Schema** from a PHP Class. | No, an autocomplete will prompt you to choose a class | `bin/console apidocbundle:component:schema "App\Entity\User"` | +| `apidocbundle:component:body` | Generates a **Request Body** from a PHP Class. | No, an autocomplete will prompt you to choose a class | `bin/console apidocbundle:component:body "App\DTO\UserDTO"` | +| `apidocbundle:component:parameter`| Generates a reusable **Parameter** component. | Yes, the name of the parameter component. | `bin/console apidocbundle:component:parameter "userId"` | +| `apidocbundle:route:generate` | interactively generates a **Route** path. | Yes, at the moment, there is no autocomplete for route generation | `bin/console apidocbundle:route:generate /my/path` | ### Command Options -| Option | Used in | Shortcut | Description | -|:--------------------|---------|:---------|:----------------------------------------------------| -| `--format` | All | `-f` | Output format: `yaml` (default), `php`, or `both`. | -| `--output` | All | `-o` | Custom output directory (relative to project root). | -| `--tag` | Route | `-t` | (Route only) Tags to associate with the route. | -| `--response-schema` | Route | `-rs` | (Route only) Reference schema for the response. | -| `--request-body` | Route | `-rb` | (Route only) Reference schema for the request body. | -| `--description` | Route | `-d` | (Route only) Description for the route. | +| Option | Used in | Shortcut | Description | +|:--------------------|------------|:---------|:---------------------------------------------------------------------------------| +| `--format` | All | `-f` | Output format: `yaml` (default), `php`, or `both`. | +| `--output` | All | `-o` | Custom output directory (relative to project root). | +| `--tag` | Route | `-t` | (Route only) Tags to associate with the route. | +| `--response-schema` | Route | `-rs` | (Route only) Reference schema for the response. | +| `--request-body` | Route | `-rb` | (Route only) Reference schema for the request body. | +| `--description` | All | `-d` | Description for the generated component. | +| `--in` | Parameter | | (Parameter only) Location of the parameter: `query`, `header`, `path`, `cookie`. | +| `--type` | Parameter | | (Parameter only) Schema type of the parameter: `string`, `integer`, etc. | +| `--required` | Parameter | | (Parameter only) Mark the parameter as required. | 📚 **[Read the Full Guide on References](docs/REFERENCES.md)** diff --git a/src/Command/ComponentGeneration/AbstractGenerateComponentCommand.php b/src/Command/ComponentGeneration/AbstractGenerateComponentCommand.php index 628dbd3..872b5b0 100644 --- a/src/Command/ComponentGeneration/AbstractGenerateComponentCommand.php +++ b/src/Command/ComponentGeneration/AbstractGenerateComponentCommand.php @@ -29,6 +29,7 @@ abstract class AbstractGenerateComponentCommand extends Command public const COMPONENT_SCHEMAS = 'schemas'; public const COMPONENT_REQUEST_BODIES = 'requestBodies'; + public const COMPONENT_PARAMETERS = 'parameters'; protected ?string $dumpLocation = null; /** @phpstan-ignore-next-line */ @@ -503,6 +504,9 @@ protected function generatePhpBuilderCode(array $array, string $componentName, s } elseif (self::COMPONENT_REQUEST_BODIES === $componentType) { $requestBody = $array['documentation']['components']['requestBodies'][$componentName] ?? []; $code .= $this->buildRequestBodyCode($componentName, $requestBody, 2); + } elseif (self::COMPONENT_PARAMETERS === $componentType) { + $parameter = $array['documentation']['components']['parameters'][$componentName] ?? []; + $code .= $this->buildParameterCode($componentName, $parameter, 2); } $code .= " }\n"; @@ -640,4 +644,34 @@ protected function buildRequestBodyCode(string $name, array $requestBody, int $i return $code; } + + /** + * @param array $parameter + */ + protected function buildParameterCode(string $name, array $parameter, int $indent): string + { + $pad = str_repeat(' ', $indent); + $code = "{$pad}\$builder->addParameter('{$name}')\n"; + + if (isset($parameter['in'])) { + $code .= "{$pad} ->in('{$parameter['in']}')\n"; + } + + if (isset($parameter['description'])) { + $description = addslashes($parameter['description']); + $code .= "{$pad} ->description('{$description}')\n"; + } + + if (isset($parameter['required']) && $parameter['required']) { + $code .= "{$pad} ->required()\n"; + } + + if (isset($parameter['schema'])) { + $code .= "{$pad} ->schema(['type' => '{$parameter['schema']['type']}'])\n"; + } + + $code .= "{$pad}->end();\n"; + + return $code; + } } diff --git a/src/Command/ComponentGeneration/GenerateComponentParameterCommand.php b/src/Command/ComponentGeneration/GenerateComponentParameterCommand.php new file mode 100644 index 0000000..4b4663f --- /dev/null +++ b/src/Command/ComponentGeneration/GenerateComponentParameterCommand.php @@ -0,0 +1,114 @@ +addArgument('name', InputArgument::REQUIRED, 'The name of the parameter component'); + $this->addOption('in', null, InputOption::VALUE_REQUIRED, 'The location of the parameter (query, header, path, cookie)'); + $this->addOption('description', 'd', InputOption::VALUE_OPTIONAL, 'A description for the parameter'); + $this->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'The schema type of the parameter (e.g., string, integer)', 'string'); + $this->addOption('required', null, InputOption::VALUE_NONE, 'Mark the parameter as required'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('name'); + $in = $input->getOption('in'); + $description = $input->getOption('description'); + $type = $input->getOption('type'); + $required = $input->getOption('required'); + + if (empty($in)) { + $in = $io->askQuestion(new ChoiceQuestion( + 'Please specify the location of the parameter (in)', + ['query', 'header', 'path', 'cookie'], + 'query' + )); + } + + if (empty($description)) { + $description = $io->askQuestion(new Question('Please provide a description for the parameter')); + } + + $parameterArray = [ + 'name' => $name, + 'in' => $in, + 'description' => $description, + 'required' => 'path' === $in || $required, + 'schema' => [ + 'type' => $type, + ], + ]; + + $array = self::createComponentArray(); + $array['documentation']['components'][self::COMPONENT_PARAMETERS][$name] = $parameterArray; + + if ($this->dumpLocation === $input->getOption('output')) { + $destination = self::COMPONENT_PARAMETERS; + } + + $format = $input->getOption('format'); + + if ('yaml' === $format || 'both' === $format) { + $this->generateYamlFile($array, $name, $input, $output, self::COMPONENT_PARAMETERS, $destination ?? null); + } + + if ('php' === $format || 'both' === $format) { + $this->generatePhpFile($array, $name, $input, $output, self::COMPONENT_PARAMETERS, $destination ?? null); + } + + return Command::SUCCESS; + } + + /** + * @param array $array + */ + protected function generatePhpBuilderCode(array $array, string $componentName, string $componentType): string + { + $parameter = $array['documentation']['components'][self::COMPONENT_PARAMETERS][$componentName]; + + $code = "addParameter('{$componentName}')\n"; + $code .= " ->in('{$parameter['in']}')\n"; + $code .= " ->description('{$parameter['description']}')\n"; + + if ($parameter['required']) { + $code .= " ->required()\n"; + } + + $code .= " ->schema(['type' => '{$parameter['schema']['type']}'])\n"; + $code .= " ->end();\n"; + $code .= " }\n"; + $code .= "};\n"; + + return $code; + } +}