Skip to Content

Chapitre 13.4 — Pont Symfony ↔ Moodle

Partie 13 : Interopérabilité — Chapitre 4/5 Version de référence : Symfony 7.4 / Moodle Web Services.

⏱️ TL;DR

  • Moodle expose des Web Services : un endpoint /webservice/rest/server.php où l’on appelle une fonction (wsfunction) avec un token (wstoken), en REST (JSON).
  • Côté Moodle : activer les WS, créer un service externe, générer un token lié à un utilisateur/rôle avec les fonctions autorisées.
  • Fonctions utiles : core_course_get_courses, core_user_create_users, enrol_manual_enrol_users, gradereport_user_get_grade_items
  • On encapsule tout dans un client typé MoodleClient (chapitre 13.1) exposant des méthodes métier.
  • Cas d’usage : provisionner cours/utilisateurs depuis Symfony, lire des notes, synchroniser des inscriptions.
  • Lien avec votre cours Moodle : ici, Symfony pilote Moodle depuis l’extérieur (l’inverse du LTI, chapitre 13.5).
  • Pour un dev Next.js : une API REST « à l’ancienne » (fonction + token en paramètres), qu’on enveloppe proprement.

🎯 Objectifs

  • Configurer un token de Web Service côté Moodle.
  • Appeler une fonction Moodle depuis Symfony.
  • Encapsuler Moodle dans un client typé.
  • Gérer les erreurs spécifiques de Moodle.

1. Le modèle des Web Services Moodle

Moodle expose des centaines de fonctions via un endpoint unique. Une requête = une fonction + un token + les paramètres :

GET /webservice/rest/server.php ?wstoken=VOTRE_TOKEN &wsfunction=core_course_get_courses &moodlewsrestformat=json

C’est un style « RPC sur HTTP » (on appelle une fonction nommée), différent du REST orienté ressources d’API Platform. Le moodlewsrestformat=json demande du JSON (sinon XML).

2. Côté Moodle : activer et créer un token

Prérequis (côté administrateur Moodle — cf. votre cours Moodle) :

  1. Activer les Web Services (et le protocole REST).
  2. Créer un service externe listant les fonctions autorisées (ex. core_course_get_courses, core_user_create_users).
  3. Créer un utilisateur de service (avec les capacités nécessaires) et générer un token pour lui sur ce service.

Le token est un secret (il agit au nom de cet utilisateur) → coffre de secrets (chapitre 3.4).

3. Un client Moodle typé

final class MoodleClient { public function __construct( #[Autowire(service: 'moodle.client')] // base_uri = %env(MOODLE_URL)% private readonly HttpClientInterface $client, #[Autowire(env: 'MOODLE_WS_TOKEN')] private readonly string $token, ) {} private function call(string $function, array $params = []): array { $response = $this->client->request('POST', '/webservice/rest/server.php', [ 'query' => [ 'wstoken' => $this->token, 'wsfunction' => $function, 'moodlewsrestformat' => 'json', ], 'body' => $params, // les paramètres de la fonction (form-encoded) ]); $data = $response->toArray(false); // false : ne pas lever sur code HTTP // Moodle renvoie 200 même en cas d'erreur métier → on inspecte le corps if (isset($data['exception'])) { throw new \RuntimeException(sprintf('Moodle: %s (%s)', $data['message'], $data['errorcode'])); } return $data; } public function getCourses(): array { return $this->call('core_course_get_courses'); } public function createUser(string $username, string $email, string $firstname, string $lastname): array { return $this->call('core_user_create_users', [ 'users' => [[ 'username' => $username, 'email' => $email, 'firstname' => $firstname, 'lastname' => $lastname, 'createpassword' => 1, ]], ]); } }

⚠️ Piège erreurs Moodle : Moodle renvoie souvent un code HTTP 200 même quand la fonction échoue — l’erreur est dans le corps ({"exception": ..., "errorcode": ..., "message": ...}). Si vous vous fiez au seul code HTTP, vous croirez à tort que tout va bien. Inspectez toujours le corps pour détecter une exception, et convertissez-la en exception PHP claire (comme ci-dessus).

4. Fonctions et paramètres

Les paramètres Moodle sont souvent des structures imbriquées encodées en form-urlencoded avec une notation particulière (users[0][username]=...). Le composant HttpClient encode correctement un tableau imbriqué passé en body. Fonctions courantes :

FonctionRôle
core_course_get_courseslister les cours
core_course_create_coursescréer des cours
core_user_create_userscréer des utilisateurs
core_user_get_usersrechercher des utilisateurs
enrol_manual_enrol_usersinscrire des utilisateurs à un cours
core_enrol_get_enrolled_userslister les inscrits d’un cours
gradereport_user_get_grade_itemslire les notes

La liste complète est documentée dans l’administration Moodle (API des services web) — que vous connaissez de votre cours Moodle.

5. Cas d’usage : provisionner et synchroniser

Scénarios réels : un système d’inscription (Symfony) crée automatiquement les comptes et inscrit les apprenants dans Moodle ; un tableau de bord (Symfony + Next.js) lit les notes et la progression ; une synchronisation nocturne (Scheduler + Messenger, Partie 12) réconcilie les inscriptions. Toutes ces intégrations « SI ↔ Moodle » sont très demandées dans l’enseignement/la formation — et vous les maîtrisez des deux côtés.

💡 Pour un dev Next.js : les WS Moodle sont une API REST « RPC » un peu datée (fonction + token en query). L’encapsuler dans un MoodleClient typé la rend agréable à utiliser depuis votre app moderne, et vous pouvez ensuite exposer une API propre (API Platform) au front Next.js par-dessus. Vous modernisez l’accès à Moodle — une vraie valeur ajoutée pour des clients EdTech.

✏️ Exercices

Exercice 1. Écrivez la méthode enrolUser(int $userId, int $courseId, int $roleId) appelant enrol_manual_enrol_users.

✅ Solution

public function enrolUser(int $userId, int $courseId, int $roleId = 5): array { return $this->call('enrol_manual_enrol_users', [ 'enrolments' => [[ 'roleid' => $roleId, // 5 = student par défaut dans Moodle 'userid' => $userId, 'courseid' => $courseId, ]], ]); }

Passe la structure imbriquée en body ; call() gère token, format et détection d’erreur.

Exercice 2. Pourquoi ne pas se fier au seul code HTTP pour détecter une erreur Moodle ?

✅ Solution

Parce que Moodle renvoie souvent HTTP 200 même en cas d’échec métier : l’erreur figure dans le corps (exception, errorcode, message). Se fier au code HTTP ferait passer une erreur pour un succès. Il faut inspecter le corps de chaque réponse et lever une exception PHP si exception est présent.

Exercice 3. Citez un scénario réel d’intégration Symfony → Moodle.

✅ Solution

Au choix : un système d’inscription (Symfony) qui crée les comptes (core_user_create_users) et inscrit les apprenants (enrol_manual_enrol_users) dans Moodle ; un tableau de bord qui lit les notes (gradereport_...) et la progression pour les afficher côté Next.js ; une synchronisation planifiée (Scheduler + Messenger) des inscriptions entre un SI et Moodle. Ces intégrations « SI ↔ LMS » sont très demandées en EdTech.

🧠 Quiz de révision

1. Comment appelle-t-on une fonction Moodle en Web Service ?

Via /webservice/rest/server.php avec wstoken, wsfunction, moodlewsrestformat=json et les paramètres de la fonction.

2. Qu’est-ce que le token, et comment le protéger ?

Un jeton lié à un utilisateur/service Moodle qui agit en son nom ; c’est un secret (coffre de secrets), avec le minimum de fonctions/capacités autorisées.

3. Comment détecte-t-on une erreur Moodle ?

En inspectant le corps de la réponse (exception/errorcode/message), car Moodle renvoie souvent HTTP 200 même en cas d’échec.

4. Citez deux fonctions Web Services courantes.

Au choix : core_course_get_courses, core_user_create_users, enrol_manual_enrol_users, core_enrol_get_enrolled_users, gradereport_user_get_grade_items

5. Quelle est la valeur ajoutée d’encapsuler Moodle dans un client typé ?

Rendre une API « RPC » datée agréable et sûre à utiliser (méthodes métier, gestion d’erreur centralisée), et pouvoir exposer une API moderne (API Platform) par-dessus pour le front Next.js.


Prochain chapitre : 13.5 — Construire un outil LTI 1.3 — Symfony comme outil branché dans Moodle.