Today we're going to build a Website to Manage a online bookshelf with Symfony ! Where we can add Books and update your progress.
This is a example of how the basics of Symfony work's.
Setup
Installing Symfony CLI
To create a base project we'll need the Symfony CLI, If you are using Linux ,you can easily run this command to install It.
wget https://get.symfony.com/cli/installer -O - | bash
For other OS , check here
Creating the Base Project
Now you can run the command to build the base for your application.
symfony new your_project_name --full
Now you can go to your project directory and run this command to start your dev server.
symfony server:start
For this tutorial we're going to need to add Routes, Controllers, Entity's, Migrations and Twig templates.
Choose your Database
In your .env file you have a "DATABASE_URL" parameter with the connection to your database. In this project I used sqlite so I had to uncomment the following line and delete the others.
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
Add Controllers
You can use command's to do most of the work of creating a Controller with a twig template.
php bin/console make:controller BookController
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class BookController extends AbstractController
{
public function index(): Response
{
return $this->render('book/index.html.twig', [
'controller_name' => 'BookController',
]);
}
}
?>
Add Entity Class
To be able to save data in your database you're going to need a Entity to better use and parse the data coming from the Database.
php bin/console make:entity
With the Entity create we can now create a Migration.
Create Migration
The Migration is an automatic procedure were It builds a database structure with the Entity's created so far. In this example It will create a table named Book with the columns of the properties we just added in the previous section.
php bin/console make:migration
php bin/console doctrine:migrations:migrate
Add Bootstrap and JQuery
in /src/templates/base.html.twig add the following lines in the head section so we can style the forms a little bit.
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css" />
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js"
integrity="sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF"
crossorigin="anonymous"></script>
Adding a new Book and Listing
Route
In /config/routes.yaml , add the following code
newBook:
path: /book/new
controller: App\Controller\BookController:new
listBook:
path: /book/list
controller: App\Controller\BookController:list
Controller
Add the following code to your BookController in src/Controller/BookController.php
public function new(Request $request): Response
{
$book = new Book();
$book->setName('Book'); // set default form values
$book->setMaxPages(1); // set default form values
$book->setPagesRead(0); // set default form values
$form = $this->createFormBuilder($book)
->add('name', TextType::class) // type of value
->add('max_pages', IntegerType::class)
->add('pages_read',IntegerType::class)
->add('save', SubmitType::class, ['label' => 'Create Book'])
->getForm(); //create a form
$form->handleRequest($request); //if is a form submit
if ($form->isSubmitted() && $form->isValid()) {
$bookData = $form->getData();
$db = $this->getDoctrine()->getManager();
$db->persist($bookData);
$db->flush();
return $this->redirectToRoute('listBook');
}
return $this->render('book/book.html.twig', [
'form' => $form->createView(),
]); //render twig view
}
public function list(): Response
{
$repository = $this->getDoctrine()->getRepository(Book::class);
$books = $repository->findAll(); //Get all rows from database
return $this->render('book/list.html.twig', ['list' => $books]);
}
Template
Now we need to create twig templates in /src/templates/book.
book.html.twig
When rendering the form we could just pass {{ form(form) }} in the template, but It wouldn't be styled so this way you have better control on the styling of your form.
{% extends 'base.html.twig' %}
{% block title %}
Book
{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font:
18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding:
2px 6px; }
</style>
<div class="example-wrapper">
{{ form_start(form) }}
{{ form_errors(form) }}
<div class="form-group">
{{ form_label(form.name) }}
{{ form_widget(form.name, {'attr': {'class': 'form-control'}}) }}
</div>
<div class="form-group">
{{ form_label(form.max_pages) }}
{{ form_widget(form.max_pages, {'attr': {'class': 'form-control'}}) }}
</div>
<div class="form-group">
{{ form_label(form.pages_read) }}
{{ form_widget(form.pages_read, {'attr': {'class': 'form-control'}}) }}
</div>
{{ form_row(form.save ,{'attr': {'class': 'btn btn-primary'}}) }}
{{ form_end(form) }}
</div>
{% endblock %}
list.html.twig
Here is were we are going to render the list of books added to the database.
{% extends 'base.html.twig' %}
{% block title %}
Book List
{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font:
18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding:
2px 6px; }
</style>
<div class="example-wrapper">
<a href="{{path('newBook')}}">
<button type="button" class="btn btn-success">Add <i class="bi bi-patch-plus-fill"></i></button>
</a>
<table class="table">
<thead>
<tr>
<th scope="col">
#
</th>
<th scope="col">
Name
</th>
<th scope="col">
Pages Read
</th>
<th scope="col">
Nº Pages
</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for book in list %}
<tr>
<th>
{{ book.id }}
</th>
<th>
{{ book.name }}
</th>
<th>
{{ book.pagesRead }}
</th>
<th>
{{ book.maxPages }}
</th>
<th>
<a href="{{path('editBook',{id:book.id})}}">
<i class="bi bi-pencil-square"></i>
</a>
</th>
<th>
<a href="{{path('deleteBook',{id:book.id})}}">
<i class="bi bi-trash"></i>
</a>
</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
With this code we can create and list books in the routes, 127.0.0.1:8000/book/list and 127.0.0.1:8000/book/new
Edit Book And Delete
Route
Let's again start by adding the route in /config/routes.yaml for the edit controller Notice that in this case we pass {id} after the url, this is because we'll pass the book id as a parameter to get the info of the book to then put it on the form.
editBook:
path: /book/edit/{id}
controller: App\Controller\BookController:edit
deleteBook:
path: /book/delete/{id}
controller: App\Controller\BookController:delete
Controller
In the controller file let's use something similar to the new() function
public function edit(int $id,Request $request): Response
{
$book = $this->getDoctrine()
->getRepository(Book::class)
->find($id);
$form = $this->createFormBuilder($book)
->add('name', TextType::class)
->add('max_pages', IntegerType::class)
->add('pages_read',IntegerType::class)
->add('save', SubmitType::class, ['label' => 'Edit Book'])
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$bookData = $form->getData();
$db = $this->getDoctrine()->getManager();
$db->persist($bookData);
$db->flush();
return $this->redirectToRoute('listBook');
}
return $this->render('book/book.html.twig', [
'form' => $form->createView(),
]);
}
Here instead of setting the default values on the form, we get the book info from the database by ID that was passed on the url.
public function delete(int $id , Request $request): Response
{
$entityManager = $this->getDoctrine()->getManager();
$book = $entityManager->getRepository(Book::class)->find($id);
$entityManager->remove($book);
$entityManager->flush();
return $this->redirectToRoute('listBook');
}
The same goes for deleting, we use the ID to get the book and then delete It. In both cases the user is redirected to the list controller after.
Views
For editing, we don't need a new view because, we can reuse the new.html.twig for editing, since the form is the same and no additional changes are needed and since the Delete is more of an action you don't need a view.
Concluison
Now you have some basics to get stated with Symfony. Creating Controllers,setting routes, rendering views with forms, use a Database to store data and some useful commands.
Go check the source code or comment below if you have any questions !