Server data from the Official MCP Registry
Create shareable links to run Symfony, Laravel or plain PHP code in the browser, no install.
About
Create shareable links to run Symfony, Laravel or plain PHP code in the browser, no install.
Remote endpoints: streamable-http: https://apprendre.forelse.fr/mcp
Security Report
Valid MCP server (1 strong, 0 medium validity signals). No known CVEs in dependencies. Imported from the Official MCP Registry.
3 tools verified · Open access · No issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Found in Source Code
Found by scanning the linked source code. This listing connects to a hosted endpoint, so none of this runs on your machine: it describes what the server software does where it is hosted.
How to Connect
Remote Plugin
No local installation needed. Your AI client connects to the remote endpoint directly.
Add this to your MCP configuration to connect:
{
"mcpServers": {
"io-github-oipnet-php-playground": {
"url": "https://apprendre.forelse.fr/mcp"
}
}
}Documentation
View on GitHubFrom the project's GitHub README.
Forelse · apprendre — le moteur
Une plateforme pour apprendre à développer en codant, sans vidéo ni installation. L'apprenant écrit son code dans le navigateur, où tourne un vrai projet : PHP 8.5 compilé en WebAssembly pour Symfony et Laravel, et des simulateurs pour Docker et Nuxt. L'aperçu se met à jour en direct, et des tests automatiques valident chaque objectif. Rien n'est exécuté sur le serveur.
C'est le moteur de apprendre.forelse.fr, open source (AGPL-3.0) et
auto-hébergeable. Les exercices n'en font pas partie : ils vivent dans des packs de contenu, que le moteur
charge. Un pack de démonstration est fourni dans examples/packs/demo, et chacun peut écrire les siens. Les parcours
de Forelse ne sont pas dans ce dépôt ; une école ou une entreprise peut les utiliser sur sa propre instance, sur devis
(en savoir plus).
- Installer une instance avec Docker : auto-hebergement/README.md
- Écrire des exercices : Packs de contenu et L'atelier des auteurs
- Développer le moteur : Démarrer en local
- Ce qui change d'une version à l'autre : CHANGELOG.md
Architecture
platform/ Application Symfony 8.1 : pages, comptes, progression, API, content:check
src/Content/Framework/ Ce que le moteur sait de chaque framework (un profil par runtime)
playground/ Îlot TypeScript : runtime PHP WebAssembly, éditeur Monaco, aperçu isolé
packages/ Ce qui peut vivre hors du moteur : contrat et simulateurs
runtime-contract/ Ce qu'un runtime doit remplir, et ce que le moteur lui fournit (types seuls)
simulateur-nuxt/ Le runtime Nuxt : simulateur, worker, greffons de build, lanceur de tests
simulateur-docker/ Le simulateur Docker, bibliothèque PHP du runtime php-wasm
environments/ Projets de base dans lesquels s'exécutent les exercices, et de quoi les empaqueter
(bin/build-env.sh) — destiné à devenir son propre dépôt, voir environments/README.md
tools/ Simulateurs Docker (docker-sim) et Nuxt (nuxt-sim), publication d'une version
auto-hebergement/ Guide, compose.yaml et .env.example d'une instance auto-hébergée
deploy/ production et préproduction de l'instance de Forelse, derrière un proxy commun (deploy/README.md)
examples/packs/ Pack de démonstration (format de contenu, tests du moteur)
examples/themes/ Exemple de thème d'instance (marque blanche) : theme.yaml commenté et ses images
Dans le navigateur :
Page exercice (plateforme) ── worker PHP (php-wasm) : instance « aperçu » + instance « tests »
│ postMessage (origine vérifiée)
▼
Relais /sandbox (origine bac à sable) ── Service Worker ── iframe d'aperçu (code de l'apprenant)
Le code de l'apprenant n'est jamais exécuté sur le serveur : PHP tourne dans le bac à sable WebAssembly du navigateur, sans accès au disque ni au réseau. Le playground offre aussi une console du projet (bin/console pour Symfony : Doctrine, debug:router… ; php artisan pour Laravel : migrate, route:list…), exécutée dans ce même bac à sable.
Le bac à sable (/bac-a-sable, /en/playground) a aussi un serveur MCP sur /mcp (HTTP, sans compte), pour les assistants IA (Claude, ChatGPT, Cursor…) : get_starter (projet de départ, chemins modifiables, paquets), create_sandbox (fabrique le lien partagé, rien n'est stocké) et read_sandbox (relit un lien). Voir App\Mcp\PlaygroundTools ; le lien suit le format de playground/src/app/scratch.ts (App\Scratch\ScratchLink). Quota par adresse IP : limiteur mcp. Chaque appel d'outil est un événement Umami envoyé par le serveur (mcp-depart, mcp-lien-cree, mcp-lien-lu : framework, langue, client MCP, jamais le code), voir App\Mcp\McpAnalytics ; les visites venues de ces liens portent utm_source=mcp. Ajout dans Claude Code : claude mcp add --transport http php-playground https://apprendre.forelse.fr/mcp. Documentation publique : /en/playground/ai ; fiche du registre MCP : mcp/server.json.
Démarrer en local
Prérequis : PHP 8.5 (avec pdo_pgsql), Composer, Node.js 22+, PostgreSQL 15+.
Créez une fois le rôle attendu par platform/.env (ou adaptez DATABASE_URL dans platform/.env.local) :
psql -d postgres -c "CREATE ROLE app LOGIN PASSWORD 'app' CREATEDB"
make install # dépendances, environnements d'exécution, bases PostgreSQL (formation, formation_test)
make dev # plateforme http://localhost:8000, bac à sable http://127.0.0.1:8001, Vite, Mailpit http://localhost:8025
Ouvrez http://localhost:8000. Pour charger d'autres packs, dans platform/.env.local :
CONTENT_PACKS_PATHS="%kernel.project_dir%/../examples/packs,/chemin/vers/mes-packs"
Tests : make test. Vérification des packs : make check.
make buildécraseplatform/public/build/.vite/entrypoints.json: la plateforme sert alors les fichiers compilés, même si Vite tourne encore. Après un build, relancez Vite (make dev-vite) pour retrouver le rechargement à chaud — sinon vos modifications front ne s'affichent pas.
Les serveurs de développement tournent avec
-d xdebug.mode=off: Xdebug fait planter le serveur interne de PHP (segfault) sur certaines pages, et ralentit tout.content:checkfait de même.
L'atelier des auteurs
/atelier écrit les exercices d'un pack depuis le navigateur : les fichiers tels qu'ils sont sur le disque (exercise.yaml, instructions.md, starter/, solution/, tests/), un bouton Vérifier qui lance content:check et affiche son verdict, et un lien pour jouer l'exercice comme un apprenant.
L'atelier écrit sur le disque et exécute le PHP du pack : il est réservé au rôle ROLE_AUTEUR.
⚠️ Sur des packs modifiables,
ROLE_AUTEURest un rôle d'administrateur. Vérifier lance PHPUnit sur les tests que l'auteur vient d'écrire, avec l'utilisateur système de la plateforme : un test peut lire les sessions,APP_SECRETet les variables d'environnement du serveur (clés Stripe et Anthropic, base). Ne le donnez qu'à qui pourrait administrer la machine. Lescompose.yamlmontent les packs en lecture seule : l'atelier y reste consultable, mais n'écrit rien, et Vérifier ne lance que ce que le dépôt de contenu contient déjà — le même code que sa CI. On écrit alors dans le dépôt de contenu, sur son poste ; ne retirez pas le:rod'une instance en production.
bin/console app:auteur vous@example.com # donner le droit
bin/console app:auteur vous@example.com --retirer # le retirer
Un exercice se supprime depuis son éditeur (dossier et ligne de track.yaml), sauf s'il sert de base à un autre : le fil rouge se casserait.
Un parcours dont le dossier est en lecture seule reste consultable, mais n'est pas modifiable.
Chaque chapitre a sa fiche de cours (voir « Packs de contenu »), éditable depuis l'atelier : markdown à gauche, à droite l'aperçu rendu par le serveur, donc exactement ce que verra l'apprenant. Squelette assemble un point de départ à partir des exercices du chapitre, Rédiger avec l'IA demande un brouillon complet au modèle ; les deux sont proposés, jamais enregistrés sans relecture. Enregistrer une fiche vide la retire du chapitre.
Génération assistée (facultative)
Avec une clé d'API, l'atelier peut proposer un brouillon d'exercice à partir d'un sujet (« tester un voter avec des stubs »), dans l'univers et le style du parcours : consignes, fichiers de départ, solution, tests, indices, liens de documentation. Après un échec de content:check, un bouton Corriger avec l'IA lui renvoie le rapport et propose une correction.
Avec la même clé, un bouton Post LinkedIn apparaît sur la page d'un parcours et sur l'éditeur d'un exercice de Pratique — les deux objets du contenu qui ont une page publique à partager. Il ouvre /atelier/<parcours>/post, où le modèle écrit trois brouillons sous trois angles (annonce, coulisses, pédagogique) à partir du contenu déjà écrit : résumé, chapitres, notions, consignes. Chaque brouillon se relit et se retouche sur place, avec le compte de caractères que LinkedIn impose (3 000 au total, ~200 avant « …voir plus »), puis se copie. Rien n'est publié — l'atelier ne parle jamais à LinkedIn — et rien n'est enregistré : un post vit dans LinkedIn, pas dans le pack.
L'éditeur d'un exercice de Pratique mène aussi à sa version anglaise (/atelier/pratique/<exercice>/anglais) : le même éditeur, sur les fichiers de i18n/en/ (voir « Traduction »). Avec la clé, Traduire avec l'IA propose la traduction entière, en partant de celle qui existe : exercise.yaml réduit aux clés traduisibles, consignes, fichiers de code qui changent, mutants, et l'empreinte source calculée par la plateforme. Rien n'est enregistré avant que l'auteur relise ; Vérifier lance content:check sur la version anglaise. Une traduction en retard sur l'exercice français est signalée dans l'éditeur, avec l'empreinte à recopier.
# platform/.env.local
ANTHROPIC_API_KEY=sk-ant-…
AI_MODEL=claude-sonnet-5 # facultatif
AI_MODEL_POST=claude-opus-5 # facultatif : les posts LinkedIn seulement, vide = AI_MODEL
AI_MODEL vaut pour tout ce qui appelle le modèle ; AI_MODEL_POST ne vaut que pour les brouillons de post. Écrire un post n'est pas relire du code : on peut vouloir un modèle plus fort là où le texte part sous son propre nom, sans payer ce modèle sur chaque revue de code du mentor.
La même clé sert à content:lesson-draft --ia, qui rédige un brouillon de fiche de cours (voir « Packs de contenu »). Sans clé, l'atelier fonctionne à l'identique et ces boutons n'apparaissent pas. Un brouillon reste un brouillon : rien n'est enregistré sans relecture, et c'est content:check qui tranche — tests rouges au départ, verts avec la solution.
Le mentor de l'apprenant
Avec la même clé d'API, l'apprenant connecté dispose d'un mentor dans le playground, qui intervient là où un bon mentor interviendrait, jamais à sa place :
- Une revue de code, sur demande, quand les tests passent : un regard sur ce qui pourrait être plus idiomatique, plus lisible ou plus sûr, en quatre points au plus, avec la solution de référence comme point de comparaison (jamais citée). Elle est conservée avec la progression.
- Des erreurs expliquées : sur une page d'exception de l'aperçu, des tests qui plantent ou une commande
bin/consoleen échec, un bouton « Expliquer l'erreur » traduit l'exception en cause probable et en piste (où regarder, quoi vérifier), sans donner la correction.
Les indices, eux, sont écrits par l'auteur dans exercise.yaml et ne coûtent rien. Le mentor est réservé aux comptes et limité à 40 appels par heure et par apprenant (config/packages/rate_limiter.yaml) : chaque appel se paie. Sans clé, les boutons n'apparaissent pas.
La solution de référence est consultable par un apprenant connecté (bouton « Solution »), à côté de son code, en lecture seule. Il est prévenu avant : l'exercice ne rapportera alors pas d'XP, même réussi ensuite avec son propre code. Un invité n'y a pas accès : les solutions font partie du contenu.
Parcours payants
Une seule question décide de ce qu'un apprenant ouvre : a-t-il accès à ce chapitre ? (TrackAccessChecker,
utilisé par les pages, les API de l'exercice, de la progression et du mentor, les fiches et le livret).
- Sans compte, rien ne se joue : la page d'un exercice reste publique et lisible (consigne, notions), mais l'éditeur, l'exécution et les tests demandent un compte.
- Le premier chapitre d'un parcours public est ouvert, gratuitement, à tout compte connecté.
- Un administrateur ou un auteur ouvre tout.
- Un parcours sans tarif, ou à 0 €, est ouvert à tout compte : une instance qui ne fixe aucun prix reste gratuite.
- Un chef de cohorte ouvre les parcours de ses cohortes.
- Sinon, il faut un accès actif : acheté, ouvert par une cohorte ou offert (
/admin→ Accès). Un accès expiré ou révoqué garde la progression, qu'un nouvel accès retrouve intacte.
Les prix (TTC, avec prix fondateur limité par une date ou un quota) se fixent dans /admin → Tarifs ou avec
bin/console app:tarif. L'achat passe par Stripe Checkout et Stripe Tax (STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET, STRIPE_TAX_CODE) ; seul le webhook /paiement/stripe/webhook ouvre l'accès, à vie.
Les événements reçus sont journalisés et se rejouent avec bin/console app:stripe:rejouer. Un administrateur
rembourse depuis /admin → Achats (l'accès est révoqué).
Une cohorte est financée par l'établissement (ses apprenants reçoivent l'accès aux parcours choisis, aux dates
de la cohorte ; l'estimation du devis se règle dans config/services.yaml, app.cohort_quote.*) ou par ses
apprenants (chacun achète, au tarif de la cohorte s'il est fixé).
Un chef de cohorte peut organiser des soutenances de fin de parcours (/cohorte/{id} → Soutenances) : il ouvre
des créneaux en ligne (lien de visio) ou en présentiel (adresse, salle), un apprenant qui a réussi tous les exercices
du parcours en réserve un depuis la page du parcours et reçoit sa convocation avec une invitation d'agenda (.ics).
L'évaluateur (chef de la cohorte ou administrateur) lit le dossier de l'apprenant (code rendu, indices, solutions
consultées, revue du mentor), remplit la grille (un niveau par chapitre) et donne le résultat, envoyé par email.
Les résultats s'exportent en CSV.
Mise à jour d'une instance vers la 0.8 : après la migration, lancer une fois bin/console app:acces:initialiser
(les cohortes et les apprenants existants gardent ce qu'ils avaient), puis seulement fixer des prix.
Packs de contenu
<pack>/pack.yaml id, titre, version, licence, moteur attendu, parcours
<pack>/tracks/<parcours>/track.yaml environnement, chapitres → exercices
<pack>/tracks/<parcours>/about.md présentation du parcours (Markdown, facultative) : ce qu'on y apprend, pour qui
<pack>/tracks/<parcours>/chapters/<chapitre>/
lesson.md fiche de cours de fin de chapitre (Markdown, facultative)
<pack>/tracks/<parcours>/exercises/<exercice>/
exercise.yaml titre, XP, accès, fichiers éditables / en lecture seule, objectifs, indices
instructions.md consignes (Markdown)
starter/ fichiers de départ, superposés à l'environnement
tests/ tests PHPUnit cachés ; chaque objectif = une méthode de test
solution/ solution de référence (jamais envoyée au navigateur, sauf en dev)
<pack>/practice/<exercice>/ exercice de Pratique, hors parcours (même contenu qu'un exercice)
<pack>/versions/<version>.md intro d'une page de nouveautés (Markdown, facultative)
<pack>/notions/<notion>.md intro d'une page de notion (Markdown, facultative ; sans elle, la page ne s'indexe pas)
<pack>/notions/en/<notion>.md intro d'une page de notion anglaise (/en/concepts/<notion>, nommée par son slug anglais)
<pack>/notions/themes.yaml rangement de l'index /notions : par framework, des thèmes et leurs notions (facultatif)
<pack>/notions/en/themes.yaml rangement de l'index /en/concepts, par les libellés anglais des notions (facultatif)
<pack>/articles/<slug>.md article du blog (en-tête YAML, puis Markdown)
<pack>/articles/en/<slug>.md sa traduction anglaise (même format, plus translation_of)
-
duration:(dansexercise.yaml, facultative) : la durée estimée de l'exercice, en minutes, pour le public du parcours (duration: 25). La carte du parcours l'affiche par exercice, par chapitre (« ≈ 3 h 15 ») et pour le parcours (« environ 46 h de pratique »), ainsi que l'accueil et le JSON-LD du cours (timeRequired). Un chapitre ou un parcours dont un exercice n'a pas de durée n'en affiche pas : une somme partielle promettrait trop peu. -
Pratique (
<pack>/practice/<exercice>/) : de courts exercices à part des parcours, pour se servir d'une fonctionnalité d'un framework, souvent une nouveauté d'une version, parfois un point précis. Pas de chapitre, pas de fiche, pas d'XP, pas d'histoire : le code de départ est écrit à l'ancienne, les tests vérifient le comportement et l'usage de la fonctionnalité (par réflexion, par exemple). Le moteur découvre les dossiers depractice/tout seul, sans liste danspack.yaml.exercise.yamlgarde son format, avec en plus :environment:(obligatoire : il n'y a pas de parcours dont l'hériter) ;published: AAAA-MM-JJ(obligatoire) : la liste/pratiqueva du plus récent au plus ancien. Une date à venir programme l'exercice : visible des seuls administrateurs (badge « Programmé »), il est publié automatiquement ce jour-là ;summary:(obligatoire), une phrase affichée dans la liste ;version: '8.1'(facultative, entre guillemets) : la version du framework qui apporte la fonctionnalité ; elle affiche le badge « Nouveauté » etcontent:checkéchoue si l'environnement a une version plus ancienne (lue dans soncomposer.lock) ;pull_request:(URL https, facultative), proposée à la réussite ;visibility: admin(facultative), comme pour un parcours : un exercice en préparation.
access,baseetxpy sont refusés : la Pratique demande toujours un compte et ne rapporte pas d'XP. Les identifiants sont uniques parmi les exercices de Pratique de tous les packs, et un parcours ne peut pas s'appelerpratique. La liste/pratiqueest publique, filtrable par framework, par notion (concepts:) et par nouveauté ; l'exercice se joue sur/pratique/<exercice>. À la réussite, le playground propose un « Avant / après » (le code de départ face à celui de l'apprenant). L'atelier crée un exercice de Pratique en préparation (visibility: admin) dans un pack modifiable.Traduction (
<exercice>/i18n/en/) : l'exercice en anglais, joué sur/en/practice/<exercice>. La Pratique anglaise ne montre que les exercices traduits ; un exercice non traduit y répond 404. Le dossier contient :exercise.yaml, avec les seules clés traduites :title,summary,objectives,hints,requests,docs,mutants(obligatoires dès que l'exercice français les a, sinon le texte français s'afficherait),concepts,previewetreadonly(facultatives ;readonlyquand un fichier montré en lecture seule a son équivalent dans l'environnement de la langue,lang/en/validation.phpau lieu delang/fr/validation.php). Les mutants traduits gardent leurs ids, que les objectifs citent ; leurssearchvisent les lignes du code traduit. Le reste (environnement, fichiers, date, version…) vient de l'exercice français ; une autre clé est refusée ;instructions.md, la consigne complète ;- au besoin, des fichiers de
starter/,solution/ettests/qui remplacent ceux de l'exercice français au même chemin, quand le code contient du français (messages, noms de méthodes de test). Un chemin que l'exercice français n'a pas est refusé.
content:checkvérifie chaque traduction comme un exercice à part (pratique/<exercice> [en]). La clésourcerecopie l'empreinte de l'exercice français : quand il change,content:checksignale la traduction en retard et donne la nouvelle empreinte à recopier une fois la traduction à jour. Une réussite en anglais est revérifiée avec les tests anglais. Les pages de notions et de nouveautés restent en français : la page anglaise n'y renvoie pas.Traduction d'un parcours : chaque exercice se traduit comme en Pratique (
<exercice>/i18n/en/, sanssummary), et le parcours lui-même dans<parcours>/i18n/en/:track.yaml, avectitle,descriptionetchapters, qui associe à l'id de chaque chapitre sontitle(et sadescriptionsi le chapitre français en a une) ;sourcerecopie l'empreinte du parcours français (track.yaml, fiches de cours et présentation) ;chapters/<chapitre>/lesson.md, la fiche de cours traduite de chaque chapitre qui en a une ;about.md, la présentation traduite, si le parcours français en a une.
Un parcours ne paraît en anglais qu'entièrement traduit : il ne fait pas passer l'apprenant d'une langue à l'autre. Ses exercices peuvent se traduire avant lui, un par un :
content:checkles vérifie déjà. Une traduction devenue incomplète (un exercice ou un chapitre ajouté en français) ne bloque rien : le parcours anglais disparaît, etcontent:checkdit ce qui manque. Le parcours de démodecouverteest traduit en exemple. Un parcours traduit a ses pages sous/en/tracks/<parcours>; ailleurs, l'adresse anglaise renvoie au français. -
Blog (
<pack>/articles/<slug>.md) : des articles sur les coulisses du contenu et du moteur, publiés sur/blog/<slug>. Le nom du fichier est l'adresse de l'article (minuscules, chiffres et tirets). Le fichier commence par un en-tête YAML entre deux lignes---, suivi du Markdown (rendu comme une fiche de cours : HTML brut supprimé, code coloré ; un# Titreen tête est retiré, la page affiche le sien) :--- title: Faire tourner Symfony dans le navigateur # obligatoire : le h1 et le title de la page description: Comment un vrai projet Symfony tourne… # obligatoire, sans HTML : la meta description published: 2026-10-06 # obligatoire ; une date à venir programme l'article updated: 2026-10-20 # facultative : dernière révision du fond visibility: admin # facultative : en préparation practice: laravel-13-26-read-through # facultative : l'exercice de Pratique dont part l'article course: symfony-pour-dev-php # facultative : le parcours proposé en fin d'article author: grace-hopper # facultative : un des « authors » du thème (sinon « person ») tags: [Symfony, Sécurité] # facultative : les sujets, filtres de la liste ---Toute autre clé est refusée (une faute de frappe ne passe pas inaperçue). Un article programmé ou en préparation n'est ni dans la liste
/blogni dans le sitemap ; seuls les administrateurs le lisent à son adresse. Une instance dont aucun pack n'a d'article paru n'a pas de blog :/blogrépond 404 et le pied de page n'y mène pas. Chaque article porte ses données structurées (BlogPosting) ; sa date dans le sitemap estupdated, à défautpublished.La liste met l'article le plus récent à la une, puis range les autres par mois. Deux articles du même jour se départagent par une heure (
published: 2026-10-28 14:00:00, en UTC ; sans heure, minuit) : le plus tardif passe devant. L'heure ne fait qu'ordonner, l'article paraît dès le matin de son jour. Les sujets (tags:, des libellés courts, tels qu'ils s'affichent) donnent les filtres de la liste, du plus fourni au moins fourni :/blog?sujet=securitene montre que les articles « Sécurité » (page suivie, non indexée). Une traduction écrit ses propres sujets, dans sa langue. Dans le flux RSS, les sujets deviennent des<category>: dev.to, qui importe le flux, en fait les tags de l'article (quatre au plus, sans espace ni accent).Chaque article a son image, dessinée par le moteur comme celles de la Pratique (
share_carddu thème) : « Blog · », le titre et le site, en 1200 × 630 sur/blog/<slug>/image-de-partage. Elle sert d'aperçu sur les réseaux, d'image des données structurées et de vignette dans le flux RSS (enclosureetmedia:content). dev.to ignore les images du flux : collez l'adresse de l'image dans « Cover image » avant de publier le brouillon.Chaque article se termine par une invitation à essayer dans le navigateur. Avec
practice:(l'identifiant d'un exercice de Pratique, qui doit exister), elle mène à cet exercice, et une ligne sous le titre y invite aussi ; sans, elle mène à la liste des exercices de Pratique. Une traduction sanspractice:reprend celle de l'article français ; si l'exercice n'est pas traduit, elle invite à la Pratique anglaise. Les clics se comptent dans Umami (événementsarticle-exercice, avec sa position, etarticle-pratique).L'encart « Aller plus loin » propose un parcours, dans la colonne collante sur un écran large (au-dessus des contenus proches) et sous l'invitation à essayer sur un téléphone : celui de
course:(l'identifiant d'un parcours, qui doit exister), sinon le parcours du framework de l'exercice depractice:, comme après un exercice de Pratique. Un parcours en préparation ou non traduit dans la langue de la page n'est pas proposé. Une traduction sanscourse:reprend celui de l'article français. Les clics se comptent dans Umami (article-parcours, avec le parcours et la position :colonneoufin).Un article d'au moins trois intertitres (
##) affiche son sommaire en tête ; chaque intertitre a son ancre. Les blocs de code ont un bouton « Copier ». Sous l'article, l'encart « Écrit par » présente la personne du thème quand elle déclarebiooulinks(voir « Thème » plus bas).Un bloc de code peut s'ouvrir dans le bac à sable : après son langage,
sandbox=php,sandbox=symfonyousandbox=laravel(le fichier ouvert à l'arrivée du bac à sable), ousandbox=symfony:src/Controller/MenuController.phppour un autre fichier modifiable. Le bloc devient tout le fichier, il doit donc se suffire (namespace,use…). Dessous, un lien « Essayer ce code dans le bac à sable » emporte le code dans l'adresse (rien n'est stocké) ;content:checkrefuse un bac à sable ou un fichier qu'il n'accepte pas. Les clics se comptent dans Umami (article-essayer, avec le framework).```php sandbox=symfony:src/Controller/MenuController.php <?php namespace App\Controller; … ```Un article se traduit en anglais dans
<pack>/articles/en/<slug>.md, publié sur/en/blog/<slug>avec sa propre adresse (running-symfony-in-the-browser.md). Même en-tête, plustranslation_of:, le nom de l'article français qu'il traduit (une traduction par article). Le blog anglais a sa liste, son flux (/en/blog/rss.xml) et ses entrées du sitemap ; l'interface autour des articles passe en anglais, mais ses liens mènent aux pages françaises. Les deux versions parues se nomment l'une l'autre (hreflang, la française enx-default) et chaque article propose de lire l'autre. Une traduction programmée ou en préparation suit les mêmes règles qu'un article. Un moteur antérieur ignore le dossieren/. -
moteur:(danspack.yaml) déclare la ou les versions du moteur avec lesquelles le pack fonctionne, avec la syntaxe de Composer (^0.1,>=1.2 <2.0). Un moteur qui ne la satisfait pas refuse de charger le pack et le dit, plutôt que de le casser en silence. La clé est facultative — un pack livré avec le moteur n'en a pas besoin — mais un pack distribué à part a tout intérêt à la déclarer (voir « Versionnage »). -
accessest dépréciée (sans effet depuis la 0.8.0, signalée parcontent:check) : le premier chapitre de chaque parcours public est gratuit pour tout compte, la suite dépend de l'accès au parcours (voir « Parcours payants »). -
editable:accepte des motifs (migrations/*.php;*ne franchit pas un « / ») pour les fichiers qu'une commande va créer, dont le nom est imprévisible. Tout ce qu'une commande de la console crée, modifie ou supprime soussrc/,migrations/,templates/,config/,tests/outranslations/(par exempledoctrine:migrations:diff) est reporté dans l'éditeur — un fichier modifiable s'ouvre aussitôt —, dans l'explorateur, dans l'instance de tests et dans le brouillon. « Réinitialiser » retire ces fichiers générés. Quand l'exercice a un motif, le bouton + de l'explorateur crée un fichier qu'il couvre, avec le bon namespace. Les fichiers déjà présents qu'un motif couvre (src/Entity/*.php) ne s'ouvrent pas tous au démarrage : l'apprenant les ouvre depuis l'explorateur.open:doit toujours désigner un fichier, jamais un motif ; ce fichier peut être couvert par un motif s'il existe au départ (content:checkle vérifie). Les commandes externes lancées par PHP (proc_open, comme le php-cs-fixer de MakerBundle) se terminent sans rien faire :make:entity,make:controller… fonctionnent dans la console du navigateur. -
Les tests d'exercices disposent d'outils fournis par l'environnement, dans
tests/Formation/:BaseDeDonnees(recréer la base depuis les entités, enregistrer des objets),FileDeMessages(file Messenger, worker) etMigrationsDeLaBase(vider la base, jouer les migrations jusqu'à une version, comparer le schéma aux entités). -
setup:liste des commandesbin/consolelancées au chargement de l'aperçu, par exempledoctrine:schema:update --force: la base de l'aperçu (SQLite) vit en mémoire et repart de zéro à chaque chargement. -
requests:propose des requêtes toutes faites dans l'onglet « Requêtes » du playground, un petit client HTTP pour tester une API :{title, method, path, headers, body}. Unbodyécrit en objet YAML est envoyé en JSON, et{{ date:+7 }}y est remplacé par la date du jour + 7 jours, pour que les exemples ne se périment pas. -
Les exercices dont l'application crée une base SQLite (
var/*.db,database/*.sqlite) ont en plus un onglet « Base » dans le playground : les tables et leurs premières lignes, lues directement dans le fichier. Une valeur chiffrée ou binaire s'y montre en hexadécimal. Rien à déclarer : l'onglet apparaît dès que la base existe. -
Exercices où l'apprenant écrit ses tests : ses fichiers éditables
tests/…Test.phpsont ses tests, notés par deux sortes d'objectifs.own-tests: pass: ses tests passent tous sur l'application correcte, avec au moins un test.mutant: <id>: ses tests échouent sur un mutant, une version volontairement cassée de l'application, déclarée dansmutants: [{id, label, changes: [{file, search, replace}]}].
Chaque mutant est appliqué, testé puis retiré, de la même façon dans le navigateur et dans
content:check.content:checkvérifie que chaque texte à remplacer existe, et que la solution détecte chaque mutant. -
Exercices à étapes (
steps:), pour avancer au rythme du TDD, rouge, vert, refacto : l'apprenant garde son code d'une étape à l'autre. Chaque étape déclare{id, phase, title, editable, open?, objectives}; sa consigne est danssteps/<id>/instructions.md(sous l'instructions.mdde l'exercice, qui présente le tout) et sa solution danssteps/<id>/solution/, posée par-dessus celles des étapes d'avant.objectives,editable,openethintsne se règlent plus pour tout l'exercice : les fichiers modifiables de l'exercice sont ceux de toutes ses étapes, et ses objectifs (ceux que rejoue la vérification d'une réussite) ceux de la dernière. Pendant une étape, les fichiers modifiables d'une autre étape restent visibles, verrouillés : les tests au vert et au refacto, le code au rouge.steps: - id: rouge-bidon-vide phase: red # red, green ou refactor title: Un bidon vide ne rapporte rien editable: [tests/PaieDuLaitTest.php] objectives: - {own-tests: fail, label: Votre test échoue sur votre code} - {own-tests: reference, label: … et passe sur le code attendu} - id: vert-bidon-vide phase: green title: Zéro centime editable: [src/PaieDuLait.php] objectives: - {own-tests: pass, label: Votre test passe} - {test: testVerifieUnBidonVide, label: Un bidon vide rapporte 0 centime}Une étape rouge a deux objectifs à elle :
own-tests: fail, un test de l'apprenant au moins échoue sur son code (le message de PHPUnit s'affiche, lire l'échec fait partie de l'étape), etown-tests: reference, ses tests passent tous sur le code attendu, l'état du projet à la fin de l'étape verte qui suit (obligatoire) : un test qui échoue pour une mauvaise raison ne valide pas l'étape. Ce code attendu part au navigateur avec l'exercice, comme les tests cachés ; il ne donne que le code de l'étape verte suivante. Un test caché ne porte jamais le nom d'un test de l'apprenant (content:checkle refuse).content:checkvérifie les étapes une à une : au départ d'une étape, ses objectifs ne sont pas tous atteints (un simple avertissement pour un refacto) ; avec sa solution, ils le sont. La progression retient l'étape atteinte ; « Voir la solution » donne celle de l'étape en cours, et « Réinitialiser » ramène à la première. Les étapes se traduisent dansi18n/<langue>/exercise.yaml(steps:avec les mêmes ids, dans le même ordre :titleetobjectives) eti18n/<langue>/steps/<id>/. Un moteur antérieur à la 3.34 refuse un exercice à étapes (aucun objectif) : déclarezmoteur: '>=3.34'. -
docs:liste la documentation qui aide à résoudre l'exercice,{title, url}(URL http(s) uniquement), affichée sous les objectifs.bin/console content:links [pack|parcours|parcours/exercice|pratique|pratique/exercice]vérifie que chaque page répond et que chaque ancre (#…) existe encore (accès réseau requis). Les liens des fiches de cours et lespull_requestde la Pratique sont vérifiés en même temps. -
Fiche de cours :
chapters/<chapitre>/lesson.mdrésume ce qu'un chapitre a enseigné ; l'apprenant y accède une fois le chapitre réussi. Elle commence à##(le moteur affiche le titre du chapitre en#), utilise des blocs de code avec leur langue (php,twig,yaml…) et des lienshttps://; le HTML brut n'est pas rendu. Structure conseillée : « Ce que vous avez appris », une section par concept avec l'extrait de code clé, « Les pièges », « Pour aller plus loin ».content:checksignale (sans échouer) les chapitres qui n'en ont pas. Pour ne pas partir d'une page blanche,bin/console content:lesson-draft <parcours>/<chapitre>écrit un squelette assemblé à partir des exercices du chapitre (concepts, sections « Rappel » des consignes, liens de documentation) ; avec--iaet une clé d'API (voir l'atelier), le modèle rédige un brouillon complet, à relire.--forceremplace une fiche existante. La fiche se lit en HTML (/parcours/<parcours>/chapitre/<chapitre>) et se télécharge en PDF, établi au nom de l'apprenant (dompdf, en PHP pur : aucune dépendance système). Une fois le parcours terminé,/parcours/<parcours>/livret.pdfregroupe toutes ses fiches derrière une page de garde et un sommaire. -
Environnements :
environments/<id>/environment.yamldécrit un projet de base (id,title,php), empaqueté parenvironments/bin/build-env.sh [<id>](sans argument : tous) en archive zip servie au navigateur, avec un index de complétion généré par Reflection. Tout ce qui concerne les environnements vit dans ce dossier — les projets et leur empaquetage —, prêt à partir dans son propre dépôt : il ne dépend d'aucun code du moteur, et le moteur n'y entre que parENVIRONMENTS_DIRet ce script (voir environments/README.md).framework:choisit le runtime (symfonypar défaut,laravel,docker,nuxt) : sa console, l'organisation de son projet, ses caches, son lanceur de tests.framework: dockerbascule sur le simulateur Docker (voir plus bas) ;framework: laravelsur les conventions de Laravel : consoleartisan, dossiers (app/,routes/,resources/…), vues Blade compilées dansstorage/framework/viewsvidées entre deux runs de tests (cache:pour un autre dossier).extends: <id>prolonge un autre environnement : le projet joué est la superposition de la chaîne, du plus général au plus particulier, et l'environnement ne porte que ce qu'il ajoute ou remplace (symfony-8-appest passé de 45 à 26 fichiers,symfony-8-doctrinede 33 à 14). Les clés d'environment.yamls'héritent sauftitle;vendor/ne se superpose pas — il vient du seul dossier de la chaîne qui déclare uncomposer.json, si bien qu'un environnement sans dépendance propre n'a nicomposer.jsonnicomposer.lockà porter. Détails et règles dans environments/README.md. Un fichier.archiveignore(motifszip -x, un par ligne) allège l'archive, par exemple des traductions devendor/inutiles à l'apprenant. Les environnements Laravel forcentAPP_ENV=testingetAPP_RUNNING_IN_CONSOLE=truedansphpunit.xml: Laravel n'exempte les tests de la vérification CSRF que si les deux moitiés derunningInConsole() && runningUnitTests()sont vraies, et la première se déduit dePHP_SAPI, qui vautwasmdans le navigateur (nicliniphpdbg) — sans cela, toutPOSTd'un test de fonctionnalité répond 419. L'aperçu, lui, vérifie le jeton pour de vrai. Leur.envest livré tel quel : rien n'y est secret, le projet ne quitte jamais le bac à sable. -
Runtimes : tout ce que le moteur sait d'un framework est déclaré une fois, dans un profil (
platform/src/Content/Framework/Profiles/) : nom, console et commande d'exemple, dossiers du projet et du code, caches à vider, dossiers masqués, racines de namespace, lanceur de tests (PHPUnit ou Vitest), paquet dont la version fait foi pourversion:, langages des fiches de cours et conventions données au modèle qui rédige. Le navigateur le reçoit dans la charge utile de l'exercice au lieu de le réécrire : l'éditeur ne contient plus de table de frameworks, seulement le code propre à chaque runtime (le worker, les snippets, le script de la console).Ajouter un framework : fournir un
FrameworkProfileProvider, une classe découverte par son étiquette de service. Rien d'autre, tant qu'il s'exécute avec un runtime existant — Laravel et le simulateur Docker tournent tous deux surphp-wasm, comme Symfony.Ajouter un runtime — une autre façon d'exécuter un projet dans le navigateur — se fait en installant un paquet, sans modifier un fichier du moteur. Le paquet déclare dans son
package.json:{ "forelse": { "runtime": "./src/browser/forelse.ts", "vite": "./src/node/vite.ts" } }runtimeexporte le manifeste (identifiant, libellé, fabrique) ;vite, facultatif, ce que le runtime demande au build — greffons, constantes, alias. Le playground parcourt ses dépendances au build, trouve les paquets qui portent ce champ et les enregistre seuls (discover-runtimes.ts). Aucune liste à tenir à jour. Côté serveur, le profil du framework déclareruntime: 'mon-runtime'et arrive par l'étiquette de serviceapp.framework.Le simulateur Nuxt est le premier à passer par ce chemin :
@forelse/simulateur-nuxtporte son runtime, son worker, ses greffons Vite et son lanceur de tests, etplayground/vite.config.tsne nomme plus Nuxt nulle part. Les types que les deux côtés partagent vivent dans@forelse/runtime-contract.Les tests côté serveur suivent la même règle.
content:checkdoit rejouer les tests d'un exercice avec le même runner que le navigateur, sinon les deux verdicts divergent. Un framework dont les tests ne sont pas du PHPUnit déclare donctestModuledans son profil — un spécificateur de paquet, jamais un chemin :testModule: '@forelse/simulateur-nuxt/tests',Le moteur demande à Node de le résoudre depuis
playground/, là où les paquets de runtime sont installés : il ne sait ni où ce paquet vit, ni qu'il existe. Le module reçoit le dossier du projet puis les fichiers de test, et écrit sur la sortie standard un JSON{cases: [{name, status, file, message}], output}.
Documentation truncated — see the full README on GitHub.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Rea
Freeby Morluto · Developer Tools
Reverse engineer anything from your terminal or agent with one CLI and MCP server.
Paperclip
Freeby Paperclipai · Developer Tools
Trending hip-hop artist momentum scores across four cultural dimensions.
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
