Welcome to the first lesson of another Symfony course. In this lesson, we will focus on constructing the foundation of a functional ToDo application using the MVC pattern.
First, let's briefly recap the MVC (Model-View-Controller) structure. The MVC pattern divides your application into three main components:
- Model: Manages the data and business logic.
- View: Displays data to the user and receives user input.
- Controller: Handles user input and interacts with the model to update the view.
In this lesson, we will introduce the concept of entities, create a simple Todo
entity, set up a service to manage our todos, and create a controller to handle requests. Finally, we will use Twig
to render our list of todos. By the end of this lesson, you will have a functional ToDo list displayed in your browser.
Let’s begin by creating the Todo
entity that will represent our tasks. An entity in Symfony is simply a PHP class that represents a table in your database. For this example, we’ll create a basic Todo
class without involving an actual database.
Here’s the code for our Todo
entity:
php1<?php 2 3namespace App\Entity; 4 5class Todo 6{ 7 // Properties to hold the ID, title, and description of each todo 8 private $id; 9 private $title; 10 private $description; 11 12 public function __construct($id, $title, $description = null) 13 { 14 // Initialize a new Todo object with an ID, title, and an optional description 15 $this->id = $id; 16 $this->title = $title; 17 $this->description = $description; 18 } 19 20 // Getter methods to access properties 21 public function getId() 22 { 23 return $this->id; 24 } 25 26 public function getTitle() 27 { 28 return $this->title; 29 } 30 31 public function getDescription() 32 { 33 return $this->description; 34 } 35}
In this code, our Todo
class belongs to the App\Entity
namespace, grouping it with related classes. It has properties like $id
, $title
, and $description
, and comes with methods to interact with these properties. This class will serve as the backbone of our ToDo app's data structure.
Before we dive into implementing the TodoService
, let's briefly talk about Symfony's session mechanism and the RequestStack
component.
In web applications, sessions are used to store information across different user requests. HTTP, the protocol used for web communication, is stateless by default. This means it doesn't inherently remember information between different requests. Sessions help overcome this limitation by allowing you to store data on the server side and associate it with a specific user through a unique session ID.
RequestStack
is a Symfony service that provides access to the current HTTP request and its session. It allows you to fetch the session and manage session data conveniently. By leveraging RequestStack
, you can store and retrieve data such as user preferences or, in our case, a list of todos.
Now that we have an understanding of the session mechanism and RequestStack
, let's implement the TodoService
.
Here’s the code for the TodoService
class, this service will use Symfony's session mechanism to store and retrieve our todos.
php1<?php 2 3namespace App\Service; 4 5use App\Entity\Todo; 6use Symfony\Component\HttpFoundation\RequestStack; 7 8class TodoService 9{ 10 private $requestStack; 11 12 public function __construct(RequestStack $requestStack) 13 { 14 // Initialize the requestStack property 15 $this->requestStack = $requestStack; 16 } 17 18 private function getSession() 19 { 20 // Fetch the current session from the request stack 21 return $this->requestStack->getSession(); 22 } 23 24 public function findAll(): array 25 { 26 // Retrieve todos from session or return an empty array if not found 27 return $this->getSession()->get('todos', []); 28 } 29}
In our TodoService
class, we use the $requestStack
property to access the current HTTP request and its session. Since HTTP is stateless, sessions are essential for maintaining data, like a list of todos, across different pages and user actions. This allows us to persist information between requests, ensuring that data isn't lost after each interaction.
The __construct
method sets up the $requestStack
property, and the getSession
method fetches the session from the request stack. With these in place, the findAll
method can retrieve all the todos stored in the session or return an empty array if there aren't any. This service manages our tasks and lets us interact with them through our controller.
Now, we need a controller to handle requests related to our todos. Let’s create a TodoController
class with a method to list all todos.
Here’s the code for the TodoController
class:
php1<?php 2 3namespace App\Controller; 4 5use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 6use Symfony\Component\HttpFoundation\Response; 7use Symfony\Component\Routing\Annotation\Route; 8use App\Service\TodoService; 9 10class TodoController extends AbstractController 11{ 12 private $todoService; 13 14 public function __construct(TodoService $todoService) 15 { 16 // Initialize the todoService property 17 $this->todoService = $todoService; 18 } 19 20 #[Route('/', name: 'todo_list')] 21 public function list(): Response 22 { 23 // Retrieve all todos from the service 24 $todos = $this->todoService->findAll(); 25 // Render the list.html.twig template with the todos data 26 return $this->render('todo/list.html.twig', ['todos' => $todos]); 27 } 28}
In our TodoController
class, we have a $todoService
property that gives us access to the service managing our todos. The __construct
method sets this up by initializing $todoService
.
We are using PHP attributes to define routes in our controller. For example, the #[Route('/', name: 'todo_list')]
attribute maps the path '/'
to the root URL of our application and gives it the route name todo_list
. This modern syntax directly embeds the routing information in our controller method, streamlining the process.
To ensure that Symfony properly reads our route attributes, we need to configure the routes.yaml
file to tell Symfony to scan our controllers for these attributes.
Here’s how we should configure our routes.yaml
file:
YAML1controllers: 2 resource: '../src/Controller/' 3 type: annotation
In this configuration:
resource: '../src/Controller/'
directs Symfony to look for route definitions in the specified directory.type: annotation
tells Symfony to use the embedded route definitions within our controller classes. Despite using the termannotation
, this configuration supports modern PHP attributes as well.
With PHP attributes, we now embed this routing information directly in our controller methods. This approach keeps our routing logic close to the related controller logic, making the process more intuitive and reducing redundancy.
Finally, we need a Twig template to display our list of todos. Here’s the code for the list.html.twig
file called by the controller:
HTML, XML1<!DOCTYPE html> 2<html> 3<head> 4 <title>ToDo List</title> 5</head> 6<body> 7 <h1>ToDo List</h1> 8 <ul> 9 {% for todo in todos %} 10 <!-- Display each todo's title and description --> 11 <li>{{ todo.getTitle() }} - {{ todo.getDescription() }}</li> 12 {% else %} 13 <!-- Show message if no todos are found --> 14 <li>No todos found</li> 15 {% endfor %} 16 </ul> 17</body> 18</html>
This basic HTML structure starts with a title and a heading that both say ToDo List. After that, there's a list where our todos will be displayed. The {% for todo in todos %}
loop goes through each todo provided by the controller, showing its title and description. If there are no todos to display, the {% else %}
block shows a message letting us know that no todos were found. This setup ensures that we either see our list of todos or a friendly message if the list is empty.
In this lesson, we built the foundation of our ToDo app by following the MVC pattern in Symfony. Here is what we have covered:
- MVC Structure Recap: We revisited the Model-View-Controller pattern, understanding its core components - Model, View, and Controller.
- Creating the Todo Entity: We created a PHP class (
Todo
) to represent our tasks with properties for id, title, and description. - Implementing TodoService: We built a service (
TodoService
) to manage our tasks using Symfony's session mechanism to store and retrieve todos, ensuring persistence across HTTP requests. - Setting Up TodoController: We set up a controller (
TodoController
) to handle HTTP requests, defining routes with annotations, and used it to interact with theTodoService
and pass data to the view. - Configuring Routes: We configured the
routes.yaml
file to ensure Symfony reads our route annotations, streamlining our routing by scanning the../src/Controller/
directory. - Displaying Todos with Twig: We used a Twig template (
list.html.twig
) to render our list of todos on a web page, displaying each todo's title and description, or showing a friendly message if the list is empty.
Now it's time to put this knowledge into practice. Complete the exercises that follow to solidify your understanding and ensure you can implement these concepts on your own.