Vous êtes sur la page 1sur 108

Symfony 5 : API REST

Achref El Mouelhi

Docteur de l’université d’Aix-Marseille


Chercheur en Programmation par contrainte (IA)
Ingénieur en Génie logiciel

elmouelhi.achref@gmail.com

H & H: Research and Training 1 / 75


Plan

1 Introduction

2 Contrôleur avec réponse JSON


NormalizerInterface
@Groups
SerializerInterface
JsonResponse
La méthode json

H & H: Research and Training 2 / 75


Plan

3 API Platform
@ApiResource
Collection et item operations
routePrefix
Pagination
normalizationContext et denormalizationContext
DataPersisterInterface
@ApiSubresource
@ApiFilter

4 JWT et API Platform

H & H: Research and Training 3 / 75


Introduction

Symfony

Service web (WS pour Web Service) ?

Un programme (ensemble de fonctionnalités exposées en temps


réel et sans intervention humaine)
H I ©
Accessible via internet, ou intranet
UEL
O
f E LM
Indépendant de tout système d’exploitation

ch r e
Indépendant de tout langage de programmation

©A
Utilisant un système standard d’échange (XML ou JSON), ces
messages sont généralement transportés par des protocoles
internet connus HTTP (ou autres comme FTP, SMTP...)
Pouvant communiquer avec d’autres WS

H & H: Research and Training 4 / 75


Introduction

Symfony

Les WS peuvent utiliser les technologies web suivantes :

HTTP (Hypertext Transfer Protocol) : le protocole, connu, utilisé par le


World Wide Web et inventé par Roy Fiedling.

H I ©
U EL
REST (Representational State Transfer) : une architecture de services Web,
créée aussi par Roy Fielding en 2000 dans sa thèse de doctorat.
M O
E L : unla transmission
SOAP (Simple object Access Protocol)
f
protocole, défini par Microsoft et IBM

h r e
ensuite standarisé par W3C, permettant de messages entre

© Ac
objets distants (physiquement distribués).

WSDL (Web Services Description Language) : est un langage de description de


service web utilisant le format XML (standardisé par le W3C depuis 2007).

UDDI (Universal Description, Discovery and Integration) : un annuaire de WS.

H & H: Research and Training 5 / 75


Introduction

HTTP, REST (Representational State Transfer) et RESTful ?

Les API REST sont basées sur le protocole HTTP (architecture client/serveur) et
utilisent le concept de ressource.

Une ressource est identifiée par une URI unique.

I ©
L’API REST utilise donc des méthodes suivantes pour l’échange de données
H
entre client et serveur
U EL
O
LM
GET pour la récupération,
POST pour l’ajout,
r e f E
ch
DELETE pour la suppression,

©A
PUT pour la modification,
...

Plusieurs formats possibles pour les données échangées : texte, XML, JSON...

RESTful est l’adjectif qui désigne une API REST.

H & H: Research and Training 6 / 75


Introduction

Symfony

Rôle du contrôleur dans une application MVC

Le contrôleur reçoit une requête HTTP et communique avec

H I ©
modèle, service, constructeur de formulaires pour retourner une

EL
réponse HTTP contenant une page HTML.

O U
Le contrôleur peut aussi retourner une réponse HTTP ne contenant
pas de vue.
f E LM
Il retourne des c r e
h ées sous format JSON, XML...
donn
© A

H & H: Research and Training 7 / 75


Introduction

Symfony

Rôle du contrôleur dans une application MVC

Le contrôleur reçoit une requête HTTP et communique avec

H I ©
modèle, service, constructeur de formulaires pour retourner une

EL
réponse HTTP contenant une page HTML.

O U
Le contrôleur peut aussi retourner une réponse HTTP ne contenant
pas de vue.
f E LM
Il retourne des c r e
h ées sous format JSON, XML...
donn
© A

Ceci est l’objet de ce chapitre.

H & H: Research and Training 7 / 75


Introduction

Symfony
Étapes à suivre avant de commencer le cours

1 Allez à https://github.com/Achreftun/api-rest-symfony

2 Clonez le dépôt dans votre espace de travail


H I ©
3
U ELou composer
Installez les dépendances : composer install
update
L MO
Créez la base de donne f :E
4

c h r ées php bin/console d:d:c

5 Appliquez©lesAmigrations : php bin/console d:m:m


6 Envoyez les fixtures (les tuples) à la base de données : php
bin/console d:f:l

7 Lancez le server : symfony server:start

H & H: Research and Training 8 / 75


Introduction

Symfony

Utilisation de Postman : simulateur d’un client REST


Aller sur internet et chercher Postman
H I ©
U EL
Télécharger puis installer l’application Postman
Démarrer l’application L MO
r e f E
A ch
©
Il existe plusieurs autres tel que ARC (pour Advanced REST Client)...

H & H: Research and Training 9 / 75


Contrôleur avec réponse JSON

Symfony

Créons un contrôleur ApiPersonneController


H I ©
EL
OU
Exécutez php bin/console make:controller
M
Répondez à Choose a E
f L for your controller class
name

chr
par ApiPersonne e
© A

H & H: Research and Training 10 / 75


Contrôleur avec réponse JSON

Symfony
Contenu généré de ApiPersonneController

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

H I ©
class ApiPersonneController extends AbstractController
UEL
{
O
LM
/**

*/
r e E
* @Route("/api/personne", name="api_personne")
f
public function index()
ch
{
©A
return $this->json([
’message’ => ’Welcome to your new controller!’,
’path’ => ’src/Controller/ApiPersonneController.php’,
]);
}
}

H & H: Research and Training 11 / 75


Contrôleur avec réponse JSON

Pour retourner les tuples de la table Personne sous format JSON


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\PersonneRepository;

class ApiPersonneController extends AbstractController


{
H I ©
EL
/**
* @Route("/api/personne", name="api_personne")
*/ O U
{
f E LM
public function index(PersonneRepository $personneRepository)

ch r e
$personnes = $personneRepository->findAll();

©A
$json = json_encode($personnes);
return $json;
}
}

H & H: Research and Training 12 / 75


Contrôleur avec réponse JSON

Pour retourner les tuples de la table Personne sous format JSON


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\PersonneRepository;

class ApiPersonneController extends AbstractController


{
H I ©
EL
/**
* @Route("/api/personne", name="api_personne")
*/ O U
{
f E LM
public function index(PersonneRepository $personneRepository)

ch r e
$personnes = $personneRepository->findAll();

©A
$json = json_encode($personnes);
return $json;
}
}

Erreur

The controller must return a Symfony\Component\HttpFoundation\Response


object but it returned a string

H & H: Research and Training 12 / 75


Contrôleur avec réponse JSON

Pour retourner une réponse HTTP contenant les tuples sous format JSON
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
use App\Repository\PersonneRepository;

class ApiPersonneController extends AbstractController


