API开发 #
一、API概述 #
1.1 RESTful API #
text
┌─────────────────────────────────────────────────────┐
│ RESTful API设计 │
├─────────────────────────────────────────────────────┤
│ │
│ GET /api/users 获取用户列表 │
│ POST /api/users 创建用户 │
│ GET /api/users/{id} 获取单个用户 │
│ PUT /api/users/{id} 更新用户 │
│ DELETE /api/users/{id} 删除用户 │
│ │
└─────────────────────────────────────────────────────┘
1.2 安装API组件 #
bash
# 安装序列化组件
composer require serializer
# 安装API Platform(可选)
composer require api
二、API控制器 #
2.1 基本API控制器 #
php
<?php
namespace App\Controller\Api;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
#[Route('/api')]
class UserApiController extends AbstractController
{
public function __construct(
private SerializerInterface $serializer
) {}
#[Route('/users', name: 'api_users_list', methods: ['GET'])]
public function list(UserRepository $userRepository): JsonResponse
{
$users = $userRepository->findAll();
$json = $this->serializer->serialize($users, 'json', [
'groups' => 'user:read',
]);
return JsonResponse::fromJsonString($json);
}
#[Route('/users/{id}', name: 'api_users_show', methods: ['GET'])]
public function show(User $user): JsonResponse
{
$json = $this->serializer->serialize($user, 'json', [
'groups' => 'user:read',
]);
return JsonResponse::fromJsonString($json);
}
#[Route('/users', name: 'api_users_create', methods: ['POST'])]
public function create(Request $request, EntityManagerInterface $entityManager): JsonResponse
{
$user = $this->serializer->deserialize(
$request->getContent(),
User::class,
'json',
['groups' => 'user:write']
);
$entityManager->persist($user);
$entityManager->flush();
$json = $this->serializer->serialize($user, 'json', [
'groups' => 'user:read',
]);
return JsonResponse::fromJsonString($json, Response::HTTP_CREATED);
}
#[Route('/users/{id}', name: 'api_users_update', methods: ['PUT', 'PATCH'])]
public function update(
Request $request,
User $user,
EntityManagerInterface $entityManager
): JsonResponse {
$this->serializer->deserialize(
$request->getContent(),
User::class,
'json',
[
'groups' => 'user:write',
'object_to_populate' => $user,
]
);
$entityManager->flush();
$json = $this->serializer->serialize($user, 'json', [
'groups' => 'user:read',
]);
return JsonResponse::fromJsonString($json);
}
#[Route('/users/{id}', name: 'api_users_delete', methods: ['DELETE'])]
public function delete(User $user, EntityManagerInterface $entityManager): JsonResponse
{
$entityManager->remove($user);
$entityManager->flush();
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
}
}
三、序列化 #
3.1 实体序列化配置 #
php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
#[ORM\Entity]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['user:read'])]
private ?int $id = null;
#[ORM\Column(length: 100)]
#[Groups(['user:read', 'user:write'])]
private ?string $name = null;
#[ORM\Column(length: 180, unique: true)]
#[Groups(['user:read', 'user:write'])]
private ?string $email = null;
#[ORM\Column]
#[Groups(['user:write'])]
private ?string $password = null;
#[ORM\Column]
#[Groups(['user:read'])]
private ?\DateTimeImmutable $createdAt = null;
#[SerializedName('fullName')]
#[Groups(['user:read'])]
public function getFullName(): string
{
return $this->name;
}
}
3.2 序列化上下文 #
php
<?php
$json = $this->serializer->serialize($user, 'json', [
'groups' => ['user:read'],
'datetime_format' => 'Y-m-d H:i:s',
'enable_max_depth' => true,
'circular_reference_handler' => function ($object) {
return $object->getId();
},
]);
四、验证 #
4.1 API验证 #
php
<?php
namespace App\Controller\Api;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class UserApiController extends AbstractController
{
public function create(
Request $request,
EntityManagerInterface $entityManager,
ValidatorInterface $validator
): JsonResponse {
$user = $this->serializer->deserialize(
$request->getContent(),
User::class,
'json'
);
$errors = $validator->validate($user);
if (count($errors) > 0) {
$errorMessages = [];
foreach ($errors as $error) {
$errorMessages[$error->getPropertyPath()] = $error->getMessage();
}
return $this->json([
'errors' => $errorMessages,
], Response::HTTP_BAD_REQUEST);
}
$entityManager->persist($user);
$entityManager->flush();
return $this->json($user, Response::HTTP_CREATED, [], [
'groups' => 'user:read',
]);
}
}
五、分页 #
5.1 分页实现 #
php
<?php
namespace App\Controller\Api;
use App\Repository\UserRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class UserApiController extends AbstractController
{
#[Route('/users', name: 'api_users_list', methods: ['GET'])]
public function list(Request $request, UserRepository $userRepository): JsonResponse
{
$page = $request->query->getInt('page', 1);
$limit = $request->query->getInt('limit', 10);
$offset = ($page - 1) * $limit;
$users = $userRepository->findBy([], ['createdAt' => 'DESC'], $limit, $offset);
$total = $userRepository->count([]);
return $this->json([
'data' => $users,
'meta' => [
'total' => $total,
'page' => $page,
'limit' => $limit,
'pages' => ceil($total / $limit),
],
], Response::HTTP_OK, [], ['groups' => 'user:read']);
}
}
六、错误处理 #
6.1 异常处理 #
php
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
class ApiExceptionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
public function onKernelException(ExceptionEvent $event): void
{
$request = $event->getRequest();
if (!str_starts_with($request->getPathInfo(), '/api/')) {
return;
}
$exception = $event->getThrowable();
$statusCode = $exception instanceof HttpException
? $exception->getStatusCode()
: Response::HTTP_INTERNAL_SERVER_ERROR;
$data = [
'error' => [
'code' => $statusCode,
'message' => $exception->getMessage(),
],
];
if ($statusCode === Response::HTTP_NOT_FOUND) {
$data['error']['message'] = 'Resource not found';
}
$event->setResponse(new JsonResponse($data, $statusCode));
}
}
七、API认证 #
7.1 API Token认证 #
php
<?php
namespace App\Security;
use App\Repository\UserRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class ApiTokenAuthenticator extends AbstractAuthenticator
{
public function __construct(
private UserRepository $userRepository
) {}
public function supports(Request $request): ?bool
{
return $request->headers->has('X-API-Token');
}
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-API-Token');
return new SelfValidatingPassport(
new UserBadge($apiToken, function (string $apiToken) {
return $this->userRepository->findOneBy(['apiToken' => $apiToken]);
})
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
return new JsonResponse([
'error' => 'Authentication failed',
], Response::HTTP_UNAUTHORIZED);
}
}
八、CORS配置 #
8.1 NelmioCorsBundle #
bash
composer require nelmio/cors-bundle
yaml
# config/packages/nelmio_cors.yaml
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/api/':
allow_origin: ['*']
allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
allow_headers: ['Content-Type', 'Authorization']
max_age: 3600
九、API文档 #
9.1 Swagger/OpenAPI #
bash
composer require nelmio/api-doc-bundle
composer require twig
yaml
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns:
- ^/api
documentation:
info:
title: My API
description: API文档
version: 1.0.0
components:
securitySchemes:
Bearer:
type: http
scheme: bearer
bearerFormat: JWT
security:
- Bearer: []
9.2 添加文档注解 #
php
<?php
use OpenApi\Attributes as OA;
#[OA\Tag(name: 'Users')]
class UserApiController extends AbstractController
{
#[OA\Get(
path: '/api/users',
summary: '获取用户列表',
tags: ['Users'],
responses: [
new OA\Response(
response: 200,
description: '成功',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: '#/components/schemas/User')
)
)
]
)]
#[Route('/users', name: 'api_users_list', methods: ['GET'])]
public function list(): JsonResponse
{
}
}
十、总结 #
本章学习了:
- RESTful API设计
- API控制器创建
- 序列化配置
- API验证
- 分页实现
- 错误处理
- API认证
- CORS配置
- API文档生成
下一章将学习 博客系统实战。
最后更新:2026-03-28