Symfony 5 AJAX CRUD App Easy Step-By-Step Tutorial

Symfony 5 AJAX CRUD App Easy Step-By-Step Tutorial

Avatar photoPosted by

Introduction:

In this blog, I will showing you how to make a Symfony 5 AJAX CRUD app. The four basic operation in any type of programming is CRUD. CRUD is an acronym for CREATE, READ, UPDATE, DELETE. In this blog, You will learn how to develop a Symfony 5 AJAX CRUD App by following an easy step-by-step tutorial. But before that let us have an introduction:

What is Symfony? Symfony is a PHP framework used to develop web application, APIs, microservices and web services. Symfony is one of the leading PHP framework for creating websites and web application.

AJAX(Asynchronous JavaScript and XML) is a set of web development techniques that use many web technologies which allow web applications work in asynchronously.

CRUD is an acronym for CREATE, READ, UPDATE, DELETE. The four basic functions in any type of programming. CRUD typically refers to operations performed in a database.

  • Create -Generate new record/s.
  • Read – Reads or retrieve record/s.
  • Update – Modify record/s.
  • Delete – Destroy or remove record/s.

Prerequisite:

Step 1: Install Symfony 5

First, select a folder that you want Symfony to be installed then execute this command on Terminal or CMD to install:

Install via composer:

composer create-project symfony/website-skeleton symfony-5-crud-ajax

Install via Symfony CLI:

symfony new symfony-5-crud-ajax --full

Step 2: Set Database Configuration

After installing, open the .env file and set database configuration. We will be using MySQL on this tutorial. Uncomment the DATABASE_URL variable for MySQL and updates its configs. Make sure you commented out the other DATABASE_URL variables.

.env

# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
#  * .env                contains default values for the environment variables needed by the app
#  * .env.local          uncommitted file with local overrides
#  * .env.$APP_ENV       committed environment-specific defaults
#  * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=37febf852b869d38be2030babb187e25
###< symfony/framework-bundle ###

###> symfony/mailer ###
# MAILER_DSN=smtp://localhost
###< symfony/mailer ###

###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
###< doctrine/doctrine-bundle ###

After configurating the database, execute this command to create database:

php bin/console doctrine:database:create

Step 3: Create Entity and Migration

Entity– it a class that represents a database table.

Migration – like version control for the database that allows us to modify and share database schema to your team.

Execute this command to create an Entity:

php bin/console make:entity

After executing the command above, it will ask question – follow the steps below:

Class name of the entity to create or update (e.g. BraveElephant):
 > Project
Project

 created: src/Entity/Project.php
 created: src/Repository/ProjectRepository.php
 
 Entity generated! Now let's add some fields!
 You can always add more fields later manually or by re-running this command.
New property name (press <return> to stop adding fields):
 > name

 Field type (enter ? to see all types) [string]:
 > string
string

 Field length [255]:
 > 255

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

 updated: src/Entity/Project.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > description

 Field type (enter ? to see all types) [string]:
 > text
text

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

 updated: src/Entity/Project.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > created_at

 Field type (enter ? to see all types) [datetime_immutable]:
 > datetime
datetime

 Can this field be null in the database (nullable) (yes/no) [no]:
 > yes

 updated: src/Entity/Project.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > updated_at

 Field type (enter ? to see all types) [datetime_immutable]:
 > datetime
datetime

 Can this field be null in the database (nullable) (yes/no) [no]:
 > yes

 updated: src/Entity/Project.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 >


           
  Success! 
           

 Next: When you're ready, create a migration with php bin/console make:migration

Now that we have finished creating an entity, we will then create a migration:

 php bin/console make:migration

This will create a migration file, inside the migration file contains SQL. we will then run the SQL using this command:

php bin/console doctrine:migrations:migrate

Before we proceed on creating the controller, we will install a bundle StofDoctrineExtensionsBundle, we will be using some of its functionality to automatically set value for the created_at and updated_at property of the newly created entity.

composer require stof/doctrine-extensions-bundle

During the installation process it will ask confirmation to execute the recipe, choose yes.

Open this file config/packages/stof_doctrine_extensions.yaml and add these lines:.

config/packages/stof_doctrine_extensions.yaml

# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
stof_doctrine_extensions:
    default_locale: en_US
    orm:
        default:
            timestampable: true 

And then update the Project Entity, use the Timestampable to automatically set value on created_at and updated_at property of Project Entity.

src/Entity/Project.php

<?php

namespace App\Entity;

