Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -640,4 +644,34 @@ protected function buildRequestBodyCode(string $name, array $requestBody, int $i

return $code;
}

/**
* @param array<mixed> $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;
}
}
114 changes: 114 additions & 0 deletions src/Command/ComponentGeneration/GenerateComponentParameterCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace Ehyiah\ApiDocBundle\Command\ComponentGeneration;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'apidocbundle:component:parameter',
description: 'Generate a reusable parameter component'
)]
final class GenerateComponentParameterCommand extends AbstractGenerateComponentCommand
{
public const COMPONENT_PARAMETERS = 'parameters';

protected function configure(): void
{
parent::configure();

$this->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<mixed> $array
*/
protected function generatePhpBuilderCode(array $array, string $componentName, string $componentType): string
{
$parameter = $array['documentation']['components'][self::COMPONENT_PARAMETERS][$componentName];

$code = "<?php\n\n";
$code .= "use Ehyiah\\ApiDocBundle\\Builder\\ApiDocBuilder;\n";
$code .= "use Ehyiah\\ApiDocBundle\\Interfaces\\ApiDocConfigInterface;\n\n";
$code .= "return new class implements ApiDocConfigInterface {\n";
$code .= " public function configure(ApiDocBuilder \$builder): void\n";
$code .= " {\n";
$code .= " \$builder->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;
}
}
Loading