Skip to content

Latest commit

 

History

History
1276 lines (1022 loc) · 43.8 KB

readme_backend.md

File metadata and controls

1276 lines (1022 loc) · 43.8 KB

Symfony 4 - App Cinemas (Backend)

We will make the backend of the application with the framework Symfony. We will have two entities, User and Task. Access to the login will be via Jwt

Summary steps:

  1. Project Creation
  2. Database Configuration
  3. Entities
  4. Routing
  5. Installing Vue, SASS and bootstrap 4 using WebPackEncore
    1. Installing Twig
    2. Installing Vue using WebPackEncore
    3. Installing SASS and bootstrap 4
  6. Creating a JSON Response
  7. Login JWT
  8. Other Requests to the Api

  • We will create the project through the console command: composer create-project symfony/skeleton symfony

Summary Symfony component`s to use

Summary Console command`s to be used

  • php bin/console server:run
  • php bin/console doctrine:database:create
  • php bin/console doctrine:schema:update --force
  • php bin/console doctrine:migrations:diff
  • php bin/console doctrine:migrations:migrate
  • php bin/console make:controller Default

Symfony 4 - Task Manager (Backend)


1.Project Creation


  1. Created our project using the Console command's,
composer create-project symfony/skeleton symfony
  1. In the next step we will access the project folder using:
cd symfony
  1. It is necessary to install the server component, to use our Server Local, through the console command:
composer require server --dev
  1. Now, we are going to install de profiler component to can use the debug toolbar of symfony:
composer require --dev profiler
  1. Now, you will be able to view the result of demo when write in the terminal the command console:
php bin/console server:run
  1. Access to http://127.0.0.1:8000/ to view the result.

2.Database Configuration


  1. We will installed Doctrine Component to manage the database of project using the command:
composer require doctrine
  1. To configurate the database connection, we will modified the environment variable called DATABASE_URL. For then, we you can find and customize this inside .env:

.env

# .env
###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
# DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
++ DATABASE_URL=mysql://root:@127.0.0.1:3306/app-cinemas
++ # db_user: root
++ # db_password: 
++ # db_name: app-cinemas
# to use sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
###< doctrine/doctrine-bundle ###

