[Website] Extend the cookbook section and render articles from RST#2148
Open
chr-hertel wants to merge 10 commits into
Open
[Website] Extend the cookbook section and render articles from RST#2148chr-hertel wants to merge 10 commits into
chr-hertel wants to merge 10 commits into
Conversation
Add a cookbook/tutorial section to the Symfony AI website with individual
article pages at /cookbook/{slug}, each walking users through a specific
AI use case:
- Build a RAG Pipeline (Platform, Store, Agent)
- Structured Output (Platform)
- Tool Calling with Agents (Platform, Agent)
- Build an MCP Server (MCP Bundle)
- Streaming Responses (Platform)
- Multi-Modal Input (Platform)
- Chatbot with Memory (Platform, Agent, Chat)
- Multi-Agent Orchestration (Platform, Agent)
Includes homepage card grid section, shared article layout with
breadcrumbs and prev/next navigation, and cookbook-specific CSS.
Make docs/cookbook/*.rst the single source of truth for the website cookbook. A console command (app:cookbook:build) compiles the RST with the docs-builder, extracts each article body, rewrites internal doc links (cookbook -> website route, others -> symfony.com), and writes committed HTML fragments under templates/cookbook/content/. The website ships only the ai.symfony.com directory (Upsun), so fragments are generated at authoring time and committed. The article layout renders the fragment via source()|raw, and a CSS bridge maps the docs-builder (RTD + highlight.php) markup onto the cookbook theme. Per-article hand-written Twig templates are removed.
Author the five website-only cookbook tutorials as RST so docs/cookbook is the single source for all ten articles: tool-calling-with-agents, multi-agent-orchestration, multi-modal-input, streaming-responses and build-an-mcp-server. Reshape chatbot-with-memory into a step-by-step tutorial and extend the cookbook index toctree. The website cookbook registry now lists all ten articles; their bodies are generated from the RST by app:cookbook:build. Validated with doctor-rst and the docs-builder (--fail-on-errors).
Replace the hand-maintained article array in CookbookArticles with a generated manifest. Each cookbook RST carries a ".. card:" front-matter comment (title, description, icon, components) that the docs-builder ignores; app:cookbook:build parses it together with the cookbook toctree order and writes templates/cookbook/content/articles.json, which the website reads. Article ordering now lives solely in the toctree.
Turn CookbookArticles into an autowired service with instance methods instead of static calls, and inject the manifest path (now config/cookbook.json) rather than hardcoding it. Controllers receive the service via autowiring. The generator writes the manifest to the same injected path. Set up PHPUnit for the website and cover the cookbook: a unit test for CookbookArticles (manifest loading, lookup, neighbors, error cases) and a functional test for the homepage listing, article rendering and 404.
Resolve the article fragment in PHP instead of building a source() path
from article.slug in the template. CookbookArticles::body() validates the
slug against a strict allowlist (no slashes or dots) before reading the
file, the route now constrains {slug} to [a-z0-9-]+, and the template
only emits the pre-resolved body. Defense in depth on top of the existing
manifest allowlist; the raw HTML is trusted, generated content.
Add tests covering the slug guard (traversal, absolute path, null byte,
case) and the missing-fragment case.
Replace the array-shaped findBySlug/findNeighbors pair with a single CookbookArticles::get() that returns a CookbookArticlePage DTO carrying the article and its previous/next neighbors, so the controller needs only one service call. Articles are now CookbookArticle DTOs instead of arrays. A missing slug throws CookbookArticleNotFoundException, which carries a #[WithHttpStatus(404)] attribute so the kernel upcasts it; the controller no longer translates the lookup to an HTTP error itself.
Fold the fragment body into CookbookArticlePage so get() returns everything an article page needs in one call. body() is now private and invoked by get(); the controller no longer makes a separate body call.
1da5982 to
336cf1a
Compare
OskarStark
approved these changes
Jun 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extending the cookbook section.
Screencast.from.2026-06-09.01-24-02.webm
DISCLAIMER
i don't think all of them are really cookbook and some should be shifted into the docs, but that's kinda fuzzy for me right now - maybe cookbook should more be about building larger example features than just another doc for multi-modal - will most likely rework, but core mechanism for shipping cookbook on website is in place.