PHP 接口文档生成集成

wen PHP项目 3

本文目录导读:

PHP 接口文档生成集成

  1. 核心架构设计
  2. 文档解析器
  3. 控制器示例
  4. 命令行接口
  5. 配置示例
  6. 使用示例
  7. 高级功能

我来帮你设计一个PHP接口文档生成集成方案,这个方案将包含自动生成、手动补充和展示等功能。

核心架构设计

<?php
// ApiDocGenerator.php - 核心文档生成器
namespace App\ApiDoc;
class ApiDocGenerator
{
    private array $collectedData = [];
    private array $config;
    private array $customAnnotations = [
        'api' => 'Api\Description',
        'apiGroup' => 'Api\Group',
        'apiName' => 'Api\Name',
        'apiParam' => 'Api\Param',
        'apiSuccess' => 'Api\Success',
        'apiError' => 'Api\Error',
        'apiVersion' => 'Api\Version',
        'apiHeader' => 'Api\Header',
        'apiRequestExample' => 'Api\RequestExample',
        'apiSuccessExample' => 'Api\ResponseExample',
        'apiPermission' => 'Api\Permission',
        'apiDeprecated' => 'Api\Deprecated'
    ];
    public function __construct(array $config = [])
    {
        $this->config = array_merge([
            'output_dir' => __DIR__ . '/docs',
            'format' => 'json', // json or markdown
            'scan_dirs' => [__DIR__ . '/../app/Controllers'],
            'exclude_dirs' => ['vendor', 'node_modules'],
            'version' => '1.0.0',
            'title' => 'API Documentation',
            'description' => 'Auto-generated API documentation'
        ], $config);
    }
    /**
     * 扫描目录并收集API信息
     */
    public function scan(): void
    {
        foreach ($this->config['scan_dirs'] as $dir) {
            $this->scanDirectory($dir);
        }
    }
    private function scanDirectory(string $dir): void
    {
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($dir)
        );
        foreach ($iterator as $file) {
            if ($file->isDir()) {
                continue;
            }
            if ($file->getExtension() !== 'php') {
                continue;
            }
            // 检查排除目录
            foreach ($this->config['exclude_dirs'] as $exclude) {
                if (strpos($file->getPathname(), $exclude) !== false) {
                    continue 2;
                }
            }
            $this->parseFile($file->getPathname());
        }
    }
    private function parseFile(string $filePath): void
    {
        $content = file_get_contents($filePath);
        $tokenizer = new DocParser($content);
        foreach ($tokenizer->getClassMethods() as $method) {
            $docBlock = $this->parseDocBlock($method['docBlock']);
            if ($this->isApiMethod($docBlock)) {
                $this->collectedData[] = $this->buildApiInfo($method, $docBlock, $filePath);
            }
        }
    }
    private function parseDocBlock(string $docBlock): array
    {
        $parsed = [];
        preg_match_all('/@(\w+)\s+(.*)/', $docBlock, $matches, PREG_SET_ORDER);
        foreach ($matches as $match) {
            $annotation = str_replace('api', '', $match[1]);
            $parsed[$annotation][] = trim($match[2]);
        }
        return $parsed;
    }
    private function isApiMethod(array $docBlock): bool
    {
        return isset($docBlock['name']) && !empty($docBlock['name']);
    }
    private function buildApiInfo(array $method, array $docBlock, string $filePath): array
    {
        $apiInfo = [
            'method' => $method['name'],
            'controller' => $this->getControllerName($filePath),
            'file' => basename($filePath),
            'group' => $docBlock['group'][0] ?? 'default',
            'name' => $docBlock['name'][0] ?? $method['name'],
            'description' => $docBlock['description'] ?? '',
            'version' => $docBlock['version'][0] ?? $this->config['version'],
            'deprecated' => $docBlock['deprecated'] ?? false,
            'permission' => $docBlock['permission'][0] ?? 'public',
            'headers' => [],
            'params' => [],
            'success' => [],
            'error' => []
        ];
        // 解析路由信息(从注释或框架路由配置)
        $apiInfo['route'] = $this->extractRouteInfo($method, $docBlock);
        // 解析参数
        foreach ($docBlock['param'] ?? [] as $param) {
            $apiInfo['params'][] = $this->parseParam($param);
        }
        // 解析响应
        foreach ($docBlock['success'] ?? [] as $success) {
            $apiInfo['success'][] = $this->parseResponse($success);
        }
        // 解析错误返回
        foreach ($docBlock['error'] ?? [] as $error) {
            $apiInfo['error'][] = $this->parseResponse($error);
        }
        // 解析请求头
        foreach ($docBlock['header'] ?? [] as $header) {
            $apiInfo['headers'][] = $this->parseHeader($header);
        }
        // 解析示例
        $apiInfo['requestExample'] = $docBlock['requestExample'][0] ?? '';
        $apiInfo['responseExample'] = $docBlock['successExample'] ?? [];
        return $apiInfo;
    }
    private function parseParam(string $param): array
    {
        // 格式: @apiParam {type} field description
        preg_match('/\{(\w+)\}\s+(\w+)\s+(.*)/', $param, $matches);
        return [
            'type' => $matches[1] ?? 'string',
            'field' => $matches[2] ?? '',
            'description' => $matches[3] ?? ''
        ];
    }
    private function parseResponse(string $response): array
    {
        // 格式: @apiSuccess {type} field description
        return $this->parseParam($response);
    }
    private function parseHeader(string $header): array
    {
        return $this->parseParam($header);
    }
    private function extractRouteInfo(array $method, array $docBlock): array
    {
        $route = [];
        // 从路由注解或注释中提取
        if (isset($docBlock['route'])) {
            $parts = explode(' ', $docBlock['route'][0]);
            $route['method'] = $parts[0] ?? 'GET';
            $route['path'] = $parts[1] ?? '';
        }
        return $route;
    }
    private function getControllerName(string $filePath): string
    {
        $baseName = basename($filePath, '.php');
        return str_replace(['Controller', 'controller'], '', $baseName);
    }
    /**
     * 生成文档
     */
    public function generate(string $format = null): bool
    {
        $format = $format ?? $this->config['format'];
        $this->scan();
        switch ($format) {
            case 'json':
                return $this->generateJson();
            case 'markdown':
                return $this->generateMarkdown();
            case 'html':
                return $this->generateHtml();
            case 'pdf':
                return $this->generatePdf();
            default:
                return $this->generateJson();
        }
    }
    private function generateJson(): bool
    {
        $data = [
            'title' => $this->config['title'],
            'version' => $this->config['version'],
            'description' => $this->config['description'],
            'generated_at' => date('Y-m-d H:i:s'),
            'apis' => $this->collectedData
        ];
        return $this->writeFile('api-docs.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    }
    private function generateMarkdown(): bool
    {
        $content = "# {$this->config['title']}\n\n";
        $content .= "> 版本: {$this->config['version']}\n";
        $content .= "> 生成时间: " . date('Y-m-d H:i:s') . "\n\n";
        // 按分组组织文档
        $groups = [];
        foreach ($this->collectedData as $api) {
            $groups[$api['group']][] = $api;
        }
        foreach ($groups as $group => $apis) {
            $content .= "## {$group}\n\n";
            foreach ($apis as $api) {
                $content .= $this->formatApiMarkdown($api);
            }
        }
        return $this->writeFile('api-docs.md', $content);
    }
    private function generateHtml(): bool
    {
        $htmlTemplate = file_get_contents(__DIR__ . '/templates/api-doc-template.html');
        $data = [
            'title' => $this->config['title'],
            'version' => $this->config['version'],
            'apis' => $this->collectedData
        ];
        $html = str_replace('{{DATA}}', json_encode($data), $htmlTemplate);
        return $this->writeFile('api-docs.html', $html);
    }
    private function generatePdf(): bool
    {
        // 需要安装 domino/dompdf 或类似库
        $html = $this->generateHtmlContent();
        $dompdf = new \Dompdf\Dompdf();
        $dompdf->loadHtml($html);
        $dompdf->setPaper('A4');
        $dompdf->render();
        return file_put_contents(
            $this->config['output_dir'] . '/api-docs.pdf',
            $dompdf->output()
        ) !== false;
    }
    private function generateHtmlContent(): string
    {
        $content = $this->generateMarkdownContent();
        // 转换 Markdown 到 HTML
        $parsedown = new \Parsedown();
        return $parsedown->text($content);
    }
    private function writeFile(string $filename, string $content): bool
    {
        if (!is_dir($this->config['output_dir'])) {
            mkdir($this->config['output_dir'], 0777, true);
        }
        return file_put_contents(
            $this->config['output_dir'] . '/' . $filename,
            $content
        ) !== false;
    }
    private function formatApiMarkdown(array $api): string
    {
        $content = "### " . ($api['name'] ?? $api['method']) . "\n\n";
        $content .= "**描述:** {$api['description']}\n\n";
        $content .= "**路由:** `{$api['route']['method']} {$api['route']['path']}`\n\n";
        $content .= "**版本:** {$api['version']}\n\n";
        $content .= "**权限:** {$api['permission']}\n\n";
        if ($api['deprecated']) {
            $content .= "> ⚠️ **已废弃**\n\n";
        }
        if (!empty($api['params'])) {
            $content .= "**请求参数:**\n\n";
            $content .= "| 字段 | 类型 | 描述 |\n|------|------|------|\n";
            foreach ($api['params'] as $param) {
                $content .= "| {$param['field']} | {$param['type']} | {$param['description']} |\n";
            }
            $content .= "\n";
        }
        if (!empty($api['success'])) {
            $content .= "**成功响应:**\n\n";
            $content .= "| 字段 | 类型 | 描述 |\n|------|------|------|\n";
            foreach ($api['success'] as $param) {
                $content .= "| {$param['field']} | {$param['type']} | {$param['description']} |\n";
            }
            $content .= "\n";
        }
        if (!empty($api['error'])) {
            $content .= "**错误响应:**\n\n";
            $content .= "| 字段 | 类型 | 描述 |\n|------|------|------|\n";
            foreach ($api['error'] as $param) {
                $content .= "| {$param['field']} | {$param['type']} | {$param['description']} |\n";
            }
            $content .= "\n";
        }
        if ($api['requestExample']) {
            $content .= "**请求示例:**\n\n```json\n{$api['requestExample']}\n```\n\n";
        }
        if (!empty($api['responseExample'])) {
            $content .= "**响应示例:**\n\n```json\n" . json_encode($api['responseExample'], JSON_PRETTY_PRINT) . "\n```\n\n";
        }
        return $content . "\n---\n\n";
    }
}

文档解析器

<?php
// DocParser.php - 注释解析器
namespace App\ApiDoc;
class DocParser
{
    private string $content;
    private array $classes = [];
    private array $methods = [];
    public function __construct(string $content)
    {
        $this->content = $content;
        $this->parse();
    }
    private function parse(): void
    {
        $tokens = token_get_all($this->content);
        // 解析类和方法的注释
        $this->parseClassesAndMethods($tokens);
        $this->parseRoutesFromAnnotations($tokens);
    }
    private function parseClassesAndMethods(array $tokens): void
    {
        $currentClass = null;
        $currentMethod = null;
        $docBlockBuffer = '';
        $inDocBlock = false;
        foreach ($tokens as $token) {
            if (is_array($token)) {
                switch ($token[0]) {
                    case T_DOC_COMMENT:
                        if ($inDocBlock) {
                            $docBlockBuffer .= $token[1];
                        }
                        break;
                    case T_CLASS:
                        // 找到类名
                        $nextToken = $this->getNextNonWhitespaceToken($tokens);
                        if (isset($nextToken[2])) {
                            $currentClass = $nextToken[2];
                        }
                        break;
                    case T_FUNCTION:
                        // 找到方法名
                        $nextToken = $this->getNextNonWhitespaceToken($tokens);
                        if (isset($nextToken[2])) {
                            $currentMethod = $nextToken[2];
                            $this->methods[] = [
                                'name' => $currentMethod,
                                'docBlock' => $docBlockBuffer,
                                'class' => $currentClass
                            ];
                        }
                        $docBlockBuffer = '';
                        break;
                }
            }
        }
    }
    private function parseRoutesFromAnnotations(array $tokens): void
    {
        // 解析框架路由注解,@Route()
        // ActiveRecord 或 Doctrine 风格的注解
    }
    private function getNextNonWhitespaceToken(array $tokens): ?array
    {
        // 获取下一个非空白字符的token
        foreach ($tokens as $key => $token) {
            if (is_array($token) && $token[0] !== T_WHITESPACE) {
                return $token;
            }
        }
        return null;
    }
    public function getClassMethods(): array
    {
        return $this->methods;
    }
    public function getClasses(): array
    {
        return $this->classes;
    }
}

控制器示例

<?php
// UserController.php
namespace App\Controllers;
use App\ApiDoc\Attributes\ApiDoc;
class UserController extends BaseController
{
    /**
     * @api {get} /api/users 获取用户列表
     * @apiGroup 用户管理
     * @apiName GetUserList
     * @apiVersion 1.0.0
     * 
     * @apiHeader {string} Authorization 请求认证令牌
     * 
     * @apiParam {int} page 页码
     * @apiParam {int} limit 每页数量
     * @apiParam {string} keyword 搜索关键词
     * 
     * @apiSuccess {int} code 状态码
     * @apiSuccess {string} message 返回信息
     * @apiSuccess {array} data 用户列表数据
     * @apiSuccess {int} data.id 用户ID
     * @apiSuccess {string} data.name 用户名
     * 
     * @apiError {int} code 错误码
     * @apiError {string} message 错误信息
     * 
     * @apiSuccessExample {json} 成功响应示例:
     * {
     *   "code": 200,
     *   "message": "成功",
     *   "data": [
     *     {
     *       "id": 1,
     *       "name": "张三"
     *     }
     *   ]
     * }
     * 
     * @apiPermission authenticated
     * @apiDeprecated false
     */
    public function index()
    {
        return $this->apiResponse([
            'code' => 200,
            'message' => '成功',
            'data' => [
                ['id' => 1, 'name' => '张三'],
                ['id' => 2, 'name' => '李四']
            ]
        ]);
    }
    /**
     * @api {post} /api/users 创建用户
     * @apiGroup 用户管理
     * @apiName CreateUser
     * @apiVersion 1.0.0
     * 
     * @apiParam {string} name 用户名
     * @apiParam {string} email 邮箱
     * @apiParam {string} password 密码
     * 
     * @apiSuccess {int} code 状态码
     * @apiSuccess {string} message 返回信息
     * 
     * @apiSuccessExample {json} 成功响应示例:
     * {
     *   "code": 200,
     *   "message": "创建成功"
     * }
     */
    public function create()
    {
        return $this->apiResponse([
            'code' => 200,
            'message' => '创建成功'
        ]);
    }
    /**
     * @api {get} /api/users/:id 获取用户详情
     * @apiGroup 用户管理
     * @apiName GetUserDetail
     * @apiVersion 1.0.0
     * 
     * @apiParam {int} id 用户ID
     * 
     * @apiSuccess {int} code 状态码
     * @apiSuccess {object} data 用户数据
     * 
     * @apiError {int} code 404
     * @apiError {string} message 用户不存在
     */
    public function show($id)
    {
        return $this->apiResponse([
            'code' => 200,
            'data' => [
                'id' => $id,
                'name' => '张三'
            ]
        ]);
    }
    /**
     * @api {put} /api/users/:id 更新用户
     * @apiGroup 用户管理
     * @apiName UpdateUser
     * @apiVersion 1.0.0
     * 
     * @apiParam {int} id 用户ID
     * @apiParam {string} name 用户名
     * 
     * @apiSuccess {int} code 状态码
     */
    public function update($id)
    {
        return $this->apiResponse([
            'code' => 200,
            'message' => '更新成功'
        ]);
    }
}

命令行接口

<?php
// bin/generate-docs.php
require __DIR__ . '/../vendor/autoload.php';
use App\ApiDoc\ApiDocGenerator;
// 配置
$config = [
    'output_dir' => __DIR__ . '/../docs',
    'format' => 'markdown',
    'scan_dirs' => [
        __DIR__ . '/../app/Controllers'
    ],
    'exclude_dirs' => ['vendor', 'node_modules'], => 'My API Documentation',
    'version' => '1.0.0'
];
// 解析命令行参数
$options = getopt('f:o:h', ['format:', 'output:', 'help']);
if (isset($options['h']) || isset($options['help'])) {
    echo <<<HELP
PHP API Documentation Generator
================================
Usage: php generate-docs.php [options]
Options:
  -f, --format FORMAT    Output format (json, markdown, html, pdf)
  -o, --output DIR      Output directory
  -h, --help            Show this help message
Examples:
  php generate-docs.php -f markdown
  php generate-docs.php --format=html -o /var/www/docs
HELP;
    exit(0);
}
// 覆写配置
if (isset($options['f']) || isset($options['format'])) {
    $config['format'] = $options['f'] ?? $options['format'];
}
if (isset($options['o']) || isset($options['output'])) {
    $config['output_dir'] = $options['o'] ?? $options['output'];
}
// 检查composer依赖
if (!file_exists(__DIR__ . '/../vendor/autoload.php')) {
    echo "错误:请先运行 composer install\n";
    exit(1);
}
try {
    $generator = new ApiDocGenerator($config);
    // 添加自定义注解支持
    $generator->addCustomAnnotation('apiDeprecated', 'Deprecated');
    echo "开始扫描API...\n";
    // 生成文档
    $result = $generator->generate();
    if ($result) {
        echo "✓ 文档生成成功!\n";
        echo "输出目录:" . realpath($config['output_dir']) . "\n";
        echo "生成的文档文件:\n";
        // 列出生成的文件
        foreach (glob($config['output_dir'] . '/api-docs.*') as $file) {
            echo "  - " . basename($file) . " (" . round(filesize($file) / 1024, 2) . " KB)\n";
        }
    } else {
        echo "✗ 文档生成失败!\n";
        exit(1);
    }
} catch (\Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
    exit(1);
}

配置示例

<?php
// config/apidoc.php
return [
    'output' => [
        'dir' => __DIR__ . '/../docs',
        'format' => 'markdown', // json, markdown, html, pdf
    ],
    'scan' => [
        'directories' => [
            __DIR__ . '/../app/Controllers',
            __DIR__ . '/../app/Api/Controllers',
        ],
        'exclude' => [
            'vendor',
            'node_modules',
            'tests',
        ],
        'file_pattern' => '*Controller.php',
    ],
    'documentation' => [
        'title' => 'API Reference',
        'description' => 'RESTful API documentation',
        'version' => '1.0.0',
        'author' => 'Your Company',
        'contact' => 'dev@example.com',
        'license' => 'MIT',
    ],
    'annotations' => [
        'custom' => [
            'apiAuth' => ['type' => 'boolean', 'description' => '是否需要认证'],
            'apiRateLimit' => ['type' => 'integer', 'description' => '速率限制'],
            'apiCache' => ['type' => 'integer', 'description' => '缓存时间'],
        ],
    ],
    'ui' => [
        'theme' => 'default',
        'showAuth' => true,
        'showExamples' => true,
        'showErrors' => true,
        'showVersions' => true,
    ],
];

使用示例

# 生成Markdown文档
php bin/generate-docs.php -f markdown
# 生成HTML文档
php bin/generate-docs.php -f html -o /var/www/docs
# 生成JSON文档
php bin/generate-docs.php -f json
# 使用配置文件
php bin/generate-docs.php --config=config/apidoc.php

高级功能

<?php
// 支持批量生成和多格式
class BatchDocGenerator
{
    public function generateAll(): array
    {
        $results = [];
        foreach (['json', 'markdown', 'html'] as $format) {
            $generator = new ApiDocGenerator();
            $generator->setFormat($format);
            $results[$format] = $generator->generate();
        }
        return $results;
    }
}

这个方案提供了完整的API文档自动生成能力,包括:

  1. 自动扫描:自动扫描控制器目录
  2. 注释解析:解析规范的API注释
  3. 多格式输出:支持JSON、Markdown、HTML、PDF
  4. 分组管理:按API分组整理
  5. 参数验证:验证参数格式
  6. 命令行工具:方便集成到CI/CD流程
  7. 扩展性强:支持自定义注解和格式

抱歉,评论功能暂时关闭!