# 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 ParamConverter est déclaré, et sera chargé de transformer l'identifiant id présent dans la requête en instance de Professional, dans le paramètre professional de 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ètre id dans 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 Security est déclaré, qui fait directement appel au système de Voters. Les Voters de l'application vont être déclenchés, le ProfessionalVoter va ê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 droit access sur ce Professional : ce mot clef access est 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.