Aujourd'hui, on va apprendre à créer les fondations de votre projet Symfony : les entités !
Imaginez que vous construisez une maison :
Les entités et leur rôle dans Symfony
Votre première entité User
Les entités entre elles
// Création d'un utilisateur à l'ancienne $sql = "INSERT INTO user (email, password, first_name) VALUES (?, ?, ?)"; $stmt = $connection->prepare($sql); $stmt->execute([$email, $password, $firstName]); // Récupération $sql = "SELECT * FROM user WHERE id = ?"; // ... beaucoup de code répétitif ...
// Création d'un utilisateur avec Symfony
$user = new User();
$user->setEmail('jean@example.com');
$user->setFirstName('Jean');
$entityManager->persist($user);
$entityManager->flush(); // C'est tout !
Une entité, c'est comme une fiche de contact dans votre téléphone :
class User {
private $email;
private $phone;
private $birthDate;
private $isFavorite;
}
Dans un site e-commerce, quelles seraient les entités principales ?
Vous travaillez en équipe sur un projet Symfony. Aujourd'hui, nous allons poser les bases solides pour que tout le monde puisse travailler efficacement !
La base de tout !
Selon votre projet :
Les liens entre entités :
Vouloir créer toutes les entités d'un coup ! On va commencer par User car c'est la pierre angulaire de tout projet.
Dans VSCode : Terminal → Nouveau terminal
Ou utilisez le raccourci : Ctrl + ` (Windows/Linux) ou Cmd + ` (Mac)
php bin/console make:user
The name of the security user class (e.g. User) [User]:
→ Appuyez sur Entrée (garder User)
Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]:
→ Appuyez sur Entrée (oui)
Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]:
→ Appuyez sur Entrée (utiliser email)
Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server) (yes/no) [yes]:
→ Appuyez sur Entrée (oui)
src/Entity/User.php - Votre entité Usersrc/Repository/UserRepository.php - Pour les requêtes
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private ?string $password = null;
// ... getters et setters
}
php bin/console make:entity User
Symfony détecte que User existe déjà et vous propose d'ajouter des champs !
New property name (press <return> to stop adding fields):
→ Tapez: firstName
Field type (enter ? to see all types) [string]:
→ Appuyez sur Entrée
Field length [255]:
→ Appuyez sur Entrée
Can this field be null in the database (nullable) (yes/no) [no]:
→ Tapez: no
💡 Pour arrêter d'ajouter des champs, appuyez sur Entrée sans rien taper.
Assurez-vous que votre serveur de base de données (MySQL, PostgreSQL...) est démarré !
php bin/console doctrine:database:create
php bin/console make:migration
C'est un fichier qui contient les instructions SQL pour créer/modifier vos tables. C'est comme un "historique des modifications" de votre base de données.
php bin/console doctrine:migrations:migrate
Votre table user est créée dans la base de données avec tous les champs !
php bin/console make:controller TestController
#[Route('/test-user', name: 'app_test_user')]
public function testUser(EntityManagerInterface $entityManager): Response
{
// Créer un nouvel utilisateur
$user = new User();
$user->setEmail('test@example.com');
$user->setFirstName('Jean');
$user->setLastName('Dupont');
$user->setPassword('$2y$13$...'); // Password hashé
$user->setRoles(['ROLE_USER']);
// Sauvegarder en base
$entityManager->persist($user);
$entityManager->flush();
return new Response('Utilisateur créé avec l\'ID : ' . $user->getId());
}
On verra dans la prochaine session comment hasher correctement les mots de passe.
Allez sur : http://localhost:8000/test-user
Vous devriez voir : "Utilisateur créé avec l'ID : 1"
Les relations sont partout :
1 → N
Un utilisateur peut écrire plusieurs articles
1 → 1
Un utilisateur a un seul profil
N → N
Plusieurs étudiants dans plusieurs cours
php bin/console make:entity Article
New property name:
→ Tapez: author
Field type (enter ? to see all types):
→ Tapez: relation
What class should this entity be related to?:
→ Tapez: User
Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
→ Tapez: ManyToOne
(Plusieurs articles peuvent avoir le même auteur)
author dans Articlearticles dans User (automatiquement !)#[ORM\ManyToOne(inversedBy: 'articles')]
#[ORM\JoinColumn(nullable: false)]
private ?User $author = null;
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): static
{
$this->author = $author;
return $this;
}
#[ORM\OneToMany(mappedBy: 'author', targetEntity: Article::class)]
private Collection $articles;
public function __construct()
{
$this->articles = new ArrayCollection();
}
public function getArticles(): Collection
{
return $this->articles;
}
💡 Mémotechnique : Le côté avec la clé étrangère (Article) utilise inversedBy
php bin/console make:migration php bin/console doctrine:migrations:migrate
#[Route('/test-article', name: 'app_test_article')]
public function testArticle(
EntityManagerInterface $entityManager,
UserRepository $userRepository
): Response {
// Récupérer un utilisateur existant
$user = $userRepository->findOneBy(['email' => 'test@example.com']);
// Créer un article
$article = new Article();
$article->setTitle('Mon premier article');
$article->setContent('Contenu super intéressant !');
$article->setCreatedAt(new \DateTimeImmutable());
$article->setAuthor($user); // Lier à l'utilisateur
// Sauvegarder
$entityManager->persist($article);
$entityManager->flush();
return new Response(sprintf(
'Article "%s" créé par %s',
$article->getTitle(),
$article->getAuthor()->getFullName()
));
}
Visitez http://localhost:8000/test-article
Résultat : "Article 'Mon premier article' créé par Jean Dupont"
$user->getArticles() pour récupérer tous les articles d'un utilisateur.
Sans validation, un utilisateur pourrait :
use Symfony\Component\Validator\Constraints as Assert;
class User
{
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank(message: 'L\'email est obligatoire')]
#[Assert\Email(message: 'L\'email {{ value }} n\'est pas valide')]
private ?string $email = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message: 'Le prénom est obligatoire')]
#[Assert\Length(
min: 2,
max: 255,
minMessage: 'Le prénom doit faire au moins {{ limit }} caractères',
maxMessage: 'Le prénom ne peut pas dépasser {{ limit }} caractères'
)]
private ?string $firstName = null;
}
lastName : NotBlank et Length (min: 2, max: 255)birthDate : LessThan('today') - doit être dans le passépassword : Length (min: 6) quand on créera le formulaireC'est comme une alarme qui se déclenche automatiquement :
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks] // Active les callbacks !
class Article
{
#[ORM\Column]
private ?\DateTimeImmutable $createdAt = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $updatedAt = null;
#[ORM\PrePersist] // Avant la première sauvegarde
public function setCreatedAtValue(): void
{
$this->createdAt = new \DateTimeImmutable();
}
#[ORM\PreUpdate] // Avant chaque mise à jour
public function setUpdatedAtValue(): void
{
$this->updatedAt = new \DateTimeImmutable();
}
}
Plus besoin de faire $article->setCreatedAt(new \DateTimeImmutable()) dans votre contrôleur. C'est automatique !
Cause : Vous essayez de créer une table qui existe déjà
Solution :
# Vérifier l'état des migrations php bin/console doctrine:migrations:status # Si besoin, marquer la migration comme exécutée php bin/console doctrine:migrations:version --add --all
Cause : Vous avez oublié de définir une valeur obligatoire
Solution : Vérifiez que tous les champs NOT NULL ont une valeur
// ❌ Mauvais
$user = new User();
$entityManager->persist($user); // Email est null !
// ✅ Bon
$user = new User();
$user->setEmail('test@example.com');
$user->setFirstName('Jean');
// ... autres champs obligatoires
$entityManager->persist($user);
Cause : Problème de relation (clé étrangère)
Solution : Vérifiez que l'entité liée existe
// ❌ Mauvais $article->setAuthor($user); // $user n'est pas en base ! // ✅ Bon $entityManager->persist($user); $entityManager->flush(); // D'abord sauver l'user $article->setAuthor($user);
php bin/console cache:clear
# Créer une nouvelle entité php bin/console make:entity NomEntite # Créer l'entité User (spécial) php bin/console make:user # Ajouter des champs à une entité existante php bin/console make:entity NomEntite
# Créer la base de données php bin/console doctrine:database:create # Créer une migration php bin/console make:migration # Exécuter les migrations php bin/console doctrine:migrations:migrate # Voir le statut des migrations php bin/console doctrine:migrations:status
# Valider le schéma de base de données php bin/console doctrine:schema:validate # Voir toutes les entités php bin/console doctrine:mapping:info # Vider le cache php bin/console cache:clear
commandes.txt dans votre projet avec ces commandes pour les avoir sous la main !
Votre mission est de créer une entité Category qui sera liée aux articles.
Commandes à utiliser dans l'ordre :
php bin/console make:entity Categoryphp bin/console make:migrationphp bin/console doctrine:migrations:migrateLes entités sont les fondations de votre application. Prenez le temps de bien les concevoir, votre futur vous en remerciera !
Conseils de travail en équipe :
Des questions ? C'est le moment ! 🙋♂️🙋♀️