Compte-rendu du TP Symfony

I. Tutoriel : Maîtriser les Notions de Symfony

Symfony est un framework PHP basé sur le MVC (Modèle-Vue-Contrôleur). Ce guide retrace les étapes et les explications pour faire une application web avec Symfony.

1 L'Environnement de Travail

Aujourd'hui pour héberger des site web on utilise en majorité Linux. Plutôt qu'une machine virtuelle lourde on va utiliser WSL (Windows Subsystem for Linux) pour installer PHP, MySQL et Composer.

# Installation et mise à jour
wsl --install -d Ubuntu-24.04
sudo apt update && sudo apt upgrade -y
sudo apt install -y php8.2 php8.2-cli php8.2-common php8.2-mysql php8.2-zip php8.2-gd php8.2-mbstring php8.2-curl php8.2-xml php8.2-bcmath php8.2-intl
sudo apt install -y mysql-server
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.deb.sh' | sudo -E bash
sudo apt install symfony-cli

# Création d'un nouveau projet symfony new mon_projet --webapp

2 Architecture MVC & Premier Contrôleur

Dans Symfony, tout commence par une Route qui appelle une méthode de Contrôleur. Une Route permet de répondre à la question : "Si l'utilisateur demande cette adresse, quel code dois-je exécuter ?". La route fait donc le pont entre le navigateur et notre application. Elle ne contient aucune logique, elle se contente d'identifier l'intention de l'utilisateur. Les routes peuvent aussi être dynamiques. Par exemple, /article/{id} permet de capturer un numéro d'article variable et de le transmettre au système.


Une fois que la route a trouvé le bon chemin, elle appelle une méthode spécifique dans une classe appelée Contrôleur. Il reçoit la Requête (Request), interroge les services (comme la base de données), et doit impérativement retourner une Réponse.


Ce système de séparation offre plusieurs avantages : On peut changer l'URL d'une page sans modifier la logique du code. Chaque action possède sa propre méthode, ce qui évite d'avoir des fichiers de code interminables et qui permet de vibler et modifier plus facilement.

# Exemple d'une route qui appel un Controlleur
app_galerie:
    path: /galerie/{page}  // Quand l'utilisateur rentre cet url
    controller: App\Controller\GalerieController::index // On appel le controller GalerieController
    defaults:
        page: 1
    requirements:
        page: '\d+'

3 La Vue : Twig & Assets

Dans Symfony, on ne mélange pas le code PHP brut avec le HTML. On utilise Twig un moteur de template qui rend le code plus lisible, plus sûr et surtout plus facile à maintenir.
Le contrôleur joue le rôle de préparateur en regroupant les informations (issues d'une base de données par exemple) dans un tableau associatif, qu'il transmet ensuite à la méthode render(). Une fois dans le fichier Twig, chaque clé de ce tableau devient une variable utilisable. L'affichage se fait alors avec une syntaxe simple : on utilise les doubles accolades {{ }} pour injecter une valeur (comme une image) et les balises {% %} pour la logique d'affichage. Cette séparation garantit que le HTML reste lisible et sécurisé, car Twig nettoie automatiquement les données pour tout sécuriser.

# Exemple d'affichage d'une image
<img src="{{ asset('images/' ~ image.url) }}">

4 Le Modèle : Doctrine (BDD & ORM)

Doctrine est l'ORM (Object-Relational Mapper) par défaut de Symfony. Au lieu d'écrire des requêtes SQL à la main, on manipule des Entités, qui sont des classes PHP. Grâce au fichier .env qui centralise les identifiants de connexion, Doctrine établit le pont : quand on crée ou modifie un objet en PHP, l'ORM génère et exécute automatiquement les requêtes INSERT ou UPDATE nécessaires dans la base de donnée. Pour recuperer les données on ne fait pas de SELECT mais on utilise les get des entité (comme on est en POO).

# Création de table et migration
php bin/console make:entity
php bin/console make:migration
php bin/console doctrine:migrations:migrate

# Exemple d'une entité class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 180)] private ?string $email = null; public function getUserIdentifier(): string { return (string) $this->email; } }

5 Formulaires & Upload

La gestion des formulaires dans Symfony agit en sécurisant l'entrée des données : plutôt que de manipuler du HTML, on crée des classes PHP qui valident et transforment automatiquement les saisies utilisateur en objets exploitables. Pour l'upload, le fichier est intercepté sous forme d'objet UploadedFile, permettant de vérifier son extension et son poids via des contraintes de validation avant tout traitement.

# Traitement du fichier dans le contrôleur
$newFilename = uniqid().'.'.$file->guessExtension();
$file->move($this->getParameter('images_directory'), $newFilename);

6 Sécurité & Authentification

Le Security Bundle de Symfony à deux fonctions : l'authentification, qui vérifie l'identité de l'utilisateur (via une entité User et un authentificateur), et l'autorisation, qui définit ses droits d'accès. En implémentant la UserInterface, notre entité devient compatible avec le système de pare-feu (firewall) du framework, lequel intercepte les requêtes pour s'assurer que les identifiants sont valides et que les mots de passe sont hachés de manière sécurisée. Une fois l'identité confirmée, le système s'appuie sur des rôles hiérarchisés (tels que ROLE_ADMIN) pour restreindre dynamiquement l'accès à certaines routes ou fonctionnalités, garantissant ainsi une protection de l'ensemble de l'application.

// Protection d'une route
#[Security("is_granted('ROLE_ADMIN')")]
public function add(...)

II. État des Lieux des Exercices Réalisés

Exercice 1 à 3 : Socle de l'Application

Mise en place de l'environnement WSL et création de la structure de la galerie. J'ai configuré les routes incluant des contraintes sur les paramètres (ex: page doit être un entier \d*).

Code important : Configuration des routes et controllers
# src/Controller/GalerieController.php
// --- 1. GALERIE & FILTRES ---
public function index(int $page, Request $request, EntityManagerInterface $em): Response
{
    $repository = $em->getRepository(Image::class);
        
    $formFilter = $this->createFormBuilder()
        ->add('auteur', TextType::class, ['required' => false])
        ->getForm();
        
    $formFilter->handleRequest($request);
        
    if ($formFilter->isSubmitted() && $formFilter->isValid()) {
        $nomAuteur = $formFilter->get('auteur')->getData();
        $images = $repository->findBy(['auteur' => $nomAuteur], ['id' => 'DESC']);
    } else {
        $limit = 6;
        $offset = ($page - 1) * $limit;
        $images = $repository->findBy([], ['id' => 'DESC'], $limit, $offset);
    }
        
    return $this->render('galerie/index.html.twig', [
        'images' => $images,
        'page' => $page,
        'form' => $formFilter->createView()
    ]);
}

# la route # --- ROUTES DE LA GALERIE --- app_galerie_add: path: /galerie/add controller: App\Controller\GalerieController::add app_galerie: path: /galerie/{page} controller: App\Controller\GalerieController::index defaults: page: 1 requirements: page: '\d+' # --- ROUTES DE SÉCURITÉ (LOGIN / LOGOUT) --- app_login: path: /login controller: App\Controller\SecurityController::login app_logout: path: /logout controller: App\Controller\SecurityController::logout # --- ROUTE D'INSCRIPTION --- app_register: path: /register controller: App\Controller\RegistrationController::register

Exercice 4 à 7 : BDD & Formulaires

Création de l'entité Image avec validations strictes sur le titre et la date. Mise en œuvre du filtrage par auteur via un formulaire de recherche. Mise en place de la possibilité d'ajouter d'upload des images.

Code important : Upload d'image avec un formulaire.
# Formulaire
class ImageType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('titre', TextType::class)
            ->add('auteur', TextType::class)
            ->add('date', DateType::class, ['widget' => 'single_text'])
            ->add('fichier', FileType::class, [
                'mapped' => false,
                'required' => true,
                'constraints' => [
                    new File([
                        'maxSize' => '5M',
                        'mimeTypes' => ['image/jpeg', 'image/png'],
                    ])
                ],
            ])
            ->add('save', SubmitType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults(['data_class' => Image::class]);
    }
}

