Handling Subresources¶
In a real world blog application, a Post
can be commented, it is a great path that the author can interact with it readers.
When designing the APIs such as adding a comment under a specific post, there are a lot of debates. For example, POST /comments and POST /posts/{id}/comments which one is more reasonable. Many architects/developers only remember to perform the CRUD operations via HTTP protocol, but do not think through the relations between entities.
Adding Comment to Post¶
A comment has to be added under a post, so I would like to design it like this.
- Request matches Http verbs/HTTP Method:
POST
- Request matches route endpoint: /posts/{id}/comments
- If successful, return a
CREATED
(201) Http Status code, and set the response header Location value to the URI of the new created comments.
Add a function to the existing PostController
.
#[Route(path: "/{id}/comments", name: "addComments", methods: ["POST"])]
public function addComment(Uuid $id, Request $request): Response
{
$data = $this->posts->findOneBy(["id" => $id]);
if ($data) {
$dto = $this->serializer->deserialize($request->getContent(), CreateCommentDto::class, 'json');
$entity = Comment::of($dto->getContent());
$this->objectManager->persist($entity->setPost($data));
$this->objectManager->flush();
return $this->json([], 201, ["Location" => "/comments/" . $entity->getId()]);
} else {
throw new PostNotFoundException($id);
//return $this->json(["error" => "Post was not found b}y id:" . $id], 404);
}
}
Retrieving Comments¶
Similar to the above adding comment, when retrieving comments of a specific post, we design the following API.
- Request matches Http verbs/HTTP Method:
GET
- Request matches route endpoint: /posts/{id}/comments
- Request header
Accept
matchesapplication/json
- If successful, return a
OK
(200) Http Status code, and a collection of data in the response body.
#[Route(path: "/{id}/comments", name: "commentByPostId", methods: ["GET"])]
public function getComments(Uuid $id): Response
{
$data = $this->posts->findOneBy(["id" => $id]);
if ($data) {
return $this->json($data->getComments());
} else {
throw new PostNotFoundException($id);
//return $this->json(["error" => "Post was not found b}y id:" . $id], 404);
}
}
Retrieving Single Comment¶
To retrieve a single comment in some cases, such as for updating purpose, we move the retrieving single comment to the /comments
endpoints.
//src/Controller/CommentController.php
#[Route('comments', name: 'comments')]
class CommentController extends AbstractController
{
public function __construct(private CommentRepository $commentRepository)
{
}
#[Route('{id}', name: "getById")]
public function byId(string $id): Response
{
$data = $this->commentRepository->findOneBy(["id" => $id]);
if ($data) {
$dto = CommentWithPostSummaryDto::of(
$data->getId(),
$data->getContent(),
PostSummaryDto::of($data->getPost()?->getId(), $data->getPost()?->getTitle())
);
return $this->json($dto);
} else {
return $this->json(["error" => "Comment was not found" . $id], 404);
}
}
}