/ PHP  AI  Cursor  Copilot  Slim  OpenAI  PHPUnit 

用 AI 工具开发 PHP 应用实战:Cursor + Copilot 全程提效指南


AI开发PHP应用

AI 编程工具正在改变 PHP 后端开发的工作流。无论是用 Cursor 生成完整的 RESTful API,还是让 Copilot 自动补全数据库查询,AI 都能显著压缩重复劳动。本文以构建一个带 AI 能力的 PHP 内容管理接口为例,演示 AI 辅助开发的完整流程。

1. 用 AI 快速搭建项目脚手架

在 Cursor 中描述需求:"创建一个 PHP 8.2 的 RESTful API 项目,使用 Slim Framework 4,集成 Eloquent ORM,支持 JWT 鉴权,目录结构遵循 MVC 分层。"

AI 生成 composer.json 和基础目录结构:

{
  "require": {
    "php": "^8.2",
    "slim/slim": "^4.12",
    "slim/psr7": "^1.6",
    "illuminate/database": "^10.0",
    "firebase/php-jwt": "^6.10",
    "vlucas/phpdotenv": "^5.6",
    "monolog/monolog": "^3.5"
  },
  "require-dev": {
    "phpunit/phpunit": "^10.5"
  },
  "autoload": {
    "psr-4": { "App\\\\": "src/" }
  }
}

2. AI 生成数据模型与数据库迁移

告诉 AI:"帮我创建文章(posts)和用户(users)的 Eloquent 模型,以及对应的数据库迁移文件。" 生成结果:

<?php
// src/Models/Post.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';
    protected $fillable = ['title', 'content', 'summary', 'author_id', 'status'];
    protected $casts = [
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }

    // AI 生成:自动生成摘要
    public function generateSummary(): string
    {
        return mb_substr(strip_tags($this->content), 0, 200) . '...';
    }
}

// database/migrations/create_posts_table.php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title', 255);
    $table->longText('content');
    $table->string('summary', 500)->nullable();
    $table->foreignId('author_id')->constrained('users')->onDelete('cascade');
    $table->enum('status', ['draft', 'published', 'archived'])->default('draft');
    $table->timestamps();
    $table->index(['author_id', 'status']);
});

3. AI 生成带 JWT 鉴权的控制器

提示词:"写一个 PostController,实现 CRUD,POST/PUT/DELETE 需要 JWT 验证,GET 公开访问,统一 JSON 响应格式,包含输入验证。" AI 生成:

<?php
// src/Controllers/PostController.php
namespace App\Controllers;

use App\Models\Post;
use App\Services\AuthService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class PostController
{
    public function __construct(private AuthService $auth) {}

    // 获取文章列表(公开)
    public function index(Request $request, Response $response): Response
    {
        $params = $request->getQueryParams();
        $page  = (int)($params['page'] ?? 1);
        $limit = min((int)($params['limit'] ?? 10), 50);

        $posts = Post::with('author:id,name')
            ->where('status', 'published')
            ->orderBy('created_at', 'desc')
            ->paginate($limit, ['*'], 'page', $page);

        return $this->json($response, [
            'code' => 200,
            'data' => $posts->items(),
            'meta' => [
                'total'    => $posts->total(),
                'page'     => $page,
                'per_page' => $limit,
            ]
        ]);
    }

    // 创建文章(需要 JWT)
    public function store(Request $request, Response $response): Response
    {
        $user = $this->auth->requireAuth($request); // 抛出 401 如未认证

        $body = $request->getParsedBody();

        // 输入验证
        $errors = $this->validate($body, [
            'title'   => 'required|max:255',
            'content' => 'required',
        ]);
        if ($errors) {
            return $this->json($response, ['code' => 422, 'errors' => $errors], 422);
        }

        $post = Post::create([
            'title'     => htmlspecialchars($body['title'], ENT_QUOTES, 'UTF-8'),
            'content'   => $body['content'],
            'summary'   => $body['summary'] ?? null,
            'author_id' => $user->id,
            'status'    => $body['status'] ?? 'draft',
        ]);

        return $this->json($response, ['code' => 201, 'data' => $post], 201);
    }

    // 更新文章(需要 JWT + 作者权限)
    public function update(Request $request, Response $response, array $args): Response
    {
        $user = $this->auth->requireAuth($request);
        $post = Post::findOrFail($args['id']);

        if ($post->author_id !== $user->id) {
            return $this->json($response, ['code' => 403, 'message' => 'Forbidden'], 403);
        }

        $post->update($request->getParsedBody());
        return $this->json($response, ['code' => 200, 'data' => $post->fresh()]);
    }

    private function json(Response $response, array $data, int $status = 200): Response
    {
        $response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE));
        return $response->withHeader('Content-Type', 'application/json')->withStatus($status);
    }

    private function validate(array $data, array $rules): array
    {
        $errors = [];
        foreach ($rules as $field => $rule) {
            foreach (explode('|', $rule) as $r) {
                if ($r === 'required' && empty($data[$field])) {
                    $errors[$field][] = "{$field} 不能为空";
                }
                if (str_starts_with($r, 'max:')) {
                    $max = (int)substr($r, 4);
                    if (mb_strlen($data[$field] ?? '') > $max) {
                        $errors[$field][] = "{$field} 不能超过 {$max} 个字符";
                    }
                }
            }
        }
        return $errors;
    }
}

