---
title: "Créer un blog avec Symfony 6 - 04 - Création du système d'articles"
description: "L'entité Article, le repository et l'affichage : le système d'articles d'un blog Symfony, avec les changements à appliquer pour tourner sur une version récente."
pubDate: 2022-03-20
updatedDate: 2026-10-04
family: 'php-symfony'
tags: []
heroImage: '/images/creer-un-blog-avec-symfony-6-04-creation-du-systeme-darticles.webp'
videoUrl: 'https://www.youtube.com/watch?v=IYtFF3s9-rs'
faq:
  - question: 'Les annotations Doctrine de l’épisode fonctionnent-elles toujours ?'
    answer: "Non. Symfony 7.0 a retiré le support des annotations : seul le chargement des attributs PHP subsiste. Les blocs @ORM\\Column ou @ORM\\Entity doivent devenir #[ORM\\Column] et #[ORM\\Entity], ce qui change la syntaxe mais pas le comportement."
  - question: 'Quelle version de Symfony viser pour un nouveau blog ?'
    answer: "Symfony 8.1 est la version stable actuelle, Symfony 7.4 la version LTS. Les versions d'exploitation moins récentes restent maintenues jusqu'à leur fin de support, mais un projet neuf n'a aucune raison de démarrer sur 6.x."
  - question: 'Comment créer la table à partir de l’entité ?'
    answer: "Avec make:migration pour générer la migration, puis doctrine:migrations:migrate pour l'appliquer. C'est le chemin recommandé ; doctrine:schema:update n'est pas fait pour la production."
  - question: 'Faut-il écrire son repository à la main ?'
    answer: "Non, make:entity génère la classe de repository et la déclare sur l'entité via l'option repositoryClass. Tu y ajoutes ensuite tes requêtes métier, par exemple en DQL."
  - question: 'Comment afficher un article à partir de son slug dans l’URL ?'
    answer: "En déclarant une route qui capture le paramètre, puis en le convertissant automatiquement en entité avec MapEntity, ce qui évite une requête manuelle et renvoie un 404 si l'article n'existe pas."
---

Un blog, c'est d'abord une entité et un moyen de la lire. Cet épisode pose le système d'articles côté Symfony : la classe, sa table, son repository et les pages qui les affichent.

La série a été enregistrée sur Symfony 6. Les principes n'ont pas bougé, mais la syntaxe a changé sur un point de fond : les annotations n'existent plus côté Symfony. Ce qui suit est la version à jour.

## L'entité Article

`make:entity` génère la classe et ses attributs. Le résultat ressemble à ceci :

```
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;
}
```

Le préfixe `use Doctrine\ORM\Mapping as ORM;` permet d'écrire `#[ORM\Column]` au lieu du chemin complet. Ces attributs sont natifs à PHP 8 : ce sont les successeurs directs des annotations, et c'est la seule syntaxe acceptée depuis Symfony 7.0.

Pour un article de blog, deux champs méritent un soin particulier : le `slug`, qui sert dans l'URL et doit être unique, et `publishedAt`, un `DateTimeImmutable` qui permet de trier et de ne pas afficher les brouillons.

## Créer la table

L'entité décrit la table, la migration la crée :

```
php bin/console make:migration
php bin/console doctrine:migrations:migrate
```

La première commande compare ton mapping à l'état réel de la base et écrit un fichier de migration ; la seconde l'applique. C'est le chemin à suivre, y compris en développement : `doctrine:schema:update` modifie la structure sans laisser de trace, ce qui devient ingérable dès qu'il y a une base à ne pas casser.

Si la base n'existe pas encore, `doctrine:database:create` la crée à partir de `DATABASE_URL`.

## Le repository

Le repository généré étend `ServiceEntityRepository` et reçoit le registre des gestionnaires d'entités :

```
class ArticleRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Article::class);
    }
}
```

C'est le lien entre ton entité et Doctrine : la classe est déclarée sur l'entité via `repositoryClass`, donc `getRepository(Article::class)` renvoie bien cette classe-là. Tu y ajoutes tes requêtes métier, en DQL quand l'expression le justifie :