H I ©
EL
{
/**
* @Route("/api/personne", name="api_personne") O U
*/
public function index()
f E LM
{
ch r e
©A
$personnes = $personneRepository->findAll();
$json = json_encode($personnes);
$reponse = new Response($json, 200, [
’content-type’ => ’application/json’
]);
return $reponse;
}
}

H & H: Research and Training 13 / 75


Contrôleur avec réponse JSON

Pour retourner une réponse HTTP contenant les tuples sous format JSON
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
use App\Repository\PersonneRepository;

class ApiPersonneController extends AbstractController


H I ©
EL
{
/**
* @Route("/api/personne", name="api_personne") O U
*/
public function index()
f E LM
{
ch r e
©A
$personnes = $personneRepository->findAll();
$json = json_encode($personnes);
$reponse = new Response($json, 200, [
’content-type’ => ’application/json’
]);
return $reponse;
}
}

Objets JSON vides car les attributs sont privés.


H & H: Research and Training 13 / 75
Contrôleur avec réponse JSON NormalizerInterface

Symfony
Solution ⇒ normaliser ou serializer les objets. Pour le faire

Installez le bundle de serialisation

Configurez config/services.yaml

H I ©
U EL
O
f E LM
ch r e
©A

H & H: Research and Training 14 / 75


Contrôleur avec réponse JSON NormalizerInterface

Symfony
Solution ⇒ normaliser ou serializer les objets. Pour le faire

Installez le bundle de serialisation

Configurez config/services.yaml

H I ©
U EL
Pour installer le bundle de serialisation
O
composer require symfony/serializer
f E LM
ch r e
©A

H & H: Research and Training 14 / 75


Contrôleur avec réponse JSON NormalizerInterface

Symfony
Solution ⇒ normaliser ou serializer les objets. Pour le faire

Installez le bundle de serialisation

Configurez config/services.yaml

H I ©
U EL
Pour installer le bundle de serialisation
O
composer require symfony/serializer
f E LM
ch r e
©A
Dans services.yaml, ajoutez le code suivant pour qu’on puisse utiliser les
getters/setters pour accéder aux attributs privés
services:
get_set_method_normalizer:
class: Symfony\Component\Serializer\Normalizer\
GetSetMethodNormalizer
tags: [serializer.normalizer]

H & H: Research and Training 14 / 75


Contrôleur avec réponse JSON NormalizerInterface

Symfony

H I ©
U EL
O
f E LM
ch r e
©A

Image de la documentation officielle de Symfony


H & H: Research and Training 15 / 75
Contrôleur avec réponse JSON NormalizerInterface

Pour retourner une réponse HTTP contenant les tuples sous format JSON
namespace App\Controller;

use App\Repository\PersonneRepository;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

H I ©
EL
class ApiPersonneController extends AbstractController
{
/**
O U
*/
f E LM
* @Route("/api/personne", name="api_personne", methods="GET")

ch r e
public function index(PersonneRepository $personneRepository,

©A
NormalizerInterface $normalizer)
{
$personnes = $personneRepository->findAll();
$normalized = $normalizer->normalize($personnes);
$json = json_encode($normalized);
return new Response($json, 200, [
’content-type’ => ’application/json’
]);
}
}
H & H: Research and Training 16 / 75
Contrôleur avec réponse JSON NormalizerInterface

Symfony
En renvoyant la requête HTTP depuis POSTMAN
A circular reference has been detected when
serializing the object of class
\"App\\Entity\\Personne\"
H I ©
U EL
O
f E LM
ch r e
©A

H & H: Research and Training 17 / 75


Contrôleur avec réponse JSON NormalizerInterface

Symfony
En renvoyant la requête HTTP depuis POSTMAN
A circular reference has been detected when
serializing the object of class
\"App\\Entity\\Personne\"
H I ©
U EL
O
f E LM
ch r e
©A

H & H: Research and Training 17 / 75


Contrôleur avec réponse JSON @Groups
Pour résoudre ce problème, on doit créer des groupes dans l’entité Personne

class Personne
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups("personne:read")
*/
private $id;

/**

H I ©
EL
* @ORM\Column(type="string", length=255, nullable=true)
* @Groups("personne:read")
*/
private $nom;
O U
/**

f E LM
* @Groups("personne:read")
*/
ch e
* @ORM\Column(type="string", length=255, nullable=true)

r
©A
private $prenom;

/**
* @ORM\ManyToMany(targetEntity=Adresse::class, inversedBy="personnes", cascade={"remove",
"persist"})
*/
private $adresses;

H & H: Research and Training 18 / 75


Contrôleur avec réponse JSON @Groups
Pour résoudre ce problème, on doit créer des groupes dans l’entité Personne

class Personne
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups("personne:read")
*/
private $id;

/**

H I ©
EL
* @ORM\Column(type="string", length=255, nullable=true)
* @Groups("personne:read")
*/
private $nom;
O U
/**

f E LM
* @Groups("personne:read")
*/
ch e
* @ORM\Column(type="string", length=255, nullable=true)

r
©A
private $prenom;

/**
* @ORM\ManyToMany(targetEntity=Adresse::class, inversedBy="personnes", cascade={"remove",
"persist"})
*/
private $adresses;

Le use nécessaire

use Symfony\Component\Serializer\Annotation\Groups ;

H & H: Research and Training 18 / 75


Contrôleur avec réponse JSON @Groups

Symfony
En utilisant la méthode normalize, on indique le·s groupe·s à inclure dans la réponse

class ApiPersonneController extends AbstractController


{
/**
* @Route("/api/personne", name="api_personne", methods="GET")
*/
H I ©
EL
public function index(PersonneRepository $personneRepository,
NormalizerInterface $normalizer)
{
O U
$personnes = $personneRepository->findAll();

f E LM
$normalized = $normalizer->normalize($personnes, null, [

r e
’groups’ => ’personne:read’
ch
©A
]);

$json = json_encode($normalized);

return new Response($json, 200, [


’content-type’ => ’application/json’
]);
}
}

H & H: Research and Training 19 / 75


Contrôleur avec réponse JSON @Groups

Et si on voulait retourner pour chaque objet Personne sa liste adresses, on ajoute le groupe personne:read à
adresses dans Personne

class Personne
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups("personne:read")
*/
private $id;

H I ©
/**

U EL
O
* @ORM\Column(type="string", length=255, nullable=true)

LM
* @Groups("personne:read")
*/
private $nom;

r e f E
ch
/**

©A
* @ORM\Column(type="string", length=255, nullable=true)
* @Groups("personne:read")
*/
private $prenom;

/**
* @ORM\ManyToMany(targetEntity=Adresse::class, inversedBy="personnes", cascade={"remove",
"persist"})
* @Groups("personne:read")
*/
private $adresses;

H & H: Research and Training 20 / 75


Contrôleur avec réponse JSON @Groups
Et dans Adresse, on ajoute personne:read pour tous les attributs sauf personnes