4. AI 集成 OpenAI:给内容自动生成摘要

提示词:"写一个 AIService,调用 OpenAI API 给文章自动生成摘要,限制在 150 字以内,支持异常降级。"

<?php
// src/Services/AIService.php
namespace App\Services;

class AIService
{
    private string $apiKey;
    private string $baseUrl = 'https://api.openai.com/v1/chat/completions';

    public function __construct()
    {
        $this->apiKey = $_ENV['OPENAI_API_KEY'] ?? throw new \RuntimeException('Missing OPENAI_API_KEY');
    }

    public function generateSummary(string $content, int $maxChars = 150): string
    {
        $prompt = "请用不超过{$maxChars}字为以下文章生成摘要,只输出摘要内容:\n\n"
                . mb_substr(strip_tags($content), 0, 2000);

        try {
            $payload = json_encode([
                'model'       => 'gpt-4o-mini',
                'messages'    => [['role' => 'user', 'content' => $prompt]],
                'max_tokens'  => 300,
                'temperature' => 0.3,
            ]);

            $ch = curl_init($this->baseUrl);
            curl_setopt_array($ch, [
                CURLOPT_POST           => true,
                CURLOPT_POSTFIELDS     => $payload,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT        => 30,
                CURLOPT_HTTPHEADER     => [
                    'Content-Type: application/json',
                    "Authorization: Bearer {$this->apiKey}",
                ],
            ]);

            $result = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            if ($httpCode !== 200) {
                throw new \RuntimeException("OpenAI API 返回 {$httpCode}");
            }

            $data = json_decode($result, true);
            return $data['choices'][0]['message']['content'] ?? '';

        } catch (\Throwable $e) {
            // 降级:截取原文前 N 字作为摘要
            error_log("AI 摘要生成失败: " . $e->getMessage());
            return mb_substr(strip_tags($content), 0, $maxChars) . '...';
        }
    }
}

5. AI 生成单元测试

让 AI "为 PostController::store 写 PHPUnit 测试,覆盖:正常创建、缺少 title 的 422 错误、未登录的 401 错误。"

<?php
// tests/PostControllerTest.php
class PostControllerTest extends TestCase
{
    private App $app;

    protected function setUp(): void
    {
        $this->app = AppFactory::create();
        // 使用内存 SQLite,隔离测试数据
        config()->set('database.default', 'sqlite');
        config()->set('database.connections.sqlite.database', ':memory:');
        $this->runMigrations();
    }

    public function test_create_post_success(): void
    {
        $token = $this->getValidJwtToken(userId: 1);
        $request = $this->createRequest('POST', '/api/posts')
            ->withHeader('Authorization', "Bearer {$token}")
            ->withParsedBody(['title' => '测试文章', 'content' => '内容正文...']);

        $response = $this->app->handle($request);

        $this->assertEquals(201, $response->getStatusCode());
        $body = json_decode((string)$response->getBody(), true);
        $this->assertEquals('测试文章', $body['data']['title']);
    }

    public function test_create_post_missing_title_returns_422(): void
    {
        $token = $this->getValidJwtToken(userId: 1);
        $request = $this->createRequest('POST', '/api/posts')
            ->withHeader('Authorization', "Bearer {$token}")
            ->withParsedBody(['content' => '内容正文']);

        $response = $this->app->handle($request);
        $this->assertEquals(422, $response->getStatusCode());
    }

    public function test_create_post_without_auth_returns_401(): void
    {
        $request = $this->createRequest('POST', '/api/posts')
            ->withParsedBody(['title' => '标题', 'content' => '内容']);

        $response = $this->app->handle($request);
        $this->assertEquals(401, $response->getStatusCode());
    }
}

6. AI 辅助开发经验总结

  • 生成路由配置:告诉 AI 你的接口列表,让它一次性生成 Slim 路由文件,比手写快 5 倍

  • SQL 优化:把慢查询日志贴给 AI,它能分析瓶颈并建议加索引或改写 ORM 查询

  • 代码审查:让 AI 检查 SQL 注入、XSS、SSRF 等常见安全漏洞,作为人工审查的补充

  • 文档生成:把 Controller 代码贴给 AI,自动生成 OpenAPI 3.0 格式的接口文档

  • 重构建议:对于老项目,让 AI 分析代码结构,给出从面向过程到 OOP 的重构路径

AI 工具最大的价值在于消灭"搬砖"代码——那些结构固定、逻辑简单但量大的代码(CRUD、验证、序列化)。把这些交给 AI,工程师可以把精力集中在系统设计、性能优化和业务建模上,这才是真正的效率革命。

发布评论

热门评论区: