Bernardo Borges
Kishido Dev Blog

Kishido Dev Blog

Build a Bookshelf with Symfony

Build a Bookshelf with Symfony

Subscribe to my newsletter and never miss my upcoming articles

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

image.png

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',
        ]);
    }
}

?>

image.png

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

image.png

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 %}

image.png

image.png

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.

image.png

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 !

 
Share this