Exercice 8 : Sécurisation

Mise en place du système d'authentification complet. Création de l'entité User, des formulaires de login et d'inscription, et protection de la route d'ajout d'image.

Code important : AppAuthenticator.php
# src/Security/AppAuthenticator.php
class AppAuthenticator extends AbstractLoginFormAuthenticator
{
    use TargetPathTrait;

    public const LOGIN_ROUTE = 'app_login';

    public function __construct(private UrlGeneratorInterface $urlGenerator)
    {
    }

    public function authenticate(Request $request): Passport
    {
        $email = $request->getPayload()->getString('email');

        $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $email);

        return new Passport(
            new UserBadge($email),
            new PasswordCredentials($request->getPayload()->getString('password')),
            [
                new CsrfTokenBadge('authenticate', $request->getPayload()->getString('_csrf_token')),
                new RememberMeBadge(),
            ]
        );
    }

    // src/Security/AppAuthenticator.php (ou GalerieAuthenticator.php)

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        // Si l'utilisateur essayait d'accéder à une page protégée avant (ex: /galerie/add),
        // on le renvoie vers cette page.
        if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
            return new RedirectResponse($targetPath);
        }

        // SINON, on le renvoie vers la galerie par défaut :
        // Remplace la ligne "throw new \Exception..." par celle-ci :
        return new RedirectResponse($this->urlGenerator->generate('app_galerie'));
    }

    protected function getLoginUrl(Request $request): string
    {
        return $this->urlGenerator->generate(self::LOGIN_ROUTE);
    }
}

III. Bilan Personnel

💡 Difficultés Rencontrées

Le plus dur a été de devoir à chaque fois réinstaller l'environnement WSL, puis Symfony, PHP, etc. Ça m'a fait perdre beaucoup de temps que je n'ai pas pu dépenser à explorer Symfony. Puis j'ai eu un souci lors de la dernière séance qui a fait que j'ai perdu tout mon projet (j'étais à l'exercice 8 sur la sécurité). J'ai donc dû refaire tout en une seule séance (je me suis beaucoup aidé de l'IA) et je suis revenu à l'exercice 8. L'une de mes déceptions, c'est donc de ne pas avoir pu finir car je n'ai pas fait les exercices 9 et 10 : passer en mode production, voir les callbacks de Doctrine et les relations entre entités.

✅ Satisfactions

J'ai plutôt bien aimé Symfony : c'est une nouvelle façon de voir les sites web et la manière de les concevoir. De plus, je n'ai pas éprouvé de difficulté en soi pour comprendre le fonctionnement et je pense avoir acquis de bonnes bases solides pour la SAE à venir. J'ai notamment bien compris le modèle MVC.

Conclusion

Le TP a été réalisé avec succès jusqu'à l'étape 8. J'ai acquis une maîtrise des piliers de Symfony : le routage, la gestion des templates, l'interaction avec la base de données via Doctrine et la sécurisation des accès. Ce projet constitue une base solide pour développer mes futurs sites web.

📦 Code Source du Projet

Vous pouvez télécharger l'intégralité du code source (src, templates, routes).

Télécharger le code (.zip)