class Adresse
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups("personne:read")
*/
private $id;

/**

H I ©
EL
* @ORM\Column(type="string", length=255, nullable=true)
* @Groups("personne:read")
*/
private $rue;
O U
/**

f E LM
* @Groups("personne:read")
*/
ch e
* @ORM\Column(type="string", length=255, nullable=true)

r
©A
private $codePostal;

/**
* @ORM\Column(type="string", length=255, nullable=true)
* @Groups("personne:read")
*/
private $ville;

/**
* @ORM\ManyToMany(targetEntity=Personne::class, mappedBy="adresses")
*/
private $personnes;

H & H: Research and Training 21 / 75


Contrôleur avec réponse JSON SerializerInterface

On peut aussi utiliser l’interface SerializerInterface qui normalise et encode en JSON


namespace App\Controller;

use App\Repository\PersonneRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

H I ©
EL
class ApiPersonneController extends AbstractController
{
/**
O U
*/
f E LM
* @Route("/api/personne", name="api_personne", methods="GET")

ch r e
public function index(PersonneRepository $personneRepository,

©A
SerializerInterface $serializer)
{
$personnes = $personneRepository->findAll();
$json = $serializer->serialize($personnes, ’json’, [’groups’ =>
’personne:read’]);
return new Response($json, 200, [
’content-type’ => ’application/json’
]);
}
}
H & H: Research and Training 22 / 75
Contrôleur avec réponse JSON JsonResponse

Pour la réponse, on peut simplifier le code en utilisant un objet JsonResponse

namespace App\Controller;

use App\Repository\PersonneRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

H I ©
EL
class ApiPersonneController extends AbstractController
{
O U
LM
/**
* @Route("/api/personne", name="api_personne", methods="GET")
*/
r e f E
ch
public function index(PersonneRepository $personneRepository,

©A
SerializerInterface $serializer)
{
$personnes = $personneRepository->findAll();
$json = $serializer->serialize($personnes, ’json’, [
’groups’ => ’personne:read’
]);
return new JsonResponse($json, 200, [], true);
}
}

H & H: Research and Training 23 / 75


Contrôleur avec réponse JSON La méthode json

Symfony
On peut aussi simplifier l’écriture en utilisant la méthode json de AbstractController
qui retourne un objet JsonResponse

namespace App\Controller;

use App\Repository\PersonneRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
H I ©
EL
use Symfony\Component\Routing\Annotation\Route;

class ApiPersonneController extends AbstractController


O U
{
/**
f E LM
r e
* @Route("/api/personne", name="api_personne", methods="GET")
ch
©A
*/
public function index(PersonneRepository $personneRepository)
{
$personnes = $personneRepository->findAll();
return $this->json($personnes, 200, [], [
’groups’ => ’personne:read’
]);
}
}

H & H: Research and Training 24 / 75


Contrôleur avec réponse JSON La méthode json

Symfony
Pour ajouter une nouvelle personne

/**
* @Route("/api/personne", name="api_personne_add", methods="POST")
*/
public function add(EntityManagerInterface $entityManager, Request $request,
SerializerInterface $serializer, ValidatorInterface $validator) {

$contenu = $request->getContent();
try {
H I ©
EL
$personne = $serializer->deserialize($contenu, Personne::class, ’json’);

$errors = $validator->validate($personne);
O U
LM
if (count($errors) > 0) {
return $this->json($errors, 400);
}

r e f E
ch
$entityManager->persist($personne);

©A
$entityManager->flush();
return $this->json($personne, 201, [], [
’groups’ => ’personne:read’
]);

} catch (NotEncodableValueException $e) {


return $this->json([
’status’ => 400,
’message’ => $e->getMessage()
]);
}
}

H & H: Research and Training 25 / 75


Contrôleur avec réponse JSON La méthode json

Symfony

Les use nécessaires


use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
H I ©
use Symfony\Component\Serializer\Exception\
NotEncodableValueException; U EL
O
use Symfony\Component\Validator\Validator\
ValidatorInterface;
f E LM
ch r e
©A

H & H: Research and Training 26 / 75


Contrôleur avec réponse JSON La méthode json

Symfony

Les use nécessaires


use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
H I ©
use Symfony\Component\Serializer\Exception\
NotEncodableValueException; U EL
O
use Symfony\Component\Validator\Validator\
ValidatorInterface;
f E LM
ch r e
©A
Pensez à installer le Validator
composer require symfony/validator

H & H: Research and Training 26 / 75


Contrôleur avec réponse JSON La méthode json

Symfony
Pour tester avec POSTMAN

Dans la liste déroulante, choisir POST puis saisir l’url vers notre web
service http://localhost:8000/api/personne

Dans le Headers, saisir Content-Type comme Key et


H I ©
application/json comme Value
U EL
O
Ensuite cliquer sur Body, cocher raw, choisir JSON
f E LM
(application/json) et saisir des données sous format json

ch r e
correspondant à l’objet personne à ajouter

{ ©A
"nom": "Morena",
"prenom": "Andreas"
}

Cliquer sur Send

H & H: Research and Training 27 / 75


Contrôleur avec réponse JSON La méthode json

Symfony

H I ©
Exercice 1
U EL
O
LM
Créez puis testez, avec POSTMAN, les deux méthodes HTTP put et

r e f E
delete qui permettront de modifier ou supprimer une personne.

ch
©A

H & H: Research and Training 28 / 75


API Platform

Symfony
API Platform
Framework PHP open-source
Créé par Kévin Dunglas et distribué par Symfony