use App\Repository\ProjectRepository;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ORM\Entity(repositoryClass=ProjectRepository::class)
 */
class Project
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    /**
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $created_at;

    /**
     * @Gedmo\Timestampable(on="update")
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $updated_at;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->created_at;
    }

    public function setCreatedAt(?\DateTimeInterface $created_at): self
    {
        $this->created_at = $created_at;

        return $this;
    }

    public function getUpdatedAt(): ?\DateTimeInterface
    {
        return $this->updated_at;
    }

    public function setUpdatedAt(?\DateTimeInterface $updated_at): self
    {
        $this->updated_at = $updated_at;

        return $this;
    }
}

Step 4: Generate Controller

A Controller is the one responsible for receiving Request and returning Response.

Execute this command to create a controller:

 php bin/console make:controller ProjectController

Then add these line of codes to the newly generated controller:

src/Controller/ProjectController.php

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use App\Entity\Project;

class ProjectController extends AbstractController
{
    /**
     * @Route("/project", name="project")
     */
    public function index(): Response
    {
        return $this->render('project/index.html.twig', [
            'controller_name' => 'ProjectController',
        ]);
    }

    /**
     * @Route("/project/show-all", name="project_show_all", methods={"GET"})
     */
    public function showAll(): Response
    {
        $products = $this->getDoctrine()
            ->getRepository(Project::class)
            ->findAll();

        $data = [];

        foreach ($products as $product) {
           $data[] = [
               'id' => $product->getId(),
               'name' => $product->getName(),
               'description' => $product->getDescription(),
           ];
        }


        return $this->json($data);
    }

    /**
     * @Route("/project/show/{id}", name="project_show", methods={"GET"})
     */
    public function show(int $id): Response
    {
        $project = $this->getDoctrine()
            ->getRepository(Project::class)
            ->find($id);

        if (!$project) {

            return $this->json('No project found for id' . $id, 404);
        }

        $data =  [
            'id' => $project->getId(),
            'name' => $project->getName(),
            'description' => $project->getDescription(),
        ];
        
        return $this->json($data);
    }

    /**
     * @Route("/project/new", name="project_new", methods={"POST"})
     */
    public function new(Request $request): Response
    {
        $entityManager = $this->getDoctrine()->getManager();

        $project = new Project();
        $project->setName($request->request->get('name'));
        $project->setDescription($request->request->get('description'));

        $entityManager->persist($project);
        $entityManager->flush();

        return $this->json('Created new project successfully with id ' . $project->getId());
    }

    /**
     * @Route("/project/edit/{id}", name="project_edit", methods={"PUT"})
     */
    public function edit(Request $request, int $id): Response
    {
        $entityManager = $this->getDoctrine()->getManager();
        $project = $entityManager->getRepository(Project::class)->find($id);

        if (!$project) {
            return $this->json('No project found for id' . $id, 404);
        }

        $project->setName($request->request->get('name'));
        $project->setDescription($request->request->get('description'));
        $entityManager->flush();

        $data =  [
            'id' => $project->getId(),
            'name' => $project->getName(),
            'description' => $project->getDescription(),
        ];
        
        return $this->json($data);
    }

    /**
     * @Route("/project/delete/{id}", name="project_delete", methods={"DELETE"})
     */
    public function delete(int $id): Response
    {
        $entityManager = $this->getDoctrine()->getManager();
        $project = $entityManager->getRepository(Project::class)->find($id);

        if (!$project) {
            return $this->json('No project found for id' . $id, 404);
        }

        $entityManager->remove($project);
        $entityManager->flush();

        return $this->json('Deleted a project successfully with id ' . $id);
    }


}

Step 5: Update the Twig Templates

Twig is the templating language used in Symfony that make you create concise and readable templates, and it is more powerful on several ways than PHP templates.

We will be using Bootstrap 5 on adding styles on our template.

Open templates/base.html.twig and add the css and js of Bootstrap:

templates/base.html.twig

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
        {% block stylesheets %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

And then let’s update the file templates/project/index.html.twig – this file is auto generated when we generate the Controller. Now lets add these lines of codes:

templates/project/index.html.twig

{% extends 'base.html.twig' %}

{% block title %}Symfony 5 Project Manager{% endblock %}

{% block stylesheets %}
    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.25/css/dataTables.bootstrap5.min.css">
{% endblock %}

{% block javascripts %}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
{% endblock %}

{% block body %}
<div class="container">
        <h2 class="text-center mt-5 mb-3">Symfony 5 Project Manager</h2>
        <div class="card">
            <div class="card-header">
                <button class="btn btn-outline-primary" onclick="createProject()"> 
                    Create New Project
                </button>
            </div>
            <div class="card-body">
                <div id="alert-div">
                  
                </div>
                <table class="table table-bordered">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Description</th>
                            <th width="240px">Action</th>
                        </tr>
                    </thead>
                    <tbody id="projects-table-body">
                          
                    </tbody>
                      
                </table>
            </div>
        </div>
    </div>
   
    <!-- modal for creating and editing function -->
    <div class="modal" tabindex="-1"  id="form-modal">
        <div class="modal-dialog" >
            <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Project Form</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <div id="error-div"></div>
                <form>
                    <input type="hidden" name="update_id" id="update_id">
                    <div class="form-group">
                        <label for="name">Name</label>
                        <input type="text" class="form-control" id="name" name="name">
                    </div>
                    <div class="form-group">
                        <label for="description">Description</label>
                        <textarea class="form-control" id="description" rows="3" name="description"></textarea>
                    </div>
                  
                    <button type="submit" class="btn btn-outline-primary mt-3" id="save-project-btn">Save Project</button>
                </form>
            </div>
            </div>
        </div>
    </div>
  
   
    <!-- view record modal -->
    <div class="modal" tabindex="-1" id="view-modal">
        <div class="modal-dialog" >
            <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Project Information</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <b>Name:</b>
                <p id="name-info"></p>
                <b>Description:</b>
                <p id="description-info"></p>
            </div>
            </div>
        </div>
    </div>
   
    <script type="text/javascript">
   
        showAllProjects();
      
        /*
            This function will get all the project records
        */
        function showAllProjects()
        {

            $.ajax({
                url: "/project/show-all",
                method: "GET",
                success: function(response) {
                    $("#projects-table-body").html("");
                    let projects = response;
                    for (var i = 0; i < projects.length; i++) 
                    {
                        let showBtn =  '<button ' +
                            ' class="btn btn-outline-info" ' +
                            ' onclick="showProject(' + projects[i].id + ')">Show' +
                        '</button> ';
                        let editBtn =  '<button ' +
                            ' class="btn btn-outline-success" ' +
                            ' onclick="editProject(' + projects[i].id + ')">Edit' +
                        '</button> ';
                        let deleteBtn =  '<button ' +
                            ' class="btn btn-outline-danger" ' +
                            ' onclick="destroyProject(' + projects[i].id + ')">Delete' +
                        '</button>';
      
                        let projectRow = '<tr>' +
                            '<td>' + projects[i].name + '</td>' +
                            '<td>' + projects[i].description + '</td>' +
                            '<td>' + showBtn + editBtn + deleteBtn + '</td>' +
                        '</tr>';
                        $("#projects-table-body").append(projectRow);
                    }
      
                      
                },
                error: function(response) {
                    console.log(response.responseJSON)
                }
            });
        }
      
        /*
            check if form submitted is for creating or updating
        */
        $("#save-project-btn").click(function(event ){
            event.preventDefault();
            if($("#update_id").val() == null || $("#update_id").val() == "")
            {
                storeProject();
            } else {
                updateProject();
            }
        })
      
        /*
            show modal for creating a record and 
            empty the values of form and remove existing alerts
        */
        function createProject()
        {
            $("#alert-div").html("");
            $("#error-div").html("");   
            $("#update_id").val("");
            $("#name").val("");
            $("#description").val("");
            $("#form-modal").modal('show'); 
        }
      
        /*
            submit the form and will be stored to the database
        */
        function storeProject()
        {   
            $("#save-project-btn").prop('disabled', true);
            let data = {
                name: $("#name").val(),
                description: $("#description").val(),
            };
            $.ajax({
                url: "/project/new",
                method: "POST",
                data: data,
                success: function(response) {
                    $("#save-project-btn").prop('disabled', false);
                    let successHtml = '<div class="alert alert-success" role="alert"><b>Project Created Successfully</b></div>';
                    $("#alert-div").html(successHtml);
                    $("#name").val("");
                    $("#description").val("");
                    showAllProjects();
                    $("#form-modal").modal('hide');
                },
                error: function(response) {
                    /*
                    show validation error
                    */
                    console.log(response)
                    $("#save-project-btn").prop('disabled', false);
                    if (typeof response.responseJSON.messages.errors !== 'undefined') 
                    {
                        let errors = response.responseJSON.messages.errors;
                        let descriptionValidation = "";
                        if (typeof errors.description !== 'undefined') 
                        {
                            descriptionValidation = '<li>' + errors.description + '</li>';
                        }
                        let nameValidation = "";
                        if (typeof errors.name !== 'undefined') 
                        {
                            nameValidation = '<li>' + errors.name + '</li>';
                        }
          
                        let errorHtml = '<div class="alert alert-danger" role="alert">' +
                            '<b>Validation Error!</b>' +
                            '<ul>' + nameValidation + descriptionValidation + '</ul>' +
                        '</div>';
                        $("#error-div").html(errorHtml);        
                    }
                }
            });
        }
      
      
        /*
            edit record function
            it will get the existing value and show the project form
        */
        function editProject(id)
        {
            
            $.ajax({
                url: "project/show/" + id,
                method: "GET",
                success: function(response) {
                    let project = response
                    $("#alert-div").html("");
                    $("#error-div").html("");   
                    $("#update_id").val(project.id);
                    $("#name").val(project.name);
                    $("#description").val(project.description);
                    $("#form-modal").modal('show'); 
                },
                error: function(response) {
                    console.log(response.responseJSON)
                }
            });
        }
      
        /*
            sumbit the form and will update a record
        */
        function updateProject()
        {
            $("#save-project-btn").prop('disabled', true);
            let data = {
                name: $("#name").val(),
                description: $("#description").val(),
            };
            $.ajax({
                url: "/project/edit/" + $("#update_id").val(),
                method: "PUT",
                data: data,
                success: function(response) {
                    $("#save-project-btn").prop('disabled', false);
                    let successHtml = '<div class="alert alert-success" role="alert"><b>Project Updated Successfully</b></div>';
                    $("#alert-div").html(successHtml);
                    $("#name").val("");
                    $("#description").val("");
                    showAllProjects();
                    $("#form-modal").modal('hide');
                },
                error: function(response) {
                    /*
                    show validation error
                    */
                    console.log(response)
                    $("#save-project-btn").prop('disabled', false);
                    if (typeof response.responseJSON.messages.errors !== 'undefined') 
                    {
                        let errors = response.responseJSON.messages.errors;
                        let descriptionValidation = "";
                        if (typeof errors.description !== 'undefined') 
                        {
                            descriptionValidation = '<li>' + errors.description + '</li>';
                        }
                        let nameValidation = "";
                        if (typeof errors.name !== 'undefined') 
                        {
                            nameValidation = '<li>' + errors.name + '</li>';
                        }
          
                        let errorHtml = '<div class="alert alert-danger" role="alert">' +
                            '<b>Validation Error!</b>' +
                            '<ul>' + nameValidation + descriptionValidation + '</ul>' +
                        '</div>';
                        $("#error-div").html(errorHtml);        
                    }
                }
            });
        }
      
        /*
            get and display the record info on modal
        */
        function showProject(id)
        {
            $("#name-info").html("");
            $("#description-info").html("");
            $.ajax({
                url: "project/show/" + id,
                method: "GET",
                success: function(response) {
                    let project = response
                    $("#name-info").html(project.name);
                    $("#description-info").html(project.description);
                    $("#view-modal").modal('show'); 
                },
                error: function(response) {
                    console.log(response.responseJSON)
                }
            });
        }
      
        /*
            delete record function
        */
        function destroyProject(id)
        {
            $.ajax({
                url: "/project/delete/" + id,
                method: "DELETE",
                success: function(response) {
                    let successHtml = '<div class="alert alert-success" role="alert"><b>Project Deleted Successfully</b></div>';
                    $("#alert-div").html(successHtml);
                    showAllProjects();
                },
                error: function(response) {
                    console.log(response.responseJSON)
                }
            });
        }
      
    </script>

{% endblock %}

Step 6: Run the Application

After finishing the steps above, you can now run your application by executing the command below:

symfony server:start

After successfully running your app, open this URL in your browser:

http://localhost:8000/project

Screenshots:

Symfony 5 AJAX CRUD App Index Page

symfony 5 crud ajax index image Binaryboxtuts

Symfony 5 AJAX CRUD App Create Modal

symfony 5 crud ajax create image Binaryboxtuts

Symfony 5 AJAX CRUD App Update Modal

symfony 5 crud ajax update image Binaryboxtuts

Symfony 5 AJAX CRUD App Show Modal

symfony 5 crud ajax show image Binaryboxtuts