Welcome back! In our previous lessons, we set up the foundation of our MVC ToDo App in Symfony. We've learned how to manage our todos with a TodoService
and added new items to our list. Now, it's time to enhance our application by allowing users to view and update individual ToDo items.
Being able to view and edit ToDo items is crucial for any todo list application, as it enables users to modify existing tasks as their needs change. By the end of this lesson, you will be able to view details of a ToDo item and update it through a dedicated details page.
First, let's implement the logic in our TodoService
to handle fetching individual ToDo items.
php1<?php 2 3namespace App\Service; 4 5use App\Entity\Todo; 6use Symfony\Component\HttpFoundation\RequestStack; 7 8class TodoService 9{ 10 // Existing code... 11 12 // Find a ToDo item by its ID 13 public function findOne($id): ?Todo 14 { 15 $todos = $this->findAll(); 16 // Iterating through the list of ToDo items 17 foreach ($todos as $todo) { 18 // Check if the current item matches the requested ID 19 if ($todo->getId() == $id) { 20 return $todo; 21 } 22 } 23 return null; 24 } 25}
The findOne
method iterates through the list of ToDo items to locate an item by its ID and return it. If there's no match, the method returns null
.
The method returns a nullable type (?Todo
) because it returns a Todo
object when a ToDo item is found, and null
if no matching item is found.
Next, let's implement the logic in our TodoService
to handle updating ToDo items.
php1<?php 2 3namespace App\Service; 4 5use App\Entity\Todo; 6use Symfony\Component\HttpFoundation\RequestStack; 7 8class TodoService 9{ 10 // Existing code... 11 12 // Update a ToDo item by its ID 13 public function update($id, $title, $description = null): ?Todo 14 { 15 $todos = $this->findAll(); 16 // Iterating through the list of ToDo items 17 foreach ($todos as $key => $todo) { 18 // Check if the current item matches the requested ID 19 if ($todo->getId() == $id) { 20 // Update the item with new title and description 21 $todos[$key] = new Todo($id, $title, $description); 22 // Save the updated list back to the session 23 $this->getSession()->set('todos', $todos); 24 return $todos[$key]; 25 } 26 } 27 return null; 28 } 29}
The update
method iterates through the list of ToDo items to find an item with the matching ID. Unlike findOne
, which returns a single item, we use findAll
because we need access to the entire list to update the relevant item and then save the updated list back to the session.
Upon finding a match, it updates the title and description of the ToDo item, saves the updated list to the session, and returns the updated item. If no item with the specified ID is found, the method returns null
.
Now, let's define the route and corresponding controller action to handle displaying individual ToDo items.
Here's the TodoController
with the detail
method:
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 // Existing code... 13 14 #[Route('/todos/{id}', name: 'todo_detail')] 15 public function detail($id): Response 16 { 17 // Fetch the ToDo item using its ID 18 $todo = $this->todoService->findOne($id); 19 // Render the details page with the fetched ToDo item 20 return $this->render('todo/detail.html.twig', ['todo' => $todo]); 21 } 22}
The detail
method within the TodoController
handles the fetching and displaying of details for a specific ToDo item. It uses the findOne
method from the TodoService
to fetch the item by its ID and then renders the detail.html.twig
template, passing the fetched item as a parameter.
Next, we'll define the route and corresponding controller action to handle updating ToDo items.
Here's the TodoController
with the update
method:
php1<?php 2 3namespace App\Controller; 4 5use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 6use Symfony\Component\HttpFoundation\Response; 7use Symfony\Component\HttpFoundation\Request; 8use Symfony\Component\Routing\Annotation\Route; 9use App\Service\TodoService; 10 11class TodoController extends AbstractController 12{ 13 // Existing code... 14 15 #[Route('/todos/{id}/update', name: 'todo_update', methods: ['POST'])] 16 public function update($id, Request $request): Response 17 { 18 // Get the updated title and description from the request 19 $title = $request->request->get('title'); 20 $description = $request->request->get('description'); 21 // Update the ToDo item using the TodoService 22 $this->todoService->update($id, $title, $description); 23 // Redirect to the list view after updating the item 24 return $this->redirectToRoute('todo_list'); 25 } 26}
The update
method handles the updating process for a specific ToDo item. It retrieves the updated title and description from the request, calls the update
method from the TodoService
to make the changes, and then redirects the user back to the list view via the todo_list
route.
We use the POST method for this route because forms natively support only GET and POST methods. Although PUT is also used for updates in the HTTP protocol, HTML forms do not support it directly. Thus, we can use POST to handle the update process securely and correctly.
Finally, let's create the Twig template that will render the details of a specific ToDo item. Here is the detail.html.twig
template:
HTML, XML1<!DOCTYPE html> 2<html> 3<head> 4 <title>ToDo Details</title> 5</head> 6<body> 7 <h1>Todo Details</h1> 8 9 <!-- Form for updating the ToDo item --> 10 <form action="{{ path('todo_update', {'id': todo.id}) }}" method="post"> 11 <div> 12 <label for="title">Title:</label> 13 <!-- Input for updating the title --> 14 <input type="text" id="title" name="title" value="{{ todo.getTitle() }}" required> 15 </div> 16 <div> 17 <label for="description">Description:</label> 18 <!-- Input for updating the description --> 19 <input type="text" id="description" name="description" value="{{ todo.getDescription() }}"> 20 </div> 21 <div> 22 <!-- Button to submit the form and update the ToDo item --> 23 <button type="submit">Update To Do</button> 24 </div> 25 </form> 26 27 <!-- Link to go back to the list view --> 28 <p><a href="{{ path('todo_list') }}">Go back to the list</a></p> 29</body> 30</html>
In this template, we have a form that allows the user to edit the title and description of the ToDo item. The form's action attribute points to the todo_update
route while passing the ToDo item's ID as a parameter. The fields are pre-filled with the current title and description values of the ToDo item. When the form is submitted via the "Update To Do" button, the data will be sent using the POST method to the update action in the controller.
To make it easy for users to navigate to the details page of any ToDo item, we need to update our list view. We will modify the list.html.twig
template so that each ToDo item redirects to the details page when clicked.
Here is the updated list.html.twig
template:
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 <li> 11 <!-- Link to the details page for each ToDo item --> 12 <a href="{{ path('todo_detail', {'id': todo.id}) }}"> 13 {{ todo.getTitle() }} - {{ todo.getDescription() }} 14 </a> 15 </li> 16 {% else %} 17 <li>No todos found</li> 18 {% endfor %} 19 </ul> 20 21 <form action="{{ path('todo_create') }}" method="post"> 22 <input type="text" name="title" placeholder="Title" required> 23 <input type="text" name="description" placeholder="Description"> 24 <button type="submit">Add To Do</button> 25 </form> 26</body> 27</html>
In this updated template, we loop through all ToDo items and create a list item (<li>
) for each one. Inside each list item, there's a link (<a>
) that points to the todo_detail
route, passing the respective ToDo item's ID as a parameter. This allows users to click on any ToDo item in the list and be redirected to the details page of that specific item. Additionally, the form at the bottom allows users to add new ToDo items.
Let's go through a complete example, from selecting an item from the list page to updating a ToDo item.
- Navigate to details page: Click on a ToDo item link on the list page to access its details.
- Edit form fields: Modify the title and description.
- Submit form: Click "Update To Do" to send data via POST.
- Controller processes update:
update
method inTodoController
processes form data. - Service updates item:
TodoService
updates the ToDo item in the session. - View updated list: Redirected list view shows the updated item.
In this lesson, we enhanced our ToDo app by adding the functionality to view and update ToDo items. We created the details page by
- Setting up the route and controller action to handle displaying the details of a ToDo item
- Implementing the update logic in the service layer
- Building the details page view using Twig.
Finally, we walked through the entire process from accessing the details page to updating a ToDo item. Now it's your turn to practice what you've learned. Head over to the practice exercises and try updating ToDo items in your list. Remember, the more you practice, the better you'll become. Great job!