H I ©
Permettant de générer des services REST pour des entités
Doctrine
UEL
O
Utilisant Swagger UI
f E LM
r e
(https://swagger.io/tools/swagger-ui/) pour visualiser
ch
©A
et interagir avec les API REST

H & H: Research and Training 29 / 75


API Platform

Symfony
API Platform
Framework PHP open-source
Créé par Kévin Dunglas et distribué par Symfony

H I ©
Permettant de générer des services REST pour des entités
Doctrine
UEL
O
Utilisant Swagger UI
f E LM
r e
(https://swagger.io/tools/swagger-ui/) pour visualiser
ch
©A
et interagir avec les API REST

Installation avec Flex


composer require api

H & H: Research and Training 29 / 75


API Platform

Symfony
Contenu de config/routes/api platform.yaml
api_platform:
resource: .
type: api_platform
prefix: /api

H I ©
UEL
O
f E LM
ch r e
©A

H & H: Research and Training 30 / 75


API Platform

Symfony
Contenu de config/routes/api platform.yaml
api_platform:
resource: .
type: api_platform
prefix: /api

H I ©
U EL
O
/ws
f E LM
Pour éviter le conflit avec nos travaux précédents, remplaçons prefix: /api par prefix:

ch r e
©A

H & H: Research and Training 30 / 75


API Platform

Symfony
Contenu de config/routes/api platform.yaml
api_platform:
resource: .
type: api_platform
prefix: /api

H I ©
U EL
O
/ws
f E LM
Pour éviter le conflit avec nos travaux précédents, remplaçons prefix: /api par prefix:

ch r e
©A
Commentez ou supprimez les lignes ajoutées précédemment dans
config/serices.yaml
# get_set_method_normalizer:
# class: Symfony\Component\Serializer\Normalizer\
GetSetMethodNormalizer
# tags: [serializer.normalizer]

H & H: Research and Training 30 / 75


API Platform @ApiResource

Symfony
Ajoutons l’annotation @ApiResource à notre entité Personne
namespace App\Entity;

use App\Repository\PersonneRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
H I ©
EL
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
O U
LM
use ApiPlatform\Core\Annotation\ApiResource;

/**
r e f E
ch
©A
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource
*/
class Personne
{

H & H: Research and Training 31 / 75


API Platform @ApiResource

Symfony
Ajoutons l’annotation @ApiResource à notre entité Personne
namespace App\Entity;

use App\Repository\PersonneRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
H I ©
EL
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
O U
LM
use ApiPlatform\Core\Annotation\ApiResource;

/**
r e f E
ch
©A
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource
*/
class Personne
{

La même chose pour l’entité Adresse.

H & H: Research and Training 31 / 75


API Platform @ApiResource

Symfony

H I ©
Pour tester
U EL
O
Allez sur localhost:8000/ws
f E LM
ch r e
©A

H & H: Research and Training 32 / 75


API Platform Collection et item operations

Symfony

Deux types d’opérations

Collection operations :
GET : /ws/personnes
H I ©
POST : /ws/personnes
U EL
O
Item operations :
f E LM
ch r e
A
GET : /ws/personnes/{id}
© : /ws/personnes/{id}
DELETE
PUT : /ws/personnes/{id}
PATCH : /ws/personnes/{id}

H & H: Research and Training 33 / 75


API Platform Collection et item operations

Symfony
Et si nous voulions exposer seulement quelques méthodes

/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
* collectionOperations={"get"},
* itemOperations={"get", "put", "delete"}
H I ©
EL
* )
*/
class Personne
O U
f E LM
ch r e
©A

H & H: Research and Training 34 / 75


API Platform Collection et item operations

Symfony
Et si nous voulions exposer seulement quelques méthodes

/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
* collectionOperations={"get"},
* itemOperations={"get", "put", "delete"}
H I ©
EL
* )
*/
class Personne
O U
f E LM
Les méthodes disponibles
ch r e
©A
GET : /ws/personnes

GET : /ws/personnes/{id}

DELETE : /ws/personnes/{id}

PUT : /ws/personnes/{id}

H & H: Research and Training 34 / 75


API Platform Collection et item operations

Symfony
Il est possible de modifier le path d’une méthode et le code de la
réponse HTTP
/**
* @ORM\Entity(repositoryClass=PersonneRepository::
class)
H I ©
* @ApiResource(
U EL
collectionOperations={ O
LM
*
*
r e f E
"get"={"path"="/person", "status"=301}

ch
* },

©A
* itemOperations={"get", "put", "delete"}
* )
*/
class Personne

H & H: Research and Training 35 / 75


API Platform Collection et item operations

Symfony
Il est possible de modifier le path d’une méthode et le code de la
réponse HTTP
/**
* @ORM\Entity(repositoryClass=PersonneRepository::
class)
H I ©
* @ApiResource(
U EL
collectionOperations={ O
LM
*
*
r e f E
"get"={"path"="/person", "status"=301}

ch
* },

©A
* itemOperations={"get", "put", "delete"}
* )
*/
class Personne

Pour tester localhost:8000/ws/person

H & H: Research and Training 35 / 75


API Platform routePrefix

Symfony
Pour modifier de préfixer le chemin de toutes les routes d’une
ressource
/**
* @ORM\Entity(repositoryClass=PersonneRepository::
class)
H I ©
* @ApiResource(
U EL
routePrefix="/library"
O
* collectionOperations={
f E LM
r e
"get"={"path"="/person", "status"=301}
ch
©A
},
* itemOperations={"get", "put", "delete"}
* )
*/
class Personne

H & H: Research and Training 36 / 75


API Platform routePrefix

Symfony
Pour modifier de préfixer le chemin de toutes les routes d’une
ressource
/**
* @ORM\Entity(repositoryClass=PersonneRepository::
class)
H I ©
* @ApiResource(
U EL
routePrefix="/library"
O
* collectionOperations={
f E LM
r e
"get"={"path"="/person", "status"=301}
ch
©A
},
* itemOperations={"get", "put", "delete"}
* )
*/
class Personne

Pour tester localhost:8000/ws/library/person


H & H: Research and Training 36 / 75
API Platform Pagination

Symfony
Pour définir le nombre d’objets à retourner par requête (page)
/**
* @ORM\Entity(repositoryClass=PersonneRepository::
class)
* @ApiResource(
H I ©
* attributes={"pagination_items_per_page"=3}
UEL
collectionOperations={ O
LM
*
*
r e f E
"get"={"path"="/person", "status"=301}

ch
* },

©A
* itemOperations={"get", "put", "delete"}
* )
*/
class Personne

H & H: Research and Training 37 / 75


API Platform Pagination

Symfony
Pour définir le nombre d’objets à retourner par requête (page)
/**
* @ORM\Entity(repositoryClass=PersonneRepository::
class)
* @ApiResource(
H I ©
* attributes={"pagination_items_per_page"=3}
UEL
collectionOperations={ O
LM
*
*
r e f E
"get"={"path"="/person", "status"=301}

ch
* },

©A
* itemOperations={"get", "put", "delete"}
* )
*/
class Personne

Pour tester localhost:8000/ws/person?page=1

H & H: Research and Training 37 / 75


API Platform normalizationContext et denormalizationContext

Symfony

Objectif

H I ©
EL
Ajouter un attribut dateEnregistrement qui correspond à la

OU
date d’insertion de la personne dans la base de données
M
E
La valeur de ce champs ne
f L pas être renseigné par l’utilisateur
doit
On utilise doncc
e
hrGroups et les deux attributs
© A les
normalizationContext et denormalizationContext

H & H: Research and Training 38 / 75


API Platform normalizationContext et denormalizationContext

Nouveau contenu après avoir ajouté l’attribut dateEnregistrement et le groupe personne:write

class Personne
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"personne:read", "personne:write"})
*/
private $id;
/**
* @ORM\Column(type="string", length=255, nullable=true)
H I ©
* @Groups({"personne:read", "personne:write"})
*/
U EL
private $nom;
O
LM
/**

E
* @ORM\Column(type="string", length=255, nullable=true)

f
* @Groups({"personne:read", "personne:write"})

r e
ch
*/
private $prenom;

©A
/**
* @ORM\ManyToMany(targetEntity=Adresse::class, inversedBy="personnes", cascade={"remove",
"persist"})
* @Groups({"personne:read", "personne:write"})
* /
private $adresses;
/**
* @ORM\Column(type="datetime")
* @Groups("personne:read")
*/
private $dateEnregistrement;

H & H: Research and Training 39 / 75


API Platform normalizationContext et denormalizationContext

Ajoutons les attributs suivants à l’annotation @ApiResource

/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
* itemOperations={"get", "put", "delete"},
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
*/
H I ©
class Personne
U EL
O
f E LM
ch r e
©A

H & H: Research and Training 40 / 75


API Platform normalizationContext et denormalizationContext

Ajoutons les attributs suivants à l’annotation @ApiResource

/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
* itemOperations={"get", "put", "delete"},
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
*/
H I ©
class Personne
U EL
O
f E LM
Explication
ch r e
©A
Les attributs ayant le groupe "personne:read" seront accessibles en lecture (GET)

Les attributs ayant le groupe "personne:write" seront accessibles en mode écriture


(POST, PUT et DELETE)

Les attributs ayant les deux groupes seront accessibles en lecture et écriture

Les attributs n’ayant aucun groupe ne seront pas pris en compte

H & H: Research and Training 40 / 75


API Platform normalizationContext et denormalizationContext
Nouveau contenu de l’entité Adresse

/**
* @ORM\Entity(repositoryClass=AdresseRepository::class)
* @ApiResource()
*/
class Adresse
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"personne:read", "personne:write"})

H I ©
EL
*/
private $id;
/**

O
* @ORM\Column(type="string", length=255, nullable=true) U
*/

f E LM
* @Groups({"personne:read", "personne:write"})

private $rue;
/**

ch r e
* @ORM\Column(type="string", length=255, nullable=true)

©A
* @Groups({"personne:read", "personne:write"})
*/
private $codePostal;
/**
* @ORM\Column(type="string", length=255, nullable=true)
* @Groups({"personne:read", "personne:write"})
*/
private $ville;
/**
* @ORM\ManyToMany(targetEntity=Personne::class, mappedBy="adresses")
*/
private $personnes;

H & H: Research and Training 41 / 75


API Platform normalizationContext et denormalizationContext

Symfony

Remarques
En essayant d’ajouter une nouvelle Personne, un message
d’erreur s’affiche.
H I ©
U ELn’a pas été
En effet, la valeur de dateEnregistrement
MO pour MySQL.
renseignée et elle n’est pas nullable
L
r e f E
A ch
©

H & H: Research and Training 42 / 75


API Platform normalizationContext et denormalizationContext

Symfony

Remarques
En essayant d’ajouter une nouvelle Personne, un message
d’erreur s’affiche.
H I ©
U ELn’a pas été
En effet, la valeur de dateEnregistrement
MO pour MySQL.
renseignée et elle n’est pas nullable
L
r e f E
A ch
©
Solution
Utiliser DataPersister

H & H: Research and Training 42 / 75


API Platform DataPersisterInterface

Symfony

Explication

On crée une classe PersonneDataPersister qui implémente


H I ©
l’interface DataPersisterInterface
U EL
O
LM
DataPersisterInterface a trois méthodes :

r e f E
ch
supports : vérifie si le persister supporte l’entité
A
©
persist : s’exécute à chaque opération de type POST ou PUT
remove : s’exécute seulement pour l’opération DELETE

H & H: Research and Training 43 / 75


API Platform DataPersisterInterface
Contenu de la classe PersonneDataPersister définie dans App/DataPersister

namespace App\DataPersister;

use App\Entity\Personne;
use Doctrine\ORM\EntityManagerInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;

class PersonneDataPersister implements DataPersisterInterface


