Utiliser la Fetch API en JS avec du Symfony 3

Utiliser la Fetch API en JS avec du Symfony 3

18/11/17

Article précédent:

Un an à étudier à Québec: Le choc culturel sur tous les plans

Ça faisait longtemps n’est-ce pas? ;)

Après cette absence de deux mois sans article, et encore plus sans sujet un peu technique, je reviens pour vous parler un petit peu du projet sur lequel je travaille. Dans ce projet j’ai eu à utiliser la Fetch API de JavaScript pour faire des requêtes AJAX dans un fichier JavaScript servi par un serveur en Symfony 3. C’était pas facile, mais j’y suis arrivé!

Photo par Ilya Pavlov sur Unsplash

Le projet

Tout d’abord un peu de contexte. Dans le cadre de mes études, je dois réaliser un projet d’une durée de 2 ans avec un groupe de 7 étudiants (moi compris). Nous avons carte blanche sur le sujet du projet, mais celui-ci doit être validé par une série de présentation devant des jurys afin de vérifier la possibilité de transformer ce projet en startup.

Notre projet s’appelle Playficient. Nous avons constaté et rendu compte grâce à des statistiques que 40% du temps de travail (en moyenne) est perdu à cause de diverses raisons. L’une des raisons principales, et le fait de passer d’un outil informatique à un autre. De nombreux logiciels ont été crées dans le but de réduire le nombre d’outils utilisés au travail (comme la synchronisation avec une API, ou “Connecter” son compte Facebook, utiliser IFTTT, ou bien de faire des WebHooks sur Slack ou sur Github).

Nous voulons créer un outil similaire, orienté pour les entreprises (PME/TPE et Startups précisément), qui ont un contact fort avec l’environnement informatique. Mais nous voulons aussi aller plus loin, notamment en intégrant les principes de la Gamification afin de motiver les travailleurs, mais aussi de conseils intelligent avec une IA pour permettre aux utilisateurs de s’améliorer chaque jour sur leurs processus de travail. Pour rendre tout ça un peu plus corsé, nous voulions ce projet personnalisable, avec un système de module qui peuvent s’ajouter ou se supprimer selon le bon vouloir des utilisateurs, comme les extensions d’un site Wordpress ou le navigateur Firefox (Texte subliminal: passez à la version 57!)

(Lien du site vitrine à venir si vous voulez jeter un oeil, ou même nous faire des retours, ce qui serait vraiment gentil! =D)

Hey, tu parlais pas d’un article technique?

Effectivement il serait temps que j’en parle. Pour facilement gérer un système de module, nous sommes partis sur un développement serveur en PHP Symfony 3 avec son fonctionnement en Bundle, dont 2 personnes s’occupent principalement. Toute la partie “Front-End” est gérée par Symfony sous forme de Templates et d’Assets:

  • Un Template est une page HTML avec une syntaxe Twig qui est réutilisable, et customisable par des remplacements de variables et diverses fonctionnalités telles que des boucles ou des conditions
  • Un Asset est un fichier qui sert à fournir un supplément à un template, comme du code JavaScript, une image, du CSS, etc.

Un Template sert donc pour créer une base de page à réutiliser, ou pour créer une page spécifique à une Route bien définie. Mais on ne trouvera dans un Template que le code HTML et des balises Twig (si on est un minimum propre). Donc les Templates ne nous intéressent pas si on veut faire du JavaScript.

À l’inverse les Assets sont bien plus intéressants, puisque c’est grâce à eux que nous allons pouvoir donner au client son fichier JavaScript. Sauf qu’on a un problème: On ne dispose pas de la syntaxe Twig des Templates pour remplacer des valeurs cotées serveur dans le JavaScript.

Et cela pose problème notamment lorsque l’on a besoin de récupérer l’URL d’une Route.

FOSJSBundle à la rescousse

Comme je le disais précédemment, Symfony fonctionne par système de Bundle, un peu comme les packages en JavaScript, ou les Gem en Ruby. Ces bundles servent de dépendances à un projet, et on peut en trouver des centaines déjà existants grâce à la communauté florissante de Symfony.

FOSJSBundle est un Bundle qui va mettre à disposition de notre code Javascript, une librairie appelée Routing pour récupérer l’URL d’une Route.

Voici comment on s’en sert:


const url = Routing.generate('route_name', {vos parametres}, urlAbsolueBool);

La fonction va nous retourner l’url correspondant à une route donnée en paramètre, exactement ce qu’il nous fallait!

Maintenant il reste à gérer la requête en elle-même. Prenons un cas d’utilisation (au hasard, ce sur quoi j’ai travaillé aujourd’hui). Nous sommes l’utilisateur X, connecté sur Playficient et possèdent le module Gestionnaire de tâches. Nous avons une liste de boites, chacune correspond à une tâche et peut contenir un nombre indéfini de sous-tâches. Nous aimerions pouvoir faire un “drag’n drop” d’une sous-tâche, pour la changer de boite. Lorsque nous faisons cela, nous avons besoin de dire au serveur que nous voulons supprimer la tâche de son ancienne position, et la rajouter dans sa nouvelle position.

Pour cela, nous allons du côté de JavaScript faire une requête AJAX. Il existe plusieurs méthodes, la plus récente est l’utilisation de la Fetch API dont nous allons nous servir ici. La Fetch API fonctionne avec deux objets et une méthode:

  • Request: C’est l’objet qui définit la requête. On va y définir l’URL, les paramètres, la méthode, si nous sommes en CORS, etc.
  • Response: C’est l’objet qui définir la réponse. On reçoit cet objet à la résolution de la Promise d’une requête.
  • fetch(): Cette fonction prend en paramètre un objet Request, et d’autres paramètres pour la résolution de la requête. Cette fonction renvoie une Promise

Maintenant que vous en savez un peu plus, on va voir le code correspondant à une requête pour supprimer la sous-tâche de la boite dont elle faisait partie:


const url = Routing.generate('task_remove', {
  taskId: 1,
  parentId: 2,
});
const request = new Request(url, { method: "GET" });

fetch(request)
  .then((response) => {
    console.log("Nous avons supprimé la tâche!");
  })
  .catch((error) => {
    console.error(`Erreur lors de la suppression: ${error}`);
  });

OK? Tout est bon? Lançons le serveur et faisons la manipulation… Voici ce que la console Firefox nous ressort:

GET  http://localhost/task/2/1/remove       302 Found
GET  http://localhost/login                 200 OK
"Nous avons supprimé la tâche!"

Et malheur, lorsque nous rechargeons la page la tâche est toujours présente!

Rapidement, on se rend compte que deux requêtes ont été faites: Une pour effectivement supprimer la tâche, et un autre sur la page de connexion…étrange. Je ne vais pas passer par quatre chemin et expliquer très rapidement pourquoi on a été redirigé et pourquoi la vraie requête ne s’est pas faite.

Nous sommes toujours l’utilisateur X, et connecté en tant que tel, le serveur à besoin de savoir au nom de qui nous faisons la requête. C’est à cette fin que sont utilisés des cookies dans Symfony. D’une page à une autre, l’utilisateur se balade avec un cookie qui lui permet de rester connecté tant qu’il ne clique pas sur “Déconnecter”.

Nous avons donc besoin dans notre requête de récupérer ce cookie et de l’envoyer, mais comment faire? En fait, c’est très simple avec l’API Fetch et Symfony, c’est un simple paramètre à la méthode fetch(), pour envoyer ce que l’on appelle les “Credentials”:


fetch(request, { credentials: 'same-origin' })

Relançons la requête:

GET  http://localhost/task/2/1/remove       200 OK
"Nous avons supprimé la tâche!"

Et voilà le tour est joué!

Utiliser la Fetch API, et du JavaScript en Symfony, est assez facile mais seulement quand on a compris comment le faire. (J’ai passé 3 jours à chercher comment avoir les routes en JavaScript, et à comprendre cette erreur de credentials pourtant assez simple!)

Je travaillerais à la traduction de cet article en anglais pour un peu plus de diversité (et pour travailler mon Anglais!). Je vais probablement profiter de mon séjour à Québec et de mon projet de fin d’études pour écrire un peu plus d’articles techniques. D’ailleurs, en ce moment j’essaie de créer un plugin pour Talk du CoralProject, quand ce sera fait j’aurais surement un article à écrire sur le sujet ;)

D’ici là bonne continuation à chacun et chacune

Comment