```
$query = $this->getEntityManager()
    ->createQuery('SELECT a FROM App\Entity\Article a WHERE a.publishedAt IS NOT NULL ORDER BY a.publishedAt DESC');
```

Pour une condition simple, `findBy()` et `findOneBy()` suffisent et évitent d'écrire du DQL.

## Persister un article

L'écriture passe par l'`EntityManagerInterface`, injecté dans le contrôleur :

```
$entityManager->persist($article);
$entityManager->flush();
```

La distinction compte : `persist()` programme l'objet, `flush()` exécute réellement les requêtes. Tant que tu n'as pas appelé `flush()`, rien n'est écrit en base.

## Afficher les articles

Les contrôleurs se génèrent avec `make:controller`. Pour un blog, le motif habituel consiste à regrouper les routes sous un préfixe au niveau de la classe :

```
#[Route('/blog', name: 'blog_')]
class BlogController extends AbstractController
{
    #[Route('/{slug}', name: 'show')]
    public function show(Article $article): Response
```

Avec `MapEntity` sur le paramètre, Symfony convertit le slug en entité sans que tu écrives la requête, et renvoie un 404 si l'article n'existe pas. Une seule déclaration remplace trois lignes de recherche manuelle et le contrôle de nullité qui va avec.

Dans le template, `path('blog_show', { slug: article.slug })` génère l'URL à partir du nom de route plutôt que de la concaténer à la main.

## Ce qui change par rapport à la série

- **Les annotations deviennent des attributs** : `@ORM\Column` s'écrit `#[ORM\Column]`. Le support des annotations a été retiré en Symfony 7.0, ce n'est pas une simple dépréciation.
- **Plus de `$this->getDoctrine()`** dans les contrôleurs : injecte `EntityManagerInterface` ou directement ton repository.
- **Les versions** : un projet neuf partira sur Symfony 8.1 (PHP 8.4) ou sur la LTS 7.4, pas sur 6.x.

L'épisode suivant de la série, [le template de base avec Bootstrap 5](/blog/creer-un-blog-avec-symfony-6-03-creation-du-template-de-base-avec-bootstrap-5/), montre comment ces articles sont rendus. La [documentation Doctrine de Symfony](https://symfony.com/doc/current/doctrine.html) détaille les types de champs et les associations, et la [documentation du routage](https://symfony.com/doc/current/routing.html) la conversion des paramètres.

## Pour approfondir

**[Symfony et backend : du premier écran à la production](https://pentiminax.fr/blog/symfony-backend.md)**

Un parcours Symfony par objectif : Twig et Doctrine, projets concrets, déploiement continu, permissions et diagnostic à partir des articles du site.

[Parcourir le guide Symfony et backend](https://pentiminax.fr/blog/symfony-backend.md)

## Questions fréquentes

**Les annotations Doctrine de l’épisode fonctionnent-elles toujours ?**

Non. Symfony 7.0 a retiré le support des annotations : seul le chargement des attributs PHP subsiste. Les blocs @ORM\Column ou @ORM\Entity doivent devenir #[ORM\Column] et #[ORM\Entity], ce qui change la syntaxe mais pas le comportement.

**Quelle version de Symfony viser pour un nouveau blog ?**

Symfony 8.1 est la version stable actuelle, Symfony 7.4 la version LTS. Les versions d'exploitation moins récentes restent maintenues jusqu'à leur fin de support, mais un projet neuf n'a aucune raison de démarrer sur 6.x.

**Comment créer la table à partir de l’entité ?**

Avec make:migration pour générer la migration, puis doctrine:migrations:migrate pour l'appliquer. C'est le chemin recommandé ; doctrine:schema:update n'est pas fait pour la production.

**Faut-il écrire son repository à la main ?**

Non, make:entity génère la classe de repository et la déclare sur l'entité via l'option repositoryClass. Tu y ajoutes ensuite tes requêtes métier, par exemple en DQL.

**Comment afficher un article à partir de son slug dans l’URL ?**

En déclarant une route qui capture le paramètre, puis en le convertissant automatiquement en entité avec MapEntity, ce qui évite une requête manuelle et renvoie un 404 si l'article n'existe pas.
