The /books page must become a real starting point.
- Display all session books.
- Provide an Add a book link.
- Provide Edit and Delete for every book.
- Preserve invalid values and display errors.
- Redirect after every successful change.
index() action
public function index(Request $request): Response
{
return $this->view('books/index.php', [
'books' => $this->session->get('books', []),
]);
}List view
src/views/books/index.php
<h1>My books</h1>
<p><a href="/books/create">Add a book</a></p>
<?php if ($books === []): ?>
<p>No books yet.</p>
<?php else: ?>
<ul>
<?php foreach ($books as $book): ?>
<li>
<strong><?= $view->escape($book['title']) ?></strong>
— <?= $view->escape($book['author']) ?>
<a href="/books/<?= (int) $book['id'] ?>/edit">Edit</a>
<form method="post" action="/books/<?= (int) $book['id'] ?>/delete">
<?= $view->csrfField() ?>
<button type="submit">Delete</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>Delete action
public function destroy(Request $request): Response
{
$id = (int) $request->attribute('id');
$books = $this->session->get('books', []);
$books = array_values(array_filter(
$books,
static fn (array $book): bool => $book['id'] !== $id,
));
$this->session->set('books', $books);
return Response::redirect('/books', 303);
}array_filter keeps books with a different identifier. array_values resets array positions into a simple order. The book's business identifier itself does not change.
Verification plan
Create a valid book303 → GET /books
Submit an empty title422 + error
Remove the CSRF token419
Edit a missing identifier404
Delete a bookdisappears from list
aml routes
aml testWhat you actually learned
You did more than create a form. You separated reading and modification, read a request, validated values, protected an action, kept state across requests, chose useful HTTP statuses, and prevented POST repetition.
SUMMARY
What to remember
- The browser helps, but the server always validates.
- csrfField() protects every modifying form.
- The view escapes every dynamic value.
- A 303 redirect cleanly completes a POST.
- Session suits learning, not permanent persistence.