(Source: https://symfony.com/doc/current/doctrine.html#configuring-the-database)

  1. In console lunch the command php bin/console doctrine:database:create. Now you have your data base created in your local server.

  2. If you want to use XML instead of yaml, add type: yml and dir: '%kernel.project_dir%/config/doctrine' to the entity mappings in your config/packages/doctrine.yaml file.

config/packages/doctrine.yaml

doctrine:
    # ...
    orm:
        # ...
        mappings:
            App:
                is_bundle: false
                # type: annotation
                type: yml
                # dir: '%kernel.project_dir%/src/Entity'
                dir: '%kernel.project_dir%/config/doctrine'
                prefix: 'App\Entity'
                alias: App

(Source: https://symfony.com/doc/current/doctrine.html)


3.Entities


  1. Now, We can generate our user and task entities.

User Entity

config/doctrine/User.orm.yml

App\Entity\Users:
    type: entity
    #repositoryClass: App\Repository\UserRepository      
    table: users
    id:
        id:
            type: integer
            nullable: false
            options:
                unsigned: false
            id: true
            generator:
                strategy: IDENTITY
    fields:
        role:
            type: json_array
            nullable: false
            length: null
            options:
                fixed: false         
        username:
            type: string
            nullable: true
            length: 80
            options:
                fixed: false
        name:
            type: string
            nullable: true
            length: 180
            options:
                fixed: false
        surname:
            type: string
            nullable: true
            length: 255
            options:
                fixed: false                
        email:
            type: string
            nullable: false
            length: 254
            options:
                fixed: false
        plainPassword:
            type: text
            length: 4096
            column: plain_password
        password:
            type: string
            length: 64
        isActive:
            type: boolean
            nullable: false
            options:
                default: '0'
            column: is_active
        createdAt:
            type: datetime
            nullable: false
            column: created_at
        updatedAt:
            type: datetime
            nullable: false
            column: updated_at
    lifecycleCallbacks: {  }

We need use the security-bundle of Symfony to extends the class user (class Users implements UserInterface {}), for it execute the command composer require symfony/security-bundle.

src/Entity/User.php

<?php
// src/Entity/User.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;

use Symfony\Component\HttpFoundation\JsonResponse;

class Users implements UserInterface {
    private $id;
		public function getId() { return $this->id; } 
    private $username;
    public function getUsername() { return $this->username; }
    public function setUsername($username=null) { $this->username = $username; }    
    private $name;    
		public function getName() { return $this->name; }
    public function setName($name) { $this->name = $name; } 		 	
		private $surname;
    public function setSurname($surname) { $this->surname = $surname; return $this; }
    public function getSurname() { return $this->surname; }		
		private $email;
    public function setEmail($email) { $this->email = $email; return $this; }
    public function getEmail() { return $this->email; }		
    private $plainPassword;
    public function getPlainPassword() { return $this->plainPassword; }
    public function setPlainPassword($password) { $this->plainPassword = $password; }    
    /**
     * The below length depends on the "algorithm" you use for encoding
     * the password, but this works well with bcrypt.
     */
    private $password;
    public function getPassword() { return $this->password; }
    public function setPassword($password) { $this->password = $password; }
		private $isActive;
    public function getIsActive() { return $this->isActive; }
    public function setIsActive($isActive) { $this->isActive = $isActive; }		
    public function __construct()     {
        $this->isActive = true;
        // may not be needed, see section on salt below
        // $this->salt = md5(uniqid('', true));
    }
    private $createdAt;
    public function setCreatedAt($createdAt) { $this->createdAt = $createdAt; return $this; }
		public function getCreatedAt() { return $this->createdAt; }
    private $updatedAt;
    public function setUpdatedAt($updatedAt) { $this->updatedAt = $updatedAt; return $this; }
    public function getUpdatedAt() { return $this->updatedAt; }    
    /* other necessary methods ***********************************************************************************/
    public function getSalt() {
			// The bcrypt and argon2i algorithms don't require a separate salt.
			// You *may* need a real salt if you choose a different encoder.
			return null;
		}
		// other methods, including security methods like getRoles()
		private $role;
    public function setRole($role) { $this->role = $role; /*return $this;*/ }
		public function getRole() { return $this->role; }
		public function getRoles(){ return array('ROLE_USER','ROLE ADMIN'); }
		// ... and eraseCredentials()
		public function eraseCredentials() { }
		/** @see \Serializable::serialize() */
		public function serialize() {
				return serialize(array(
						$this->id,
						$this->username,
						$this->password,
						$this->role,
						// see section on salt below
						// $this->salt,
				));
		}
		public function unserialize($serialized) {
				list (
						$this->id,
						$this->username,
						$this->password,
						// see section on salt below
						// $this->salt
				) = unserialize($serialized);
		} 		
}

We can load the database in (/symfony/bbdd/bbdd.sql) using phmyadmin and enter a first example user.

To read the rest of the project entities access the folders /symfony/src/Entity/ and /symfony/config/doctrine/.


4.Routing


  1. We will use the routing type yaml, for them we configure the type of routing in config/routes.yaml.

config/routes.yaml

#index:
#    path: /
#    controller: App\Controller\DefaultController::index
# Importante en los archivos con extensión .yaml cada sangrado equivale a 4 espacios!!!
routing_distributed:
    # loads routes from the given routing file stored in some bundle
    resource: '../src/Resources/config/routing.yaml' 
    type: yaml
  1. Next step is defined our folder with routing files of our app in src/Resources/config/routing.yml

src/Resources/config/routing.yml

app_routing_folder:
    # loads routes from the given routing file stored in some bundle
    prefix: '/api/v1'
    resource: 'routing/' 
    type: directory

src/Resources/config/routing/default.yml

default_pruebas:
    path: /test
    controller: App\Controller\DefaultController::test
    #methods:   [POST] 
  1. Now we can test the generated entities and the router. For this we generate a first controller /src/Controller/DefaultController.php with a method tests().

/src/Controller/DefaultController.php

<?php
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use App\Entity\Users;

class DefaultController extends Controller {
  public function test (request $request) {
    $em = $this->getDoctrine()->getManager();
    $user_repo = $em->getRepository(Users::class);
    $userList = $user_repo->findAll();
    return $this->render('base.html.twig', [
      'base_dir' =>realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR,
      'user'=>$userList
    ]);
  }
}
  1. If we run php bin/console server:run and access http://127.0.0.1:8000/api/v1/test we can see the existing entity.

5.Installing Vue, SASS and bootstrap 4 using WebPackEncore



5.1.Installing Twig


composer require twig/extensions

(Source: Twig Extension Component)

composer require symfony/asset

(Source: Assets for Twig Component)

We will add the line <meta name="viewport" content="width=device-width, initial-scale=1.0"> for can use the responsive web in mobiles.

/templates/base.html.twig

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
++    <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>{% block title %}Welcome!{% endblock %}</title>
      {% block stylesheets %}{% endblock %}
  </head>
  <body>
    {% block body %}{% endblock %}
    {% block javascripts %}{% endblock %}
  </body>
</html>

5.2.Installing Vue using WebPackEncore


(Source: CloudWays - Getting Started With Vue.Js In Symfony)

First, make sure you install Node.js and also the NPM package manager.

We can use the commands node -v and npm -v to know the version installed in our computer.

Then, install Encore into your project with NPM:

npm install @symfony/webpack-encore --save-dev

If you are using Flex for your project, you can initialize your project for Encore via:

composer require encore

(Source: Symfony Doc - Webpack Encore Component)

This will create a webpack.config.js file, add the assets/ directory, and add node_modules/ to .gitignore.

This command creates (or modifies) a package.json file and downloads dependencies into a node_modules/ directory. When using npm 5, a package-lock.json file is created/updated.

Next, create your webpack.config.js.

webpack.config.js

// webpack.config.js
var Encore = require('@symfony/webpack-encore');

Encore
    // the project directory where all compiled assets will be stored
    .setOutputPath('public/build/')

    // the public path used by the web server to access the previous directory
    .setPublicPath('/build')

    // will create public/build/app.js and public/build/app.css
    .addEntry('app', './assets/js/app.js')

    // allow legacy applications to use $/jQuery as a global variable
    .autoProvidejQuery()

    // enable source maps during development
    .enableSourceMaps(!Encore.isProduction())

    // empty the outputPath dir before each build
    .cleanupOutputBeforeBuild()

    // show OS notifications when builds finish/fail
    .enableBuildNotifications()

    // create hashed filenames (e.g. app.abc123.css)
    // .enableVersioning()

    // allow sass/scss files to be processed
    // .enableSassLoader()
;

// export the final configuration
module.exports = Encore.getWebpackConfig();

/templates/base.html.twig

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}
++    <link rel="stylesheet" href="{{ asset('build/app.css') }}">
    {% endblock %}
  </head>
  <body>
    {% block body %}{% endblock %}
    {% block javascripts %}
++     <script src="{{ asset('build/app.js') }}"></script>  
    {% endblock %}
  </body>
</html>

Now, we install Vue and some dependencies:

npm i vue --save-dev

(Source: https://www.npmjs.com/package/vue)

npm i vue-loader --save

(Source: https://www.npmjs.com/package/vue-loader)

npm i vue-template-compiler --save

(Source: https://www.npmjs.com/package/vue-template-compiler)

Then, activate the vue-loader and its plugin in webpack.config.js:

webpack.config.js

var Encore = require('@symfony/webpack-encore');
++ const { VueLoaderPlugin } = require('vue-loader');

Encore
    // the project directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // the public path used by the web server to access the previous directory
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    // uncomment to create hashed filenames (e.g. app.abc123.css)
    // .enableVersioning(Encore.isProduction())

    // uncomment to define the assets of the project
    // .addEntry('js/app', './assets/js/app.js')
++  .addEntry('js/app', './assets/js/app.js')
    // .addStyleEntry('css/app', './assets/css/app.scss')

    // uncomment if you use Sass/SCSS files
    // .enableSassLoader()

    // uncomment for legacy applications that require $/jQuery as a global variable
    // .autoProvidejQuery()
++  // Enable Vue loader
++  .enableVueLoader()
++  .addPlugin(new VueLoaderPlugin())
++  ;
;

module.exports = Encore.getWebpackConfig();

Issue enableVueLoader does not include VueLoaderPlugin? #311 - symfony - webpack-encore

We can already create our first component of Vue:

/assets/js/app.js

// assets/js/app.js
import Vue from 'vue';
 
import Example from './components/Example'
 
/**
* Create a fresh Vue Application instance
*/
new Vue({
  el: '#app',
  components: {Example}
});

And its component.

/assets/js/components/Example.vue

<template>
   <div>
       <p>This is an example of a new components in VueJs</p>
   </div>
</template>
 
<script>
   export default {
       name: "example"
   }
</script>
 
<style scoped>
 
</style>

/templates/base.html.twig

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}
      <link rel="stylesheet" href="{{ asset('build/app.css') }}">
    {% endblock %}
  </head>
  <body>
++  <div id="app">  
      {% block body %}{% endblock %}
++  </div>  
    {% block javascripts %}
      <script src="{{ asset('build/app.js') }}"></script>  
    {% endblock %}
  </body>
</html>

/templates/default/vue-example.html.twig

{% extends 'base.html.twig' %}
{% block body %}
 
   <div class="text-center">
       <h3>My Symfony 4 sample project</h3>
 
       <div class="jumbotron text-center">
           <example></example>
       </div>
   </div>
 
{% endblock %}

src/Resources/config/routing/default.yml

# ...
test:
    path: /test
    controller: App\Controller\DefaultController::test
    #methods:   [POST]
++ test_vue_example:
++   path: /vue_example
++   controller: App\Controller\DefaultController::vueExample  

/src/Controller/DefaultController.php

<?php
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use App\Entity\Users;

class DefaultController extends Controller {
  // ...
++ public function vueExample (request $request) {
++   return $this->render('default/vue-example.html.twig');
++ }  
}

5.3.Installing SASS and bootstrap 4


First, we install Sass and Bootstrap dependencies:

npm i sass-loader --save-dev

(Source: npm - sass-loader)

npm i node-sass --save-dev

(Source: npm - node-sass)

npm i bootstrap --save-dev

(Source: npm - bootstrap)

npm i jquery --save-dev

(Source: npm - jquery)

npm i popper.js --save

(Source: npm - popper)

And we will have to modified the webpack.config.js with the new configuration.

webpack.config.js

var Encore = require('@symfony/webpack-encore');
const { VueLoaderPlugin } = require('vue-loader');

Encore
    // the project directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // the public path used by the web server to access the previous directory
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    // uncomment to create hashed filenames (e.g. app.abc123.css)
    // .enableVersioning(Encore.isProduction())

    // uncomment to define the assets of the project
    // .addEntry('js/app', './assets/js/app.js')
    .addEntry('js/app', './assets/js/app.js')
    // .addStyleEntry('css/app', './assets/scss/app.scss')

    // uncomment if you use Sass/SCSS files
    // .enableSassLoader()
++  .enableSassLoader()

    // uncomment for legacy applications that require $/jQuery as a global variable
    // .autoProvidejQuery()
    // Enable Vue loader
    .enableVueLoader()
    .addPlugin(new VueLoaderPlugin())
    ;
;

module.exports = Encore.getWebpackConfig();

Now modified the /assets/js/app.js for include the libraries jquery and bootstrap in our vue app.

/assets/js/app.js

// assets/js/app.js
++ import $ from 'jquery';
++ import 'bootstrap';

import Vue from 'vue';

import Example from './components/Example'
 
/**
* Create a fresh Vue Application instance
*/
new Vue({
  el: '#app',
  components: {Example}
});

And include the library bootstrap in our style file,

/assets/scss/app.scss

@import '~bootstrap/scss/bootstrap';

$acceVerde: #84a640;
$acceAzul: #396696;

h1 {
    color: $acceVerde;

    s {
        color: $acceAzul;
    }
}

6.Creating a JSON Response


To convert data to Json using symfony it is necessary to have the http-foundation component ( Source: The HttpFoundation Component ).

composer require symfony/http-foundation
  1. First we will check the sending method jsonResponse, generating a test in the controller /src/Controller/DefaultController.php.

/src/Controller/DefaultController.php

<?php
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

++ use Symfony\Component\HttpFoundation\JsonResponse;

use App\Entity\User;

class DefaultController extends Controller {
  public function tests (Request $request){
    $em = $this->getDoctrine()->getManager();
    $user_repo = $em->getRepository(User::class);
    $userList = $user_repo->findAll();
++  /*
    return $this->render('base.html.twig', [
      'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR,
      'user'=>$user_repo
    ]);
++  */
++  return new JsonResponse(array(
++    'status' => 'succes',
++    'users' => $user_repo
++  ));
  }
}

Although we use the JsonResponse() component, in symfony there is a default component which we access through $this->json().

  1. If we run php bin/console server: run and access http://127.0.0.1:8000/ we will see that it does not convert correctly. Still we see that JsonResponse() does not perform the conversion correctly so we will have to generate a service that performs the conversion correctly.

To create our encoder Json you must execute the command composer require symfony/serializer to download the serializer component.

composer require symfony/serializer

/src/Service/Helpers.php

<?php
// src/Service/Helpers.php
namespace App\Service;

class Helpers{
    public $manager;
	public function __construct($manager){
		$this->manager = $manager;
	}    
	public function json($data){
		$normalizers = array(new \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer());
		// 
		$encoders = array("json" => new \Symfony\Component\Serializer\Encoder\JsonEncoder());

		$serializer = new \Symfony\Component\Serializer\Serializer($normalizers, $encoders);
		$json = $serializer->serialize($data, 'json');

		$response = new \Symfony\Component\HttpFoundation\Response();
		$response->setContent($json);
		$response->headers->set('Content-Type', 'application/json');

		return $response;
	}
	public function decoding_json($json){
		$params = json_decode($json);
		return $params;
	}
}
  • GetSetMethodNormalizer, This normalizer reads the content of the class by calling the "getters" (public methods starting with "get"). It will denormalize data by calling the constructor and the "setters" (public methods starting with "set"). Objects are normalized to a map of property names and values (names are generated removing the get prefix from the method name and lowercasing the first letter; e.g. getFirstName() -> firstName).

  • Encoders, JsonEncoder, This class encodes and decodes data in JSON.

  1. To use this new service it is necessary to declare it in config/services.yaml.

/config/services.yaml

//...
    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
++  App\Service\Helpers:
++      public: true
++      arguments: 
++          $manager: '@doctrine.orm.entity_manager'
  1. Inside the src/Controller/DefaultController.php we will call it that.

/src/Controller/DefaultController.php

<?php
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use Symfony\Component\HttpFoundation\JsonResponse;

++ use App\Service\Helpers;

use App\Entity\User;

class DefaultController extends Controller {
-- public function index (Request $request){
++ public function index (Request $request, Helpers $helpers){    
    $em = $this->getDoctrine()->getManager();
    $user_repo = $em->getRepository(User::class);
    $userList = $user_repo->findAll();

++  return $helpers->json($userList);   
--  return new JsonResponse(array(
--      'status' => 'succes',
--      'users' => $userList
--  ));
  }
}

7.Login JWT


  1. Create the function login within /src/Controller/AuthenticationController.php.

/src/Controller/DefaultController.php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
++ use Symfony\Component\HttpFoundation\JsonResponse;

++ use Symfony\Component\Validator\Constraints as Assert;

++ use App\Service\Helpers;

//...
class AuthenticationController extends Controller {
  //..

++ public function login(Request $request, Helpers $helpers){
++  // Receive json by POST
++  $json = $request->get('json', null);
++  // Array to return by default
++  $data = array(
++      'status' => 'error',
++      'data' => 'Send json via post !!'
++    );
++  }
++  return $helpers->json($data);
++ }

//..
  1. We add a new route in src/Resources/config/routing/default.yml.

/src/Resources/config/routing/authentication.yml

authentication_login:
    path: /login
    controller: App\Controller\AuthenticationController::login
    methods:   [POST]
  1. Access to https://app.getpostman.com/ and send a petition to url http://127.0.0.1:8000/api/v1/login

You must receive the following message: {"status":"error","data":"Send json via post !!"}.

  1. We complete the method login so that it looks for the email match within the User entity.

/src/Controller/AuthenticationController.php

//...
class AuthenticationController extends Controller {
  //..
  public function login(Request $request, Helpers $helpers){
    // Receive json by POST
    $json = $request->get('json', null);
    // Array to return by default
    $data = array( 'status' => 'error', 'data' => 'Send json via post !!' );
++  if($json != null){
++      // you make the login
++      // We convert a json to a php object
++      $params = json_decode($json);
++      $email = (isset($params->email)) ? $params->email : null;
++      $password = (isset($params->password)) ? $params->password : null;

++      $emailConstraint = new Assert\Email();
++      $emailConstraint->message = "This email is not valid !!";
++      $validate_email = $this->get("validator")->validate($email, $emailConstraint);

++      if($email != null && count($validate_email) == 0 && $password != null){
++          $data = array(
++              'status' => 'success',
++              'data' => 'Login success'
++          );
++      }else{
++          $data = array(
++              'status' => 'error',
++              'data' => 'Email or password incorrect'
++          );
++      }
++  }
    return $helpers->json($data);
  }
//..
  1. Access to https://app.getpostman.com/ and send a petition to url http://127.0.0.1:8000/api/v1/login using in the body x-www-form-urlencoded with key json and value {"email":"admin@admin.com", "password":"1"}.

You must receive the following message: {"status":"success","data":"Email or password success"}.

  1. we will need to create a new service that authenticates us and generates the token.

src/Service/JwtAuth.php

<?php
// src/Service/JwtAuth.php
namespace App\Service;

use App\Entity\User;
use Firebase\JWT\JWT;

class JwtAuth{
	public $manager;
	public $key;

	public function __construct($manager){
		$this->manager = $manager;
		$this->key = 'helloHowIAmTheSecretKey-98439284934829';
	}
	public function signup($email, $password, $getHash = null){
		$user = $this->manager->getRepository(User::class)->findOneBy(array(
			"email" => $email,
			"password" => $password
		));
		$signup = (is_object($user))? true : false;
		if($signup == true){
			// GENERATE TOKEN JWT
			$token = array(
				"sub"   => $user->getId(),
				"email" => $user->getEmail(),
				"name"	=> $user->getName(),
				"surname" => $user->getSurname(),
				"iat"	=> time(),
				"exp"	=> time() + (7 * 24 * 60 * 60)
			);
			$jwt = JWT::encode($token, $this->key, 'HS256');
			$decoded = JWT::decode($jwt, $this->key, array('HS256'));
            $data = ($getHash == null)? $jwt : $decoded ;
		}else{
			$data = array( 'status' => 'error', 'data' => 'Login failed!!' );
		}
		return $data;
	}
}

You need the library Firebase\JWT, to install execute the command composer require firebase/php-jwt.

  1. To use this new service it is necessary to declare it in config/services.yaml.

config/services.yaml

//...
    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
    App\Service\Helpers:
        public: true
        arguments: 
            $manager: '$manager: '@doctrine.orm.entity_manager'
++  App\Service\JwtAuth:
++      public: true
++      arguments: 
++          $manager: '@doctrine.orm.entity_manager'            
  1. Now we will modify the login method, in /src/Controller/AuthenticationController.php, so that it consults in the database if the user exists and if it exists, it generates a token.

/src/Controller/AuthenticationController.php

//...
use App\Service\Helpers;
++ use App\Service\JwtAuth;
//...
class AuthenticationController extends Controller {
  //..
-- public function login(Request $request, Helpers $helpers){
++ public function login(Request $request, Helpers $helpers, JwtAuth $jwt_auth ){    
    // Receive json by POST
    $json = $request->get('json', null);
    // Array to return by default
    $data = array( 'status' => 'error', 'data' => 'Send json via post !!' );
    if($json != null){
        // you make the login
        // We convert a json to a php object
        $params = json_decode($json);
        $email = (isset($params->email)) ? $params->email : null;
        $password = (isset($params->password)) ? $params->password : null;

        $emailConstraint = new Assert\Email();
        $emailConstraint->message = "This email is not valid !!";
        $validate_email = $this->get("validator")->validate($email, $emailConstraint);

        if($email != null && count($validate_email) == 0 && $password != null){
--          $data = array(
--              'status' => 'success',
--              'data' => 'Login success'
--          );
++          $jwt_auth = $this->get(JwtAuth::class);
++          if($getHash == null || $getHash == false){
++              $signup = $jwt_auth->signup($email, $pwd);
++          }else{
++              $signup = $jwt_auth->signup($email, $pwd, true);
++          }
++          return new JsonResponse($signup);
        }else{
            $data = array(
                'status' => 'error',
                'data' => 'Email or password incorrect'
            );
        }
    }
    return $helpers->json($data);
  }
//..

IMPORTANT We replace the return command $this->json($signup); by return new JsonResponse($signup); to avoid failures in the transformation to json.

  1. we must generate a new method within the service that checks the token.

src/Service/JwtAuth.php

//..
class JwtAuth{
    //..
++	public function checkToken($jwt, $getIdentity = false){
++      $auth = false;
++		try{
++			$decoded = JWT::decode($jwt, $this->key, array('HS256'));
++      }catch(
++            \UnexpectedValueException $e){ $auth = false; 
++      }catch(
++            \DomainException $e){ $auth = false; 
++      }
++		if(isset($decoded) && is_object($decoded) && isset($decoded->sub)){ $auth = true; }else{ $auth = false; }
++		if($getIdentity == false){ 
++          return $auth; 
++      }else{
++          return $decoded; 
++      }
++  }
    //..
  1. Now, I can post in https://app.getpostman.com/ a new request to url http://127.0.0.1:8000/api/v1/login using in the body x-www-form-urlencoded with key json and value {"email":"admin@admin.com", "password":"1"} to collect the token.

In our case we received:

"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwibmFtZSI6ImFkbWluIiwic3VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTI1MDk3NTEwLCJleHAiOjE1MjU3MDIzMTB9.UA3f6W2mqzrHCoJNvCqxHW4NmOFe-9sMVfNOXXPW_gY"
  1. We modify the test method in /src/Controller/DefaultController.php.

/src/Controller/DefaultController.php

//...
use App\Service\Helpers;
use App\Service\JwtAuth;
//...
class DefaultController extends Controller {
  public function test (Request $request, Helpers $helpers){
++  $token = $request->get("authorization", null);      
++  if ($token){  
        $em = $this->getDoctrine()->getManager();
        $user_repo = $em->getRepository(User::class);
        $userList = $user_repo->findAll();

        return $helpers->json(array(
            'status' => 'success',
            'users' => $userList
        ));
++  } else {
--      return $helpers->json($userList);
++    return $helpers->json(array(
++      'status' => 'error',
++      'code' => 400,
++      'users' => 'Login failed!!!'
++    )); 
++  }
}
  1. Again, I can post in https://app.getpostman.com/ a new request to url http://127.0.0.1:8000/api/v1/test using in the body x-www-form-urlencoded with key authorization and value "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwibmFtZSI6ImFkbWluIiwic3VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTI1MDk3NTEwLCJleHAiOjE1MjU3MDIzMTB9.UA3f6W2mqzrHCoJNvCqxHW4NmOFe-9sMVfNOXXPW_gY" to collect the token.

In our case we received:

{"status":"success","users":[{"id":1,"username":"","name":"admin","surname":"admin","email":"admin@admin.com","password":"1","createdAt":{"timezone":{"name":"UTC","transitions":[{"ts":-9223372036854775808,"time":"-292277022657-01-27T08:29:52+0000","offset":0,"isdst":false,"abbr":"UTC"}],"location":{"country_code":"??","latitude":0,"longitude":0,"comments":""}},"offset":0,"timestamp":1522540800},"salt":null,"role":"admin","roles":["ROLE_USER","ROLE ADMIN"]}]}

8.Other Requests to the Api


To be able to access the rest of the requests, you must see the url of the request inside the routing files of the directory /src/Resources/config/routing/ and access the corresponding controller method /src/Controller/.