如何从Laravel FormRequest规则自动生成嵌套OpenAPI请求体?
解决方案:解析Laravel验证规则生成嵌套OpenAPI注解
核心思路
要处理带.*的嵌套规则,需要分两步实现:
- 将扁平的规则键(如
destinations.*.details.*.trip_cost_category_id)转换为嵌套的Schema树结构 - 递归遍历Schema树,生成符合L5 Swagger规范的
@OA注解字符串
第一步:构建嵌套Schema树
先实现一个工具函数,把扁平规则解析成层级化的结构,同时标记必填字段:
/** * 将Laravel验证规则转换为嵌套Schema树 * @param array $rules 控制器的rules数组 * @return array 包含schema树和顶层必填字段的数组 */ function buildSchemaTree(array $rules): array { $tree = []; $topLevelRequired = []; foreach ($rules as $key => $ruleString) { $segments = explode('.', $key); $currentNode = &$tree; $isRequired = str_contains($ruleString, 'required'); $fieldType = extractRuleType($ruleString); // 标记顶层必填字段 if ($isRequired && count($segments) === 1) { $topLevelRequired[] = $key; } foreach ($segments as $index => $segment) { $isLastSegment = $index === count($segments) - 1; $isArrayItem = $segment === '*'; if ($isArrayItem) { // 处理数组项:父节点标记为array类型,指向items子节点 if (!isset($currentNode['type']) || $currentNode['type'] !== 'array') { $currentNode['type'] = 'array'; $currentNode['items'] = []; } $currentNode = &$currentNode['items']; } else { // 处理普通属性节点 if (!isset($currentNode[$segment])) { $currentNode[$segment] = []; } if ($isLastSegment) { $currentNode[$segment]['type'] = $fieldType; // 标记嵌套属性的必填性 if ($isRequired && count($segments) > 1) { $parentNode = &$tree; // 回溯到父对象节点 for ($i = 0; $i < $index; $i++) { $seg = $segments[$i]; $parentNode = $seg === '*' ? &$parentNode['items'] : &$parentNode[$seg]; } $parentNode['required'][] = $segment; } } else { $currentNode = &$currentNode[$segment]; } } } unset($currentNode); } return [ 'tree' => $tree, 'required' => $topLevelRequired, ]; } /** * 从规则字符串中提取字段类型 * @param string $ruleString 如"required|integer:min=1" * @return string 字段类型(integer/string/array等) */ function extractRuleType(string $ruleString): string { $validTypes = ['integer', 'string', 'array', 'boolean', 'numeric', 'date']; $rules = explode('|', $ruleString); foreach ($rules as $rule) { $baseRule = explode(':', $rule)[0]; if (in_array($baseRule, $validTypes)) { return $baseRule; } } return 'string'; // 默认类型 }
第二步:生成L5 Swagger注解
基于Schema树递归生成注解字符串,处理缩进和嵌套结构:
/** * 生成完整的@OA\RequestBody注解 * @param array $schemaTree 从buildSchemaTree获取的结构 * @param array $topLevelRequired 顶层必填字段 * @param int $indent 基础缩进量 * @return string 注解字符串 */ function generateOaRequestBody(array $schemaTree, array $topLevelRequired, int $indent = 4): string { $indentStr = str_repeat(' ', $indent); $innerIndent = str_repeat(' ', $indent + 4); $annotation = "@OA\RequestBody(\n"; $annotation .= "{$indentStr}required=true,\n"; $annotation .= "{$indentStr}@OA\JsonContent(\n"; // 添加顶层必填字段 if (!empty($topLevelRequired)) { $requiredList = implode(', ', array_map(fn($f) => "\"{$f}\"", $topLevelRequired)); $annotation .= "{$innerIndent}required={$requiredList},\n"; } // 生成所有属性注解 $annotation .= generateOaProperties($schemaTree, $indent + 4); $annotation .= "{$indentStr})\n"; $annotation .= ")"; return $annotation; } /** * 递归生成@OA\Property注解 * @param array $node 当前Schema节点 * @param int $indent 当前缩进量 * @return string 属性注解片段 */ function generateOaProperties(array $node, int $indent): string { $indentStr = str_repeat(' ', $indent); $innerIndent = str_repeat(' ', $indent + 4); $properties = ''; foreach ($node as $propName => $propData) { // 跳过内部标记字段 if (in_array($propName, ['type', 'items', 'required'])) continue; $properties .= "{$indentStr}@OA\Property(\n"; $properties .= "{$innerIndent}property=\"{$propName}\",\n"; $properties .= "{$innerIndent}type=\"{$propData['type']}\",\n"; // 处理数组类型的items if (isset($propData['items'])) { $properties .= "{$innerIndent}@OA\Items(\n"; if (!empty($propData['items'])) { $properties .= generateOaProperties($propData['items'], $indent + 8); // 添加数组项的必填字段 if (isset($propData['items']['required'])) { $requiredList = implode(', ', array_map(fn($f) => "\"{$f}\"", $propData['items']['required'])); $properties .= str_repeat(' ', $indent + 8) . "required={$requiredList},\n"; } } $properties .= "{$innerIndent})\n"; } // 添加当前对象的必填字段 if (isset($propData['required'])) { $requiredList = implode(', ', array_map(fn($f) => "\"{$f}\"", $propData['required'])); $properties .= "{$innerIndent}required={$requiredList},\n"; } $properties .= "{$indentStr}),\n"; } // 移除最后一个多余的逗号 return rtrim($properties, ",\n") . "\n"; }
在自定义命令中使用
在你的GenerateSwaggerAnnotations命令的handle方法中调用上述函数:
public function handle() { // 示例:获取目标控制器的rules数组 $controller = new \App\Http\Controllers\YourController(); $rules = $controller->rules(); // 构建Schema树 $schemaData = buildSchemaTree($rules); // 生成注解 $annotation = generateOaRequestBody($schemaData['tree'], $schemaData['required']); // 后续逻辑:将注解写入控制器方法上方(需自行实现文件读写逻辑) // 例如:找到方法位置,插入注解字符串 }
扩展优化点
- 支持更多规则:可以扩展
extractRuleType函数,处理email(映射为format="email")、date_format(映射为format="date-time")等规则 - nullable处理:若规则包含
nullable,在注解中添加nullable=true - 数组验证规则:处理
min:3/max:10等数组规则,添加minItems/maxItems属性 - 文件读写优化:使用Laravel的文件操作工具类,精准定位控制器方法位置插入注解
内容的提问来源于stack exchange,提问作者Inji Aliyeva
相关产品推荐
相关产品推荐

