Symfony10 min

Symfony Demo : la decortiquer pour apprendre

Par Pierre-Arthur Demengel
SymfonyDemoApprentissageBest Practices

L'application Symfony Demo est le projet de reference officiel de Symfony. Maintenue par l'equipe core, elle illustre les bonnes pratiques de developpement Symfony avec du code de production. Plutot que de lire uniquement la documentation, decortiquer cette application est l'un des moyens les plus efficaces pour progresser. Voici une analyse methodique de ce qu'elle contient et de ce qu'on peut en apprendre.

Installation et premier lancement

L'installation est immediate. Si vous avez le CLI Symfony (et si ce n'est pas le cas, suivez mon guide d'installation) :

# Avec le CLI Symfony
symfony new --demo symfony-demo
cd symfony-demo
symfony server:start

# Ou avec Composer
composer create-project symfony/symfony-demo symfony-demo
cd symfony-demo
php bin/console server:run

L'application inclut une base SQLite pre-remplie avec des fixtures. Pas besoin de configurer MySQL ou PostgreSQL pour explorer le code. Ouvrez http://localhost:8000 et vous avez un blog fonctionnel avec authentification, commentaires et back-office.

Structure du projet

La Demo suit la structure standard Symfony. Voici les repertoires cles :

symfony-demo/
├── config/          # Configuration YAML
│   ├── packages/    # Config par bundle
│   └── routes/      # Routing
├── src/
│   ├── Command/     # Commandes console
│   ├── Controller/  # Controleurs (Blog, Admin, Security)
│   ├── DataFixtures/# Fixtures Doctrine
│   ├── Entity/      # Entites Doctrine
│   ├── EventSubscriber/ # Subscribers
│   ├── Form/        # Types de formulaires
│   ├── Repository/  # Repositories Doctrine
│   ├── Security/    # Voters
│   ├── Twig/        # Extensions Twig custom
│   └── Utils/       # Classes utilitaires
├── templates/       # Templates Twig
├── tests/           # Tests (Unit, Functional)
├── translations/    # Traductions (en, fr, etc.)
└── public/          # Assets publics

La separation des responsabilites est exemplaire. Chaque repertoire a un role precis. C'est cette structure que vous devriez reproduire dans vos projets.

Entites et relations

Le modele de donnees de la Demo est simple mais instructif. Trois entites principales :

  • User : l'utilisateur avec roles (ROLE_USER, ROLE_ADMIN), email et mot de passe hache.
  • Post : l'article de blog avec titre, slug, contenu, auteur (ManyToOne vers User) et tags (ManyToMany vers Tag).
  • Comment : le commentaire lie a un Post (ManyToOne) et a un User (ManyToOne).
  • Tag : le tag avec une relation ManyToMany vers Post.

Ce qui est instructif, c'est la facon dont les relations sont definies avec les attributs PHP 8 :

// src/Entity/Post.php - extrait
#[ORM\Entity(repositoryClass: PostRepository::class)]
#[ORM\Table(name: 'symfony_demo_post')]
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: Types::INTEGER)]
    private ?int $id = null;

    #[ORM\Column(type: Types::STRING)]
    #[Assert\NotBlank]
    private ?string $title = null;

    #[ORM\Column(type: Types::STRING, unique: true)]
    private ?string $slug = null;

    #[ORM\ManyToOne(targetEntity: User::class)]
    #[ORM\JoinColumn(nullable: false)]
    private ?User $author = null;

    /** @var Collection */
    #[ORM\OneToMany(
        targetEntity: Comment::class,
        mappedBy: 'post',
        orphanRemoval: true,
        cascade: ['persist']
    )]
    #[ORM\OrderBy(['publishedAt' => 'DESC'])]
    private Collection $comments;

    /** @var Collection */
    #[ORM\ManyToMany(targetEntity: Tag::class, cascade: ['persist'])]
    #[ORM\JoinTable(name: 'symfony_demo_post_tag')]
    #[ORM\OrderBy(['name' => 'ASC'])]
    private Collection $tags;
}

Points a retenir : orphanRemoval: true sur les commentaires (un commentaire sans post est supprime), cascade: ['persist'] sur les tags (persister un post persiste aussi ses nouveaux tags), et OrderBy pour definir l'ordre par defaut des collections.

Securite : Voters et authentification

La Demo illustre parfaitement le systeme de securite Symfony. L'authentification utilise un formulaire de login classique configure dans security.yaml.

Mais le vrai interet reside dans le Voter pour l'autorisation fine. Le PostVoter decide si un utilisateur peut editer ou supprimer un article :

// src/Security/PostVoter.php - simplifie
class PostVoter extends Voter
{
    public const DELETE = 'delete';
    public const EDIT = 'edit';
    public const SHOW = 'show';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return $subject instanceof Post
            && in_array($attribute, [self::DELETE, self::EDIT, self::SHOW]);
    }

    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token,
    ): bool {
        $user = $token->getUser();
        if (!$user instanceof User) {
            return false;
        }

        /** @var Post $post */
        $post = $subject;

        return match ($attribute) {
            self::SHOW => true,
            self::EDIT, self::DELETE => $post->getAuthor() === $user,
            default => false,
        };
    }
}