{
private $entityManager;

public function __construct(EntityManagerInterface $entityManager)

H I ©
EL
{
$this->entityManager = $entityManager;
}

O
public function supports($data, array $context = []): bool U
LM
{

r e E
return $data instanceof Personne;

f
ch
public function persist($data, array $context = [])
{

©A
$dateTimeZone = new \DateTimeZone(’Europe/Paris’);
$data->setDateEnregistrement(new \DateTime(’now’, $dateTimeZone));
$this->entityManager->persist($data);
$this->entityManager->flush();
}

public function remove($data, array $context = [])


{
$this->entityManager->remove($data);
$this->entityManager->flush();
}
}

H & H: Research and Training 44 / 75


API Platform DataPersisterInterface

Symfony

H I ©
EL
Remarque

O U
LM
En faisant la modification d’une personne existante,

r e f E
dateEnregistrement sera aussi mise à jour

ch
©A

H & H: Research and Training 45 / 75


API Platform DataPersisterInterface

Symfony
Pour affecter une valeur à dateEnregistrement seulement en cas d’ajout
(POST), on ajoute le test suivant
public function persist($data, array $context = [])
{
if (($context[’collection_operation_name’] ?? null) === ’
post’) {
H I ©
EL
$dateTimeZone = new \DateTimeZone(’Europe/Paris’);
$data->setDateEnregistrement(new \DateTime(’now’,
O U
LM
$dateTimeZone));
}

r e f E
$this->entityManager->persist($data);
ch
©A
$this->entityManager->flush();
}

H & H: Research and Training 46 / 75


API Platform DataPersisterInterface

Symfony
Pour affecter une valeur à dateEnregistrement seulement en cas d’ajout
(POST), on ajoute le test suivant
public function persist($data, array $context = [])
{
if (($context[’collection_operation_name’] ?? null) === ’
post’) {
H I ©
EL
$dateTimeZone = new \DateTimeZone(’Europe/Paris’);
$data->setDateEnregistrement(new \DateTime(’now’,
O U
LM
$dateTimeZone));
}

r e f E
$this->entityManager->persist($data);
ch
©A
$this->entityManager->flush();
}

Remarque

collection operation name pour Collection operations et


item operation name pour Item operations

H & H: Research and Training 46 / 75


API Platform @ApiSubresource

Ajoutons l’annotation @ApiSubresource à l’attribut adresses pour récupérer la liste d’adresses d’une personne dont
l’id est passé dans la route

namespace App\Entity;

use App\Repository\PersonneRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

©
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;

H I
/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
U EL
* @ApiResource
O
LM
*/
class Personne
{

r e f E
ch
// contenu précédent
/**

©A
* @ORM\ManyToMany(targetEntity=Adresse::class, inversedBy="personnes", cascade={"persist
"})
* @Groups({"personne:read", "personne:write"})
* @ApiSubresource
*/
private $adresses;

H & H: Research and Training 47 / 75


API Platform @ApiSubresource

Ajoutons l’annotation @ApiSubresource à l’attribut adresses pour récupérer la liste d’adresses d’une personne dont
l’id est passé dans la route

namespace App\Entity;

use App\Repository\PersonneRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

©
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;

H I
/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
U EL
* @ApiResource
O
LM
*/
class Personne
{

r e f E
ch
// contenu précédent
/**

©A
* @ORM\ManyToMany(targetEntity=Adresse::class, inversedBy="personnes", cascade={"persist
"})
* @Groups({"personne:read", "personne:write"})
* @ApiSubresource
*/
private $adresses;

Ainsi on a une nouvelle route pour le verbe GET : /api/personnes/{id}/adresses

H & H: Research and Training 47 / 75


API Platform @ApiFilter

@ApiFilter

permet de filtrer le résultat de recherche

peut concerner une entité ou un attribut

peut utiliser plusieurs classes de filtre

I ©
SearchFilter : principalement pour les champs de type chaı̂ne
H
de caractères
EL
M OU
DateFilter : pour les champs de type date

f E L
NumericFilter et RangeFilter : pour les champs de type
numérique hr
c e
... © A

H & H: Research and Training 48 / 75


API Platform @ApiFilter

@ApiFilter

permet de filtrer le résultat de recherche

peut concerner une entité ou un attribut

peut utiliser plusieurs classes de filtre

I ©
SearchFilter : principalement pour les champs de type chaı̂ne
H
de caractères
EL
M OU
DateFilter : pour les champs de type date

f E L
NumericFilter et RangeFilter : pour les champs de type
numérique hr
c e
... © A

Liste complète

https://api-platform.com/docs/core/filters/

H & H: Research and Training 48 / 75


API Platform @ApiFilter

Ajoutons l’annotation @ApiFilter à l’entité Adresse pour effectuer une recherche selon
la ville (par exemple)

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use App\Repository\AdresseRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
H I ©
EL
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use
O
Symfony\Component\Serializer\Annotation\Groups; U
use
LM
ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

f E
/**
ch r e
* @ORM\Entity(repositoryClass=AdresseRepository::class)
* @ApiResource
©A
* @ApiFilter(SearchFilter::class, properties={"ville": "partial"})
*/
class Adresse

H & H: Research and Training 49 / 75


API Platform @ApiFilter

Ajoutons l’annotation @ApiFilter à l’entité Adresse pour effectuer une recherche selon
la ville (par exemple)

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use App\Repository\AdresseRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
H I ©
EL
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use
O
Symfony\Component\Serializer\Annotation\Groups; U
use
LM
ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

f E
/**
ch r e
* @ORM\Entity(repositoryClass=AdresseRepository::class)
* @ApiResource
©A
* @ApiFilter(SearchFilter::class, properties={"ville": "partial"})
*/
class Adresse

Exemple de route pour le test : /api/adresses?ville=mars

H & H: Research and Training 49 / 75


API Platform @ApiFilter

Symfony

Valeurs possibles de properties de SearchFilter


partial ≡ ville like %texte%
exact ≡ ville = texte
H I ©
U EL
end ≡ ville like %texte
M O
f E L
start ≡ ville like texte%
ch r e
© A

H & H: Research and Training 50 / 75


API Platform @ApiFilter

Symfony

Valeurs possibles de properties de SearchFilter


partial ≡ ville like %texte%
exact ≡ ville = texte
H I ©
U EL
end ≡ ville like %texte
M O
f E L
start ≡ ville like texte%
ch r e
© A
Il est possible aussi d’effectuer la recherche à partir de la ressource
Personne : /api/personnes/9/adresses?ville=mars

H & H: Research and Training 50 / 75


API Platform @ApiFilter

On peut aussi définir des contraintes de recherche sur les nombres en utilisant
RangeFilter

use Doctrine\ORM\Mapping as ORM;


use App\Repository\AdresseRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
H I ©
EL
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter;
use
O U
ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

/**
f E LM
r e
* @ORM\Entity(repositoryClass=AdresseRepository::class)
* @ApiResource
ch
©A
* @ApiFilter(SearchFilter::class, properties={"ville": "partial"})
* @ApiFilter(RangeFilter::class, properties={"codePostal"})
*/
class Adresse

H & H: Research and Training 51 / 75


API Platform @ApiFilter

On peut aussi définir des contraintes de recherche sur les nombres en utilisant
RangeFilter

use Doctrine\ORM\Mapping as ORM;


use App\Repository\AdresseRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
H I ©
EL
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter;
use
O U
ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

/**
f E LM
r e
* @ORM\Entity(repositoryClass=AdresseRepository::class)
* @ApiResource
ch
©A
* @ApiFilter(SearchFilter::class, properties={"ville": "partial"})
* @ApiFilter(RangeFilter::class, properties={"codePostal"})
*/
class Adresse

Exemple de route pour le test : /api/adresses?ville=mars&codePostal[gt]=13006

H & H: Research and Training 51 / 75


API Platform @ApiFilter

Symfony

Valeurs possibles de properties de RangeFilter

gt ≡ supérieur à
H I ©
lt ≡ inférieur à
U EL
O
gte ≡ supérieur ou égal à
f E LM
ch r
lte ≡ inférieur ou égal àe
©A
between ≡ entre (Exemple :
codePostal[between]=13000..13016)

H & H: Research and Training 52 / 75


API Platform @ApiFilter

On peut aussi définir des contraintes sur les entités imbriquées (inverses)
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use App\Repository\PersonneRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
H I ©
EL
use Doctrine\Common\Collections\ArrayCollection;
use
use
Symfony\Component\Serializer\Annotation\Groups;
O U
ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

/**
f E LM
ch r e
* @ORM\Entity(repositoryClass=PersonneRepository::class)

©A
* @ApiResource(
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
* @ApiFilter(SearchFilter::class, properties={"adresses": "exact"})
*/
class Personne

H & H: Research and Training 53 / 75


API Platform @ApiFilter

On peut aussi définir des contraintes sur les entités imbriquées (inverses)
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use App\Repository\PersonneRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
H I ©
EL
use Doctrine\Common\Collections\ArrayCollection;
use
use
Symfony\Component\Serializer\Annotation\Groups;
O U
ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

/**
f E LM
ch r e
* @ORM\Entity(repositoryClass=PersonneRepository::class)

©A
* @ApiResource(
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
* @ApiFilter(SearchFilter::class, properties={"adresses": "exact"})
*/
class Personne

Pour récupérer les personnes qui ont l’adresse avec un id = 49 :


/api/personnes?adresses=49
H & H: Research and Training 53 / 75
API Platform @ApiFilter

On peut aussi chercher selon un attribut dans l’entité inverse autre que l’identifiant
use Doctrine\ORM\Mapping as ORM;
use App\Repository\PersonneRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
H I ©
EL
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

O U
LM
/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
r e f E
ch
* normalizationContext={"groups"={"personne:read"}},

©A
* denormalizationContext={"groups"={"personne:write"}}
* )
* @ApiFilter(SearchFilter::class, properties={"adresses.ville": "
partial"})
*/
class Personne

H & H: Research and Training 54 / 75


API Platform @ApiFilter

On peut aussi chercher selon un attribut dans l’entité inverse autre que l’identifiant
use Doctrine\ORM\Mapping as ORM;
use App\Repository\PersonneRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
H I ©
EL
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

O U
LM
/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
r e f E
ch
* normalizationContext={"groups"={"personne:read"}},

©A
* denormalizationContext={"groups"={"personne:write"}}
* )
* @ApiFilter(SearchFilter::class, properties={"adresses.ville": "
partial"})
*/
class Personne

Pour récupérer les personnes qui ont une adresse dont le nom de la ville contient mars :
/api/personnes?adresses.ville=mars

H & H: Research and Training 54 / 75


API Platform @ApiFilter

Symfony

Exercice 2
H I ©
Créez une application Angular qui permetU ELutilisateur, via des in-
à un
M O (ajout, modification, sup-
terfaces graphiques) la gestion de personnes
f L
E en utilisant les web services définis
pression, consultation et recherche)
par Symfony.
ch r e
© A

H & H: Research and Training 55 / 75


JWT et API Platform

Symfony

JWT : JSON Web Token

Librairie d’échange sécurisé d’informations

Utilisant des algorithmes de cryptage comme HMAC SHA256 ou RSA

Utilisant les jetons (tokens)


H I ©
EL
OU
Un jeton est composé de trois parties séparées par un point :
M
E
entête (header) : objet JSON
f L décrivant le jeton encodé en base 64
e
cé henrbase 64: objet JSON contenant les informations du
charge utile (payload)
A
jeton encod
©
Une signature numérique = concaténation de deux éléments
précédents séparés par un point + une clé secrète (le tout crypté
par l’algorithme spécifié dans l’entête)
Documentation officielle : https://jwt.io/introduction/

H & H: Research and Training 56 / 75


JWT et API Platform

Symfony

Entête : exemple
{
"alg": "HS256",
"typ": "JWT"
H I ©
}
UEL
O
f E LM
ch r e
©A

H & H: Research and Training 57 / 75


JWT et API Platform

Symfony

Entête : exemple
{
"alg": "HS256",
"typ": "JWT"
H I ©
}
UEL
O
f E LM
Charge utile : exemple
ch r e
{ © A
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

H & H: Research and Training 57 / 75


JWT et API Platform

Symfony
Exemple de construction de signature en utilisant l’algorithme précisé dans l’entête
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

H I ©
U EL
O
f E LM
ch r e
©A

H & H: Research and Training 58 / 75


JWT et API Platform

Symfony
Exemple de construction de signature en utilisant l’algorithme précisé dans l’entête
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

H I ©
U EL
Résultat
O
f E LM
ch r e
©A

H & H: Research and Training 58 / 75


JWT et API Platform

Symfony
Exemple complet (pour tester https://jwt.io/#debugger-io)

H I ©
UEL
O
f E LM
ch r e
©A

H & H: Research and Training 59 / 75


JWT et API Platform

Symfony

Plusieurs étapes
H I ©
va E
Préparation de la partie utilisateur (qui U
L
se connecter)
O
Préparation de la partie JWTL(clM
r e f E é publique, clé privée...)

ch
Gestion de rôles
A
©

H & H: Research and Training 60 / 75


JWT et API Platform

Symfony

H I ©
EL
Intégrons le bundle de sécurité dans notre projet
U
L MO
composer require symfony/security-bundle

r e f E
A ch
©

H & H: Research and Training 61 / 75


JWT et API Platform

Symfony
Pour créer la classe User

exécutez la commande php bin/console make:user

répondez à The name of the security user class par User

I ©
répondez à Do you want to store user data in the database (via
H
EL
Doctrine)? par yes

O U
répondez à Enter a property name that will be the unique "display"
name for the user par email

f E LM
r e
répondez à Does this app need to hash/check user passwords? par yes
ch
©A

H & H: Research and Training 62 / 75


JWT et API Platform

Symfony
Pour créer la classe User

exécutez la commande php bin/console make:user

répondez à The name of the security user class par User

I ©
répondez à Do you want to store user data in the database (via
H
EL
Doctrine)? par yes

O U
répondez à Enter a property name that will be the unique "display"
name for the user par email

f E LM
r e
répondez à Does this app need to hash/check user passwords? par yes
ch
©A
Le résultat est
created: src/Entity/User.php
created: src/Repository/UserRepository.php
updated: src/Entity/User.php
updated: config/packages/security.yaml

H & H: Research and Training 62 / 75


JWT et API Platform

Nouveau contenu de security.yaml

security:
encoders:
App\Entity\User:
algorithm: auto

# https://symfony.com/doc/current/security.html#where-do-users-come
-from-user-providers
providers:
H I ©
EL
# used to reload user from session & other features (e.g.
switch_user)
O U
LM
app_user_provider:
entity:

e f E
class: App\Entity\User
r
ch
property: email

©A
firewalls:
dev:
pattern: ˆ/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: lazy
provider: app_user_provider

access_control:

H & H: Research and Training 63 / 75


JWT et API Platform

Symfony

Pour créer la table User

exécutez la commande php bin/console make:migration

H I ©
et ensuite la commande php bin/console doctrine:migrations:migrate

UEL
O
f E LM
ch r e
©A

H & H: Research and Training 64 / 75


JWT et API Platform

Symfony

Pour créer la table User

exécutez la commande php bin/console make:migration

H I ©
et ensuite la commande php bin/console doctrine:migrations:migrate

UEL
O
f E LM
ch r e
Pour remplir la table User avec des données aléatoires

©A
installez le bundle de fixture composer require --dev
doctrine/doctrine-fixtures-bundle

demandez à ce bundle de créer une classe fixtures php bin/console


make:fixtures

répondez à The class name of the fixtures to create par UserFixtures

H & H: Research and Training 64 / 75


JWT et API Platform

Symfony
Contenu généré pour UserFixtures

namespace App\DataFixtures;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
H I ©
class UserFixtures extends Fixture
UEL
O
LM
{

r e f E
public function load(ObjectManager $manager)
ch
©A
{
// $product = new Product();
// $manager->persist($product);

$manager->flush();
}
}

H & H: Research and Training 65 / 75


JWT et API Platform

Symfony
Nouveau contenu de UserFixtures

class UserFixtures extends Fixture


{
private $passwordEncoder;

public function __construct(UserPasswordEncoderInterface $passwordEncoder)


{

}
$this->passwordEncoder = $passwordEncoder;

H I ©
EL
public function load(ObjectManager $manager)

U
{
$user = new User();
O
LM
$user->setEmail(’john@wick.us’);
$user->setRoles([’ROLE_ADMIN’]);

r
$manager->persist($user);
e f E
$user->setPassword($this->passwordEncoder->encodePassword($user, ’wick’));

ch
$user2 = new User();

©A
$user2->setEmail(’jack@dalton.us’);
$user2->setPassword($this->passwordEncoder->encodePassword($user2, ’dalton’));
$manager->persist($user2);
$manager->flush();
}
}

H & H: Research and Training 66 / 75


JWT et API Platform

Symfony
Nouveau contenu de UserFixtures

class UserFixtures extends Fixture


{
private $passwordEncoder;

public function __construct(UserPasswordEncoderInterface $passwordEncoder)


{

}
$this->passwordEncoder = $passwordEncoder;

H I ©
EL
public function load(ObjectManager $manager)

U
{
$user = new User();
O
LM
$user->setEmail(’john@wick.us’);
$user->setRoles([’ROLE_ADMIN’]);

r
$manager->persist($user);
e f E
$user->setPassword($this->passwordEncoder->encodePassword($user, ’wick’));

ch
$user2 = new User();

©A
$user2->setEmail(’jack@dalton.us’);
$user2->setPassword($this->passwordEncoder->encodePassword($user2, ’dalton’));
$manager->persist($user2);
$manager->flush();
}
}

Les use nécessaires

use App\Entity\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

H & H: Research and Training 66 / 75


JWT et API Platform

Symfony

H I ©
EL
Pour insérer les utilisateurs dans la base de données, exécutez
U
O
LM
php bin/console doctrine:fixtures:load ou php
bin/console d:f:l
r e f E
ch
©A

H & H: Research and Training 67 / 75


JWT et API Platform

Symfony

H I ©
Intégrons le bundle JWT dans notre projet
UEL
O
f E LM
composer require lexik/jwt-authentication-bundle

ch r e
©A

H & H: Research and Training 68 / 75


JWT et API Platform

Symfony

Pour créer les clés

exécutez mkdir config/jwt (pour créer un répertoire jwt dans config)

exécutez openssl genrsa -out config/jwt/private.pem -aes256 4096 pour


générer une clé privée (n’oubliez pas le passphrase)
H I ©
U EL
exécutez openssl rsa -pubout -in config/jwt/private.pem -out
O
config/jwt/public.pem pour générer une clé public

f E LM
ch r e
©A

H & H: Research and Training 69 / 75


JWT et API Platform

Symfony

Pour créer les clés

exécutez mkdir config/jwt (pour créer un répertoire jwt dans config)

exécutez openssl genrsa -out config/jwt/private.pem -aes256 4096 pour


générer une clé privée (n’oubliez pas le passphrase)
H I ©
U EL
exécutez openssl rsa -pubout -in config/jwt/private.pem -out
O
config/jwt/public.pem pour générer une clé public

f E LM
ch r e
©A
Vérifiez le contenu suivant dans .env (remplacez passphrase par sa valeur)

###> lexik/jwt-authentication-bundle ###


JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=symfony
###< lexik/jwt-authentication-bundle ###

H & H: Research and Training 69 / 75


JWT et API Platform

Modifiez la section firewalls de security.yaml

security:

firewalls:
dev:
pattern: ˆ/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ˆ/ws/
stateless: true
anonymous: true
provider: app_user_provider
H I ©
EL
guard:

U
authenticators:

O
- lexik_jwt_authentication.jwt_token_authenticator

LM
main:
anonymous: true
json_login:

r e f E
check_path: /authentication_token

ch
username_path: email

©A
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:

- { path: ˆ/ws/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY } # Pour autoriser l’accès à


Swagger UI
- { path: ˆ/authentication_token, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ˆ/, roles: IS_AUTHENTICATED_FULLY }

H & H: Research and Training 70 / 75


JWT et API Platform

Symfony

Définissez la route /authentication token dans routes.yaml


H I ©
UEL
authentication_token:
O
path: /authentication_token
f E LM
methods: [’POST’]
ch r e
©A

H & H: Research and Training 71 / 75


JWT et API Platform

Symfony
Pour exiger le rôle ROLE ADMIN aux demandeurs de la ressource Personne

namespace App\Entity;

use App\Repository\PersonneRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
H I ©
EL
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
O U
LM
use ApiPlatform\Core\Annotation\ApiResource;

/**
r e f E
ch
* @ORM\Entity(repositoryClass=PersonneRepository::class)

©A
* @ApiResource(
* attributes={"security"="is_granted(’ROLE_ADMIN’)"},
* itemOperations={"get", "put", "delete"},
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
*/
class Personne

H & H: Research and Training 72 / 75


JWT et API Platform

Symfony
Pour obtenir le jeton avec POSTMAN

Dans la liste déroulante, choisir POST puis saisir l’url vers notre web
service http://localhost:8000/authentication_token

Dans le Headers, saisir Content-Type comme Key et


H I ©
application/json comme Value
UEL
O
f E LM
Ensuite cliquer sur Body, cocher raw, choisir JSON
(application/json) et saisir des données sous format JSON
ch r e
correspondant à l’objet personne à ajouter

{ ©A
"email": "wick@wick.us",
"password": "wick"
}

Cliquer sur Send puis copier le jeton

H & H: Research and Training 73 / 75


JWT et API Platform

Symfony

Pour obtenir la liste des personnes avec POSTMAN

Dans la liste déroulante, choisir GET puis saisir l’url vers notre web
service http://localhost:8000/ws/personnes H I ©
UEL
O
Dans le Headers, saisir Content-Type comme Key et
E
application/json comme Value
f LM
r e
Achet collercliquer
Dans Authorization,
Bearer©Token
sur Type et choisir raw, choisir
le token
Cliquer sur Send

H & H: Research and Training 74 / 75


JWT et API Platform

Symfony
Il est possible de sécuriser l’accès seulement à certaines méthodes (mais pas toutes)

/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
* collectionOperations={
* "get",
H I ©
EL
* "post"={"security"="is_granted(’ROLE_ADMIN’)"}
*
*
},
itemOperations={
O U
*
*
"get",

f E LM
"put"={"security"="is_granted(’ROLE_ADMIN’)"},
*
ch r e
"delete"={"security"="is_granted(’ROLE_ADMIN’)"}

©A
* },
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
*/
class Personne

H & H: Research and Training 75 / 75


JWT et API Platform

Symfony
Il est possible de sécuriser l’accès seulement à certaines méthodes (mais pas toutes)

/**
* @ORM\Entity(repositoryClass=PersonneRepository::class)
* @ApiResource(
* collectionOperations={
* "get",
H I ©
EL
* "post"={"security"="is_granted(’ROLE_ADMIN’)"}
*
*
},
itemOperations={
O U
*
*
"get",

f E LM
"put"={"security"="is_granted(’ROLE_ADMIN’)"},
*
ch r e
"delete"={"security"="is_granted(’ROLE_ADMIN’)"}

©A
* },
* normalizationContext={"groups"={"personne:read"}},
* denormalizationContext={"groups"={"personne:write"}}
* )
*/
class Personne

N’oublions pas de commenter le contenu de la section access control dans security.yaml

H & H: Research and Training 75 / 75

Vous aimerez peut-être aussi