# Gestion des requêtes
Le but de cette partie est de présenter comment les requêtes sont digérées par l'application et de comprendre la circulation globale de l'information au sein de l'application.
# Requête simple
Prenons l'exemple d'une requête simple.
Je souhaite trouver au maximum 10 établissements inscrits à Fort-de-France (id de la ville de Fort-de-France: 11).
Pour ce faire, je dispose de la route GET /public/professional, et des paramètre d'URL city=11 et limit=10.
TIP
Ici, nous avons déjà une première information dans le nom de la route : elle commence par /public, et est donc accessible sans utilisateur connecté, mais nécessite une clef API. Nous verrons par la suite comment gérer la connexion d'un utilisateur.
J'envoie donc une requête HTTP GET /public/professional?city=11&limit=10.
WARNING
Toutes les requêtes, sauf celles sur les endpoints /ext, doivent présenter une clef API valide dans leur header X-API-KEY.
# Compréhension de la requête
Le Controller configuré pour s'exécuter à cette requête est le Pub/ProfessionalController, et sa méthode listingAction.
Cette configuration est déclarée en décorateur de la route :
/**
* @Rest\Get("/professional.{_format}", defaults={"_format": "json"},
* requirements={
* "_format": "json|geojson",
* }
* )
*/
TIP
Le contrôleur étant dans le dossier Pub, il n'a pas besoin de déclarer le début de sa route (/public).
Puis le contrôleur, par le biais de sa classe parente AbstractCrudController, va chercher à construire le tableau de données correspondant à la requête entrante.
# Requêtage de la base de données
Au travers du système de filtres, on va construire la requête pour la base de données correspondant aux filtres demandés : la ville doit être Fort-de-France, et on veut au maximum 10 établissements.
Une fois la requête construire, on demande à Doctrine d'exécuter la requête en base de données, et Doctrine nous renvoie un tableau de maximum 10 Professional (entité "Établissement") dont l'adresse est à Fort-de-France.
Il ne reste plus qu'à renvoyer ce tableau.
# Sérialisation
Ce tableau de données va être renvoyé en JSON (format par défaut de la route).
L'application va donc chercher à transformer nos 10 établissements en JSON, ce qu'elle va faire par le biais de décorateurs déclarés sur les attributs de l'entité Professional.
Exemple : afin de déterminer si l'attribut name du Professional doit être sérialisé, le décorateur @JMS\Expose sera utilisé : si celui-ci est présent, l'attribut sera sérialisé.
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=100, nullable=false)
* @JMS\Expose
*/
private $name;
# Requête de modification d'une entité existante (PUT)
Je souhaite modifier l'établissement (Professional) d'identifiant 33 qui m'appartient.
Je dispose de la route PUT /pro/professional/13.
TIP
Ici, /pro remplace /public au début de la route : cela signifie que j'accède à des routes privées (utilisateur connecté requis), et plus spécifiquement des routes d'accès à des ressources liés à des fiches établissement.
Je souhaite modifier le nom de mon établissement, j'envoie donc le JSON suivant en corps de la requête :
{
"name": "Mon nouveau nom"
}
De plus, je dois envoyer un header supplémentaire, Authorization: Bearer {TOKEN}, avec mon token de connexion, afin de m'authentifier auprès de l'API.
TIP
Bien entendu, le header X-API-KEY est toujours d'actualité, cette route n'appartenant pas au scope /ext.
WARNING
L'API utilise un système d'authentification basé sur OAuth2, mais sans systèmme de scopes... Pour l'instant ! Un exemple de génération d'un token de connexion se situe sur cette page.
# Compréhension de la requête
Le contrôleur chargé de gérer cette requête est le Pro/ProfessionalController, et sa méthode editAction.
/**
* Update an owned Professional
*
* @Rest\Put("/professional/{id}", requirements={"id" = "\d+"})
* @ParamConverter("professional")
* @Security("is_granted('access', professional)")
*/
public function editAction(Request $request, Professional $professional) {
// ...
}
Ici, plusieurs choses à noter :
- Un décorateur
ParamConverterest déclaré, et sera chargé de transformer l'identifiantidprésent dans la requête en instance deProfessional, dans le paramètreprofessionalde la fonction associée. Ce fonctionnement fait un peu "automagically", mais il faut le voir comme un simple "sucre syntaxique élaboré" : on va chercher un paramètreiddans la requête (par défaut, mais configurable, c'est le cas sur d'autres routes), puis Doctrine se charge de nous rappatrier l'Entity du type du nom du paramètre demandé (ici,Professional), et renvoie une 404 en cas d'échec. - Un décorateur
Securityest déclaré, qui fait directement appel au système deVoters. LesVotersde l'application vont être déclenchés, leProfessionalVoterva être sélectionné et aura pour rôle de déterminer si la requête est autorisée. Il va pour cela utiliser le token de connexion de l'utilisateur pour déterminer si cet utilisateur a ou non le droitaccesssur ce Professional : ce mot clefaccessest utilisé régulièrement dans l'application pour déterminer l'accès ou non à une ressource.
Une fois ces étapes franchies, on va traiter le corps de la requête dans un formulaire.
# Formulaire
Le formulaire utilisé sera le ProfessionalType.
WARNING
Le terme de "formulaire" est ici un peu galvaudé : il s'agit de valider les données envoyées.
Dans ce formulaire, sont déclarés plusieurs choses :
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Professional::class,
// ...
]
);
}
Ceci déclare à Symfony que le formulaire est mappé sur un Professional.
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
//...
}
Le rôle de cette ligne de code est de déclarer à Symfony que le champ name fait partie du formulaire, et donc l'attribut name du Professional par extension.
Enfin, vu d'ici, on pourrait avoir l'impression que tous les noms sont valides. Hors, ce n'est pas le cas, et le moyen le plus élégant pour le déclarer est au niveau de l'entité Professional :
/**
* @var string
*
* @Assert\NotBlank()
* @Assert\Length(
* max = 100,
* )
*/
private $name;
name ne pourra donc pas être une chaîne vide, ou null, et aura une taille maximale de 100 caractères.
TIP
Si le builder n'avait pas déclaré ->add('name'), on aurait pu envoyer "name": null sans erreur sur cette route. En revanche, aucune modification du Professional n'aurait eu lieu, ni en base de données, ni dans l'exécution du code : le champ n'aurait simplement pas été mappé.
# Sauvegarde
Le Professional est sauvegardé en base de données : le changement de son nom est acté.
# Serialisation
Enfin, le Professional est renvoyé, et les étapes de sérialisation de celui-ci sont à nouveau invoquées.
Le champ name est déclaré à JMS via @JMS\Expose, et il sera donc renvoyé dans le Professional sérialisé, accompagné de tous les autres champs à sérialiser pour ce Professional.