Ce pattern est bien plus propre que des if ($user->getId() === $post->getAuthor()->getId()) eparpilles dans les controleurs. Le Voter centralise la logique d'autorisation et est reutilisable partout : controleurs, templates Twig, services. Pour approfondir, lisez mon guide complet sur la securite Symfony.

Formulaires et DataTransformers

La Demo montre comment creer des formulaires sophistiques. Le PostType utilise un DataTransformer pour convertir une chaine de tags separee par des virgules en collection d'entites Tag :

// Le champ tags dans le formulaire
$builder->add('tags', TextType::class, [
    'required' => false,
]);

// Le DataTransformer convertit "symfony, php, web"
// en Collection de Tag entities (et inversement)
$builder->get('tags')->addModelTransformer(
    $this->tagsTransformer
);

Ce pattern est essentiel a connaitre. Les DataTransformers resolvent l'ecart entre ce que le formulaire affiche (une chaine) et ce que l'entite attend (une collection). Retrouvez plus de techniques de formulaires dans le guide ultime des FormType.

EventSubscribers

La Demo utilise des EventSubscribers pour decoupler la logique metier des controleurs. Le CommentNotificationSubscriber envoie un email a chaque nouveau commentaire :

class CommentNotificationSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            CommentCreatedEvent::class => 'onCommentCreated',
        ];
    }

    public function onCommentCreated(CommentCreatedEvent $event): void
    {
        // Envoyer un email a l'auteur du post
    }
}

Ce pattern decouple la creation du commentaire (dans le controleur) de la notification (dans le subscriber). Le controleur ne sait meme pas qu'un email est envoye - il se contente de dispatcher l'evenement.

Tests : la couverture de la Demo

La Demo inclut une suite de tests complete qui sert de modele :

  • Tests unitaires : pour les utilitaires (Slugger, Markdown parser).
  • Tests fonctionnels : pour les controleurs (navigation, soumission de formulaires, redirections).
  • Tests d'integration : pour les repositories (requetes Doctrine).
// tests/Controller/BlogControllerTest.php - extrait
class BlogControllerTest extends WebTestCase
{
    public function testIndex(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/blog/');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorExists('article.post');
        $this->assertCount(
            Post::NUM_ITEMS,
            $crawler->filter('article.post'),
            'La page d\'accueil du blog affiche le bon nombre d\'articles.'
        );
    }

    public function testAdminCanEditPost(): void
    {
        $client = static::createClient();
        $client->loginUser($this->getAdminUser());

        $client->request('GET', '/en/admin/post/1/edit');
        $this->assertResponseIsSuccessful();
    }
}

Notez l'utilisation de loginUser() pour simuler une connexion dans les tests fonctionnels, et assertSelectorExists() pour verifier la presence d'elements HTML.

Extensions Twig personnalisees

La Demo montre comment creer une extension Twig pour convertir du Markdown en HTML. Le pattern est propre : une classe TwigExtension qui delegue le travail a un TwigRuntime (lazy-loaded) :

// src/Twig/AppExtension.php
class AppExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('md2html', [AppRuntime::class, 'markdownToHtml'], [
                'is_safe' => ['html'],
            ]),
        ];
    }
}

// Utilisation dans Twig :
// {{ post.content|md2html }}

Ce qu'il faut adapter pour un vrai projet

La Demo est un excellent support d'apprentissage, mais certains choix ne conviennent pas a un projet de production :

  • SQLite : utilisez PostgreSQL ou MySQL en production. SQLite ne supporte pas bien les acces concurrents.
  • Structure monolithique : pour un projet complexe, envisagez une architecture en modules ou en bounded contexts.
  • Pas d'API : la Demo est 100 % server-rendered. Pour une API, ajoutez API Platform.
  • Pas de messaging : les traitements sont synchrones. En production, utilisez Messenger pour les taches lourdes (emails, generation de PDF, etc.).
  • Pas de cache applicatif : la Demo ne cache rien. En production, cachez les requetes couteuses.

Prenez les patterns (Voter, DataTransformer, EventSubscriber, structure de tests) et appliquez-les a votre contexte. Ne copiez pas la Demo comme un template.

Prochaines etapes

Apres avoir etudie la Demo, vous avez une base solide pour demarrer votre propre projet. Installez Symfony et commencez a coder. Pour la securite, le guide complet va plus loin que ce que la Demo illustre. Et pour les formulaires avances, le guide FormType couvre les cas que la Demo n'aborde pas.

Besoin d'un accompagnement pour monter en competence sur Symfony ou pour demarrer un projet ? Contactez-moi. Consultez aussi mes tarifs et mes services pour un apercu de ce que je propose.

Questions fréquentes

13 projets livrésGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tête ?

Discutons de votre site web. Réponse garantie sous 24h.

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler