Capstone: blog
| The spec | |
| Posts CRUD | |
| Comments | |
| Smoke tests |
The spec
The capstone ties the course together: a tiny blog with posts, comments, and a guarded admin area. Build it in order: routes first, then pages, then forms, then login. Four routes cover the whole project.
GET /posts - list, GET /posts/id - read one
GET /admin/posts - manage, POST /admin/posts - create
Posts CRUD
Posts are standard CRUD: list them, show one, create and edit behind login. One controller with one route per action keeps the shape clear, and the controller extends the base controller for templates and redirects.
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class PostController { #[Route("/posts", methods: ["GET"])] public function list(PostRepository $posts): Response { return $this->render("post/list.html.twig", [ "posts" => $posts->findAll(), ]); } }
Comments
Comments belong to posts through a many-to-one relation from comment to post. Render the comment form under the post and save through the same entity manager, so one flush stores the comment with its link to the post.
use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class Comment { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 500)] private string $body = ""; public function getId(): ?int { return $this->id; } }
Smoke tests
Finish with tests that load every public page and submit one form. They run in seconds and catch broken routes before visitors do, which is exactly the safety net a first project needs.
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class BlogSmokeTest extends WebTestCase { public function testListLoads(): void { $client = static::createClient(); $client->request("GET", "/posts"); $this->assertResponseIsSuccessful(); } }
Next: Capstone: JSON API
Article author: Arthur Isaev