This documentation will help to jump start with Symfony. If you are familiar with different framework of php then this documentation is for you, otherwise not. Remember one thing, this is a dummy CRUD project using symfony which lets you to start Symfony quickly but not teach you Symfony. To do this you have to visit Symfony site.
- web-server-bundle
- security-checker
- twig(https://twig.symfony.com/)
- routing
- form
- form-type
- controller
- entity
- xml
- orm
- serializer
- CRUD Application
- Developed By: Md. Noor-A-Alam Siddique
Dillinger uses a number of open source projects to work properly:
- [Symfony 4.1.0] - Number one fastest PHP framework!
- [Twig] - Modern template engine for PHP.
- [markdown-it] - Markdown parser done right. Fast and easy to extend.
- [PhpStorm] - Super flexible and rich IDE for project development.
This project requires latest composer version and git to run.
- If you already have virtual host setup and not require internal web server [here my project name is : symfony-api-boilerplate.local].
$ composer create-project symfony/website-skeleton symfony-api-boilerplate.local- Switch to project directory
$ cd symfony-api-boilerplate.local- If you require internal web server than
$ composer require symfony/web-server-bundle --dev- to start listening development server
$ php bin/console server:start-
start browsing
http://127.0.0.1:8000 -
to stop listening development server
$ php bin/console server:stop- Symfony provides a utility called the "Security Checker" to check whether your project's dependencies contain any known security vulnerability
$ composer require sensiolabs/security-checker --dev- Before creating the routes, lets create Database with installing Doctrine support via the ORM pack
composer require symfony/orm-pack- This command will create ```Entity```, ```Migrations``` and ```Repository``` folder inside ```src``` folder - In addition under ```config/packages``` it'll create ```doctrine.yaml``` and ```doctrine_migrations.yaml``` file - At the same time under ```config/packages/prod``` folder it'll create another ```doctrine.yaml``` file - To create a database by command please got to the
.envfile and change thedb_user,db_passwordanddb_nameand run the bellow command, which will create the database for you.
php bin/console doctrine:database:create-
For our project we are not using orm type as
annotations, where as we are using orm type asxml. [Personally I myself and other develoer also feel orm typexmlis most handy rather than usingannotations] -
Only annotation mapping is supported by
php bin/console make:Entityso we have to make our xml by typing it. -
Now, we are going to change the orm type
- to do this, open the
config/packages/doctrine.yamlfile - find the line
type: annotationand replace it withtype: xml
- to do this, open the
-
For more generalized our project we are changing the orm mapping directory [it's just an practise, not mandatory]
- to do this, open the
configfolder - create a folder called
doctrine - now open the
config/packages/doctrine.yamlfile - find the line
dir: '%kernel.project_dir%/src/Entity'and replace it withdir: '%kernel.project_dir%/config/doctrine'
- to do this, open the
-
Though we are not using
annotations, so we have to create entity file by manually- open
src/Entityfolder and create two entity files calledOffices.phpandOfficeEmployees.php - to present the object relation between entities we created two entity here
- now placed the bellow code into
Offices.phpand followed byOfficeEmployees.php
- open
<?php namespace App\Entity; class Offices { /** * @var int */ protected $id; /** * @var string */ protected $office_name; /** * @var string */ protected $office_description; /** * @return int */ public function getId() { return $this->id; } /** * @return string */ public function getOfficeName() { return $this->office_name; } /** * @param string $office_name */ public function setOfficeName(string $office_name) { $this->office_name = $office_name; } /** * @return string */ public function getOfficeDescription() { return $this->office_description; } /** * @param string $office_description */ public function setOfficeDescription(string $office_description) { $this->office_description = $office_description; } }- here variable type
Officesis a special type of variable, which indicate that this is variable which is connected with other table's [here isOfficesorsrc\Entity\Offices.php] id. In Symfony autometically add_idwith the variable name.
<?php namespace App\Entity; class OfficeEmployees { /** * @var int */ protected $id; /** * @var Offices */ protected $offices; /** * @var string */ protected $emp_name; /** * @return int */ public function getId() { return $this->id; } /** * @return string */ public function getEmpName() { return $this->emp_name; } /** * @param string $emp_name */ public function setEmpName(string $emp_name) { $this->emp_name = $emp_name; } /** * @return Offices */ public function getOffices() { return $this->offices; } /** * @param Offices $offices */ public function setOffices(Offices $offices) { $this->offices = $offices; } }- To use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc
composer require gedmo/doctrine-extensions- Now inside
config/doctrinefolder create a fileOffices.orm.xmland paste bellow codes
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="App\Entity\Offices" table="offices"> <id name="id" column="id" type="integer"> <generator strategy="AUTO" /> </id> <field name="office_name" type="string" nullable="true" /> <field name="office_description" type="string" nullable="true" /> </entity> </doctrine-mapping>- Again inside
config/doctrinefolder create a fileOffices.orm.xmland paste bellow codes
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="App\Entity\OfficeEmployees" table="office_employees"> <id name="id" column="id" type="integer"> <generator strategy="AUTO" /> </id> <field name="emp_name" type="string" nullable="true" /> <one-to-one field="offices" target-entity="App\Entity\Offices"> <cascade> <cascade-persist/> </cascade> <gedmo:blameable on="create"/> <join-column name="offices_id" referenced-column-name="id" nullable="false"/> </one-to-one> </entity> </doctrine-mapping>-
Here
one-to-oneorone-to-manyormany-to-onedefine the relationship with tables -
Now run the command for creating migration file by which next time developer can track the changes
php bin/console make:migration- Our migration file is ready to execute now. After running the bellow command create the tables into database.
php bin/console doctrine:migrations:migrate-
We will come back to
Repository, but before that lets do something controlling stuff withController -
Create two file inside
Controllerfolder calledOfficesController.phpandOfficeEmployeesController.php- Extends base controller by using
use Symfony\Bundle\FrameworkBundle\Controller\Controller;with this two controller use Symfony\Bundle\FrameworkBundle\Controller\Controller;with this two controlleruse Symfony\Component\HttpFoundation\Response;with this two controller
- Extends base controller by using
-
Now we are starting with
OfficesController.phpcontroller -
Inside
OfficesController.phpcreate a public function calledindex, which will show a hello message for us. So, lets do something fun part.- Run
composer require twigcommand to addtwig. Twig is a view template engine. In symfony you can use two different view engine for view write. Apart fromtwigyou can usephp[ reference : https://symfony.com/doc/2.0/cookbook/templating/PHP.html]. But remember, Symfony recomandtwiginstead of any other things. I am using twig for this documentation. - After running the command for twig, it'll create a folder called
Templateinto project root directory, where mainly all view related files will stored. - For configure your own directory path, you have to open the
config/packages/twig.yamlfile and change intodefault_path: '%kernel.project_dir%/templates'this line. I am taking the default path for this project. - To organize the files for particular things I love to create folder by folder for it's related files.
- Create a folder called
Officeswhere we'll store allOfficesrelated files. - First of all create a file called
index.html.twiginsideOfficesfolder - add bellow html code into
index.html.twigfor an example purpose - Twig is a view design template engine. To read more about it visit it's official website documentation [https://twig.symfony.com/].
- Run
<h1> {{ title }}</h1> <div> <h3>What is Lorem Ipsum?</h3> <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p> <br> <h3>Why do we use it?</h3> <p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</p> </div>- Now we are going to call this view file from controller. Open
OfficesController.phpfile and paste the bellow code insideindexfunction.
$pageTitle = "Home Page"; $offices = $this->getDoctrine()->getRepository(Offices::class)->findAll(); return $this->render('Offices/index.html.twig', [ 'title' => $pageTitle, 'offices' => $offices, ]);<!-- - Add ```SerializerInterface``` into the ```indexAction``` function --> - Now lets call the route. Create a
.htaccessinto yourpublicdirectory and bellow code into their to remove index.php from url.
<IfModule mod_rewrite.c> <IfModule mod_negotiation.c> Options -MultiViews -Indexes </IfModule> RewriteEngine On # Handle Authorization Header RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [L,R=301] # Handle Front Controller... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] </IfModule>- To more organize our application we are creating a folder called
Routesintosrcdirectory and going to map it with the main route fileconfig/routes.yamlby add this code
index: path: / controller: App\Controller\OfficesController::index web_app: resource: "../src/Routes/routes.yml" prefix: /web- here ```prefix``` is not mendatory. - Inside
Routesfolder Create a fileroutes.ymland a folderweband again insidewebcreate another folderoffices - Now Open the
routes.ymlfile and add bellow code
web_app_offices: resource: "web/offices/routes_for_offices.yaml" prefix: /offices- here ```prefix``` is not mendatory. - Inside
officesfolder create a fileroutes_for_offices.yamland add bellow codeOfficesControlleris a controller andindexis a function, what we create earlier- path
indexis the last part of our route
web_app_index: path: /index methods: GET controller: App\Controller\OfficesController::index-
Now browse
http://127.0.0.1:8000/web/offices/indexSeeeeeeeee!!!! we are done!!! -
Now we will add some office name into
officestable from our another view file. -
Oh! now lets do some stylesheet stuf by creating
cssandimagesfolder intopublicfolder. We are creatingcssfolder for stylesheets andimagesfor assets into public folder. we are using public folder for regular access. Now createstyle.cssfile into css folder and add bellow code to test our own stylesheet test.
h1 { color: #F00001; }- run ```composer require asset``` command to access asset() function. - Now open
templates/base.html.twigfile and bellow command
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %}</title> {% block stylesheets %} <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> <link rel="stylesheet" href="{{ asset('css/style.css') }}"> {% endblock %} </head> <body> <nav class="navbar navbar-expand-lg navbar-light navbar-bg mb-5"> <a style="margin-left: 75px;" class="navbar-brand space-brand" href="{{ app.request.schemeAndHttpHost }}"> <img class="nav-profile-img rounded-circle" src="{{ asset('images/dummy_logo_1.jpg') }}"> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNavDropdown"> <ul class="navbar-nav mr-auto"> <ul class="navbar-nav ml-auto"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" style="color: #000;" href="{{ asset('web/offices/index') }}" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Offices </a> <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> <a class="dropdown-item" href="{{ asset('web/offices/create') }}">Office List</a> <a class="dropdown-item" href="{{ asset('web/offices/create') }}">New Office</a> </div> </li> </ul> <ul class="navbar-nav ml-auto"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" style="color: #000;" href="{{ asset('web/offices/index') }}" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Office Employee </a> <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> <a class="dropdown-item" href="{{ asset('web/offices/create') }}">Office Employee List</a> <a class="dropdown-item" href="{{ asset('web/offices/create') }}">New Office Employee</a> </div> </li> </ul> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> <button class="btn btn-info my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> {% block body %} {% endblock %} {% block pagescripts %} <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script> <script> $('.dropdown-toggle').dropdown(); </script> {% endblock %} </body> </html>- Again open our created
index.html.twigand paste the bellow code
{% extends 'base.html.twig' %} {% block title %} {{ title }} {% endblock %} {% block body %} <div class="container"> <div class="row"> <div class="col-sm-12"> <div class="show-article-container p-3 mt-4"> <table id="offices" class="table table-striped"> <thead> <tr> <th>Office Name</th> <th>Office Description</th> <th>Actions</th> </tr> </thead> <tbody> {% if offices %} {% for office in offices %} <tr> <td>{{ office.officeName }}</td> <td>{{ office.officeDescription }}</td> <td> <a href="/web/offices/{{ office.id }}" class="btn btn-dark">Show</a> <a href="/web/offices/edit/{{ office.id }}" class="btn btn-light">Edit</a> <a href="#" class="btn btn-danger delete-office" data-id="{{ office.id }}">Delete</a> </td> </tr> {% endfor %} {% else %} <tr> <td colspan="3">No offices to display</td> </tr> {% endif %} </tbody> </table> </div> </div> </div> </div> {% endblock %}- Lets add some offices and for that inside
offices/routes_for_offices.yamlfolder add bellow code first
web_app_create: path: /create methods: ['GET','POST'] controller: App\Controller\OfficesController::create- Before start with controller we are going to add a bundle
formfor easy to handle symfony form\
composer require form- Add Bellow code with your controller
use App\Entity\Offices; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\HttpFoundation\Request;- Now inside
OfficesController.phpcontroller create another function calledcreateand paste the bellow code
$pageTitle = "Create Page"; $offices = new Offices(); $form = $this->createFormBuilder($offices) ->add('office_name', TextType::class, array('attr' => array('class' => 'form-control'))) ->add('office_description', TextareaType::class, array('attr' => array('class' => 'form-control'))) ->add('office_save', SubmitType::class, array('label' => 'Submit', 'attr' => array('class'=>'btn btn-primary mt-3'))) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $offices = $form->getData(); $entityManager = $this->getDoctrine()->getManager(); $entityManager->persist($offices); $entityManager->flush(); return $this->redirectToRoute('web_app_index'); } return $this->render('Offices/create.html.twig', array( 'title' => $pageTitle, 'form' => $form->createView() ));- Now create
create.html.twigand paste the bellow code
{% extends 'base.html.twig' %} {% block title %} {{ title }} {% endblock %} {% block body %} <div class="container"> <div class="row"> <div class="col-sm-12"> {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }} </div> </div> </div> {% endblock %}- lets show individual office details by clicking
showbutton. For this create a view file calledshow.html.twigand paste the bellow code
{% extends 'base.html.twig' %} {% block title %} {{ title }} {% endblock %} {% block body %} <div class="container"> <div class="row"> <div class="col-sm-12"> <div class="row"> <div class="col-md-12"> <h3>{{ offices.officeName }}</h3> </div> </div> <div class="row"> <div class="col-md-12"> <p>{{ offices.officeDescription }}</p> </div> </div> </div> </div> </div> {% endblock %}- To call this page add a route
routes_for_offices.yamlby adding the bellow code
web_app_show: path: /{id} methods: GET controller: App\Controller\OfficesController::show- Lets create a function
showand pass two parameter called$idandSerializerInterface $serializerinsideOfficesController.phpcontroller and add the bellow code. Here second parameter is not mendatory, This is a complex topic but I am using it to avoid unexpected null value error. To use this you have to runcomposer require symfony/serializercommand and have to useuse Symfony\Component\Serializer\SerializerInterface;in your controller.
$pageTitle = "Show Page"; $offices = $this->getDoctrine()->getRepository(Offices::class)->find($id); return $this->render('Offices/show.html.twig', [ 'title' => $pageTitle, 'offices' => $offices, ]);- Now we'll work for delete section. To call delete route we are going to add a route into
routes_for_offices.yamlfile
web_app_delete: path: /delete/{id} methods: DELETE controller: App\Controller\OfficesController::delete-
We are adding some js stuff for alert us before delete. For that we are going to create a folder called
jsinsidepublicfolder and create a js file calledmain-js.js -
Now we add this js file's reference into our
base.htnl.twigfile. For that open the file and add bellow code there
. . {% block javascripts %} {% endblock %} </body> </html>- Add another js file's reference into our
index.htnl.twigfile. For that open the file and add bellow code there
. . {% block javascripts %} <script type="text/javascript" src="/js/main-js.js"></script> {% endblock %}- Now add some javascript into
public/js/main-js.jsfile
const offices = document.getElementById('offices'); if (offices) { offices.addEventListener('click', (e) => { if (e.target.className === 'btn btn-danger delete-office') { if (confirm('Are you Sure?')) { const id = e.target.getAttribute('data-id'); fetch(`/web/offices/delete/${id}`, { method: 'DELETE' }).then(res => window.location.reload()); } } }); }- Lets do some controlling stuff for delete method. Create a
deletefunction intoOfficeController.phpwith parameter $id
$offices = $this->getDoctrine()->getRepository(Offices::class)->find($id); $entityManager = $this->getDoctrine()->getManager(); $entityManager->remove($offices); $entityManager->flush(); $response = new Response(); $response->send();- Now we are going to edit our newly created office information. For that at first we'll create a route in
routes_for_offices.yamlfile
web_app_edit: path: /edit/{id} methods: ['GET','POST'] controller: App\Controller\OfficesController::edit- For edit something we need a view file where our current content will display. So, for that we are going to create a view file called
edit.html.twigintotemplates/Officesfolder
{% extends 'base.html.twig' %} {% block title %} {{ title }} {% endblock %} {% block body %} <div class="container"> <div class="row"> <div class="col-sm-12"> {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }} </div> </div> </div> {% endblock %}- Now come up it's controlling stuff. for that we take two parameter into a function called
editintoOfficesController.php. One parameter is$idand another one isRequest $request.
$pageTitle = "Edit Page"; $offices = $this->getDoctrine()->getRepository(Offices::class)->find($id); $form = $this->createFormBuilder($offices) ->add('office_name', TextType::class, array('attr' => array('class' => 'form-control'))) ->add('office_description', TextareaType::class, array('attr' => array('class' => 'form-control'))) ->add('office_save', SubmitType::class, array('label' => 'Update', 'attr' => array('class'=>'btn btn-primary mt-3'))) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $entityManager = $this->getDoctrine()->getManager(); $entityManager->flush(); return $this->redirectToRoute('web_app_index'); } return $this->render('Offices/edit.html.twig', array( 'title' => $pageTitle, 'form' => $form->createView() ));