TD2: Écriture d'API

Mise en place

Dans ce TD, nous nous baserons sur le code écrit dans le TD1. Maintenant que nous pouvons publier des messages en récupérant les coordonnées des adresses, nous allons exposer notre propre API, pour permettre aux utilisateurs de publier et de consulter les messages.

Ajout des dates

Avant d'aller plus loin, nous allons stocker les dates de création des messages. Lancez :

symfony console make:entity

Et ajoutez un champ date de type datetime (qui peut être null). Lancez :

symfony console doctrine:schema:update --force

Pour mettre à jour votre base de données. Ajoutez le constructeur suivant dans Message pour que la date initiale soit celle de création:

public function __construct()
{
    $this->date = new \DateTime();
}

Enfin, ajoutez un message et vérifiez que la date est bien enregistrée.

Création d'un contrôleur API

À l'aide de la commande:

symfony console make:controller

Créez un contrôlleur ApiController. Ajoutez l'attribut #[Route('/api')] à la classe. De cette manière, toutes les routes de ce contrôlleur seront préfixées par /api/.

C'est ici que nous hébergerons nos endpoints !

Liste des messages

Créez tout d'abord un premier endpoint, dont voici les caractéristiques:

  • Adresse: /api/messages
  • Verbe: GET
  • Description: obtient la liste de tous les messages

La réponse aura cette forme :

{
    "messages": [
        {
            "id": 17,
            "text": "Bonjour à tous!",
            "date": "2023-01-16T16:14:14+01:00"
        },
    ...
    ]
}

(La date est ici formatée en ISO 8601)

Conseil: avec Symfony il est possible de construire un tableau PHP $data et d'utiliser: return $this->json($data)

Sérialisation automatique

Avant d'aller plus loin, nous allons utiliser des outils qui permettront de nous simplifier la tâche. Premièrement, installez :

symfony composer require friendsofsymfony/rest-bundle

Éditez alors config/packages/fos_rest.yaml de cette façon:

# config/packages/fos_rest.yaml
fos_rest:
    view:
        view_response_listener:  true
    format_listener:
        rules:
            - { path: ^/api, prefer_extension: true, fallback_format: json, priorities: [ json ] }
            - { path: '^/', priorities: ['text/html', '*/*'], fallback_format: html, prefer_extension: true }

Et ajoutez l'attribut View (de FOS\RestBundle\Controller\Annotations\View) à votre action, qui pourra devenir :

#[View()]
#[Route('/messages', name: 'app_api')]
public function index(MessageRepository $messagesRepository)
{
    return [
        "messages" => $messagesRepository->findAll()
    ];
}

Constatez que les messages sont désormais sérialisés automatiquement !

Paramétrer la sérialisation

La sérialisation automatique est très pratique, mais elle expose par défaut tous les champs de notre message, ce qui pourrait être inutile, ou même divulguer des informations que l'on ne souhaite pas.

Pour ce faire, il est possible de définir ce que l'on appelle des groupes de sérialisation.

À l'aide de la documentation, créez un groupe de sérialisation message_basic qui ne contient que les informations "basiques" (l'id, le texte et la date), et modifiez votre attribut View pour utiliser ce groupe.

Documentation :

Recherche et filtrage des messages

Géo-messages

Nous allons pouvoir enfin assembler ce que nous avons fait de manière à apporter une première fonction de Géo-chat !

Modifiez le endpoint /api/messages, de manière à ce que :

  • Il utilise deux paramètres GET :
    • address obligatoire: une chaîne de caractère donnant l'adresse autour de laquelle on souhaite obtenir les messages
    • radius: un rayon (par défaut 2000) en mètres utilisé pour la recherche
  • Les résultats seront triés du plus récent au plus ancien
  • On limitera à 10 résultats

Les erreurs suivantes seront gérées:

  • L'adresse n'est pas fournie
  • L'adresse fournie ne peut pas être résolue

Elle donneront lieu à des erreurs HTTP de type 400 (Bad request).

Les distances seront fournies dans la réponse, exemple :

{
    "results": [
        {
            "message": {
                "text": "Un bonjour du département #iutinformatique"
            },
            "distance": 386.33058427448344
        }
    ],
    ...
}

Vous pourrez utiliser la méthode MessageRepository::findClose(), qui vous aidera à calculer les distances entre les points.

Vous pouvez utiliser MapQueryParameter afin de gérer plus efficacement les paramètres address et radius.

Filtre par date

Ajoutez un paramètre posted_after optionnel au endpoint /api/messages, qui filtre les messages qui ont été postés uniquement après une date donnée.

Publication/édition/suppression

Publier des messages

Créez un nouveau endpoint permettant de publier des messages :

  • Adresse: /api/message
  • Verbe: POST
  • Contenu de la requête : les données d'un message en JSON

Cet appel créera un message dans la base de données. Pour l'instant, ne vous occupez pas des erreurs, et répondez :

{
    "message": {
        "id": 34,
        "text": "Coucou !",
        "date": "2023-01-16T18:15:57+01:00"
    }
}

Pour cela, vous utiliserez MapRequestPayload, afin de désérialiser le message reçu en JSON.

Vérification des contraintes

Maintenant, nous allons gérer les contraintes suivantes :

  • On veut que le champ text ne soit pas vide
  • On veut que le champ address ne soit pas vide
  • On veut que l'adresse obtenue soit correcte

Pour cela :

  • Ajoutez des contraintes sur Message à l'aide de l'attribut #[Assert\...] du validateur de Symfony
  • Levez une erreur si aucune longitude/latitude n'est trouvée pour l'adresse donnée

Mettre à jour un message

Implémentez maintenant un endpoint permettant de mettre à jour un message :

  • Adresse: /message/{id}
  • Verbe: PATCH

Ce endpoint fonctionnera exactement comme le précédent, mais en mise à jour. Vous êtes invité à factoriser le plus de code possible !

Supprimer un message

Ajoutez maintenant un endopoint permettant de supprimer un message :

  • Adresse: /message/{id}
  • Verbe: DELETE

Cet appel détruit le message passé en paramètre. Pour l'instant, notez que :

  • Il n'y a pas de droits d'accès ou d'utilisateurs à gérer
  • Si le message n'existe pas, une erreur 404 sera déjà levée par le framework