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.phpoù 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=jsonC’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) :
- Activer les Web Services (et le protocole REST).
- Créer un service externe listant les fonctions autorisées (ex.
core_course_get_courses,core_user_create_users). - 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 uneexception, 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 :
| Fonction | Rôle |
|---|---|
core_course_get_courses | lister les cours |
core_course_create_courses | créer des cours |
core_user_create_users | créer des utilisateurs |
core_user_get_users | rechercher des utilisateurs |
enrol_manual_enrol_users | inscrire des utilisateurs à un cours |
core_enrol_get_enrolled_users | lister les inscrits d’un cours |
gradereport_user_get_grade_items | lire 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
MoodleClienttypé 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.