Le cycle requête / réponse

Symfony suit le pattern Front Controller : toutes les requêtes passent par public/index.phpKernelRouterControllerResponse.
HTTP Request
    ↓
public/index.php  (front controller)
    ↓
Kernel::handle()
    ↓
Router  →  trouve la route correspondante
    ↓
Controller::action()  →  construit une Response
    ↓
HTTP Response  (HTML, JSON, redirect…)

Déclarer des routes

Symfony accepte 4 formats : Attributs PHP (recommandé), YAML, XML, PHP.

Format YAML (config/routes.yaml)

blog_index:
    path: /blog
    controller: App\Controller\BlogController::index
    methods: [GET]

blog_show:
    path: /blog/{slug}
    controller: App\Controller\BlogController::show

Format Attribut (recommandé Symfony 6+)

<?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/blog', name: 'blog_')]  // préfixe sur la classe
class BlogController extends AbstractController
{
    #[Route('/', name: 'index', methods: ['GET'])]
    public function index(): Response
    {
        return $this->render('blog/index.html.twig');
    }

    #[Route('/{slug}', name: 'show', methods: ['GET'])]
    public function show(string $slug): Response
    {
        return $this->render('blog/show.html.twig', ['slug' => $slug]);
    }
}

Options de l'attribut #[Route]

<?php
#[Route(
    path: '/article/{id}',
    name: 'article_show',
    methods: ['GET', 'POST'],
    requirements: ['id' => '\d+'],    // regex sur le paramètre
    defaults: ['id' => 1],            // valeur par défaut
    host: '{locale}.example.com',     // contrainte de domaine
    schemes: ['https'],               // HTTPS uniquement
    priority: 10,                     // priorité de matching
)]
public function show(int $id): Response { /* … */ }

Déboguer les routes

php bin/console debug:router
php bin/console debug:router blog_index   # détail d'une route
php bin/console router:match /blog/hello  # tester une URL

Paramètres & Value Resolvers

Paramètres de route

<?php
// Paramètre simple
#[Route('/user/{id}', requirements: ['id' => '\d+'])]
public function show(int $id): Response { /* … */ }

// Paramètre optionnel
#[Route('/articles/{page}', defaults: ['page' => 1])]
public function list(int $page): Response { /* … */ }

// EntityValueResolver (Symfony 6.2+) — inject directement l'entité
use App\Entity\Post;
#[Route('/post/{id}')]
public function showPost(Post $post): Response
{
    // Symfony fait automatiquement EntityManager::find(Post::class, $id)
    return $this->render('post/show.html.twig', ['post' => $post]);
}

Query string & paramètres de requête

<?php
use Symfony\Component\HttpFoundation\Request;

#[Route('/search')]
public function search(Request $request): Response
{
    $q     = $request->query->get('q', '');       // ?q=symfony
    $page  = $request->query->getInt('page', 1);  // ?page=2
    $sort  = $request->query->getString('sort', 'date');

    // Sécurisé : get() retourne null si absent
    return $this->render('search/results.html.twig', compact('q', 'page'));
}

AbstractController — services utiles

<?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class DemoController extends AbstractController
{
    public function demo(): Response
    {
        // Rendu Twig
        return $this->render('demo/index.html.twig', ['key' => 'value']);

        // Redirection
        return $this->redirectToRoute('blog_index');
        return $this->redirect('https://example.com');

        // 404 / exceptions HTTP
        throw $this->createNotFoundException('Article introuvable');
        throw $this->createAccessDeniedException();

        // Sérialisation JSON directe
        return $this->json(['status' => 'ok', 'data' => []]);

        // Vérifier les droits
        $this->denyAccessUnlessGranted('ROLE_ADMIN');
        $this->denyAccessUnlessGranted('edit', $article); // Voter
    }
}

L'objet Request

<?php
use Symfony\Component\HttpFoundation\Request;

public function create(Request $request): Response
{
    // Méthode HTTP
    $request->getMethod();          // 'POST'
    $request->isMethod('POST');     // true

    // Paramètres POST (formulaire)
    $name = $request->request->get('name');
    $data = $request->request->all(); // tableau complet

    // Paramètres GET
    $page = $request->query->getInt('page', 1);

    // Headers
    $contentType = $request->headers->get('Content-Type');

    // Cookies
    $token = $request->cookies->get('session_token');

    // Corps JSON (API)
    $payload = json_decode($request->getContent(), true);
    // Ou avec getAttribute si content-type: application/json
    $data = $request->toArray(); // Symfony 5.2+

    // Fichiers uploadés
    $file = $request->files->get('avatar');

    // IP client
    $ip = $request->getClientIp();

    // Requête AJAX/Fetch
    $isXhr = $request->isXmlHttpRequest();
    $wantsJson = $request->getPreferredFormat() === 'json';

    return new Response('ok');
}

Réponses HTTP

<?php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

// Réponse HTML simple
return new Response('<h1>Bonjour</h1>', 200, ['Content-Type' => 'text/html']);

// Réponse JSON
return new JsonResponse(['status' => 'ok', 'count' => 42]);
return $this->json(['items' => $items], 201); // statut 201 Created

// Téléchargement de fichier
return new BinaryFileResponse('/path/to/file.pdf');

// Réponse streamée (gros fichiers)
return new StreamedResponse(function () {
    echo 'ligne 1'.PHP_EOL;
    flush();
});

// Headers personnalisés
$response = new JsonResponse($data);
$response->headers->set('X-Custom-Header', 'value');
$response->setMaxAge(3600); // cache HTTP
return $response;

Flash messages & Redirects

<?php
// Dans le controller
public function store(Request $request): Response
{
    // traitement...
    $this->addFlash('success', 'Article créé avec succès !');
    $this->addFlash('warning', 'Image manquante.');

    return $this->redirectToRoute('blog_index');
}

// Dans Twig — templates/base.html.twig
{% for type, messages in app.flashes %}
  {% for message in messages %}
    <div class="alert alert-{{ type }}">{{ message }}</div>
  {% endfor %}
{% endfor %}
▶ Mini-projet SF02 🧠 QCM SF02 Module 03 →