Série : Symfony 7
Fichiers : https://github.com/NouvelleTechno/OpenBlog
Dans ce tutoriel, nous allons créer un système de gestion d'articles de blog en utilisant Symfony 7. Nous allons couvrir la configuration de la sécurité, la création d'un contrôleur pour modifier les articles, et la mise en place d'un voter pour gérer les permissions. Nous allons également créer une vue pour afficher les articles de l'utilisateur.
Étape 1 : Configuration de la Sécurité
Commençons par configurer les rôles et les permissions dans le fichier security.yaml
.
security.yaml
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profil, roles: ROLE_USER }
Explication
- access_control : Cette section définit les règles d'accès pour différentes parties de l'application.
- { path: ^/profil, roles: ROLE_USER } : Cette règle indique que seuls les utilisateurs avec le rôle
ROLE_USER
peuvent accéder aux pages commençant par/profil
.
Étape 2 : Création du Contrôleur pour Modifier les Articles
Ensuite, nous allons créer un contrôleur pour modifier les articles de blog. Ce contrôleur sera responsable de la gestion des formulaires et de la mise à jour des articles dans la base de données.
PostsController.php
namespace App\Controller;
use App\Entity\Posts;
use App\Form\AddPostFormType;
use App\Repository\PostsRepository;
use App\Service\PictureService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\String\Slugger\SluggerInterface;
class PostsController extends AbstractController
{
// Contenu déjà existant
#[Route('/modifier/{id}', name: 'edit')]
public function editArticle(
$id,
Request $request,
SluggerInterface $slugger,
EntityManagerInterface $em,
PostsRepository $postsRepository,
PictureService $pictureService
): Response
{
$post = $postsRepository->find($id);
// Si on n'a pas de post
if (!$post) {
return $this->redirectToRoute('app_profile_index');
}
// On vérifie les permissions
$this->denyAccessUnlessGranted('POST_EDIT', $post);
$form = $this->createForm(AddPostFormType::class, $post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug(strtolower($slugger->slug($post->getTitle())));
$post->setUsers($this->getUser());
$featuredImage = $form->get('featuredImage')->getData();
$image = $pictureService->square($featuredImage, 'articles', 300);
$post->setFeaturedImage($image);
$em->persist($post);
$em->flush();
$this->addFlash('success', 'L\'article a été créé');
return $this->redirectToRoute('app_profile_posts_index');
}
return $this->render('profile/posts/add.html.twig', [
'form' => $form->createView(),
]);
}
}
Explication
Route et Méthode :
#[Route('/modifier/{id}', name: 'edit')]
public function editArticle(...): Response
#[Route('/modifier/{id}', name: 'edit')]
: Définit la route pour modifier un article avec l'ID de l'article en paramètre.public function editArticle(...): Response
: Définit la méthodeeditArticle
qui prend plusieurs paramètres et retourne une réponse.
Récupération de l'Article :
$post = $postsRepository->find($id);
- Récupère l'article avec l'ID donné à partir du repository.
Vérification de l'Existence de l'Article :
if (!$post) {
return $this->redirectToRoute('app_profile_index');
}
- Si l'article n'existe pas, redirige vers la page de profil.
Vérification des Permissions :
$this->denyAccessUnlessGranted('POST_EDIT', $post);
- Vérifie si l'utilisateur a la permission de modifier l'article.
Création du Formulaire :
$form = $this->createForm(AddPostFormType::class, $post);
- Crée un formulaire pour modifier l'article.
Traitement du Formulaire :
$form->handleRequest($request);
- Traite la requête pour le formulaire.
Soumission et Validation du Formulaire :
if ($form->isSubmitted() && $form->isValid()) {
...
}
- Vérifie si le formulaire a été soumis et est valide.
Mise à Jour de l'Article :
$post->setSlug(strtolower($slugger->slug($post->getTitle())));
$post->setUsers($this->getUser());
$featuredImage = $form->get('featuredImage')->getData();
$image = $pictureService->square($featuredImage, 'articles', 300);
$post->setFeaturedImage($image);
$em->persist($post);
$em->flush();
- Met à jour le slug, l'utilisateur, et l'image de l'article.
- Persiste et flush l'article mis à jour dans la base de données.
Redirection et Message de Succès :
$this->addFlash('success', 'L\'article a été créé');
return $this->redirectToRoute('app_profile_posts_index');
- Ajoute un message de succès et redirige vers la page des articles de l'utilisateur.
Rendu du Formulaire :
return $this->render('profile/posts/add.html.twig', [
'form' => $form
]);
- Rend la vue du formulaire.
Étape 3 : Création du Voter pour les Permissions
Nous allons maintenant créer un voter pour gérer les permissions de modification et de suppression des articles.
PostsVoter.php
namespace App\Security\Voter;
use App\Entity\Posts;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
class PostsVoter extends Voter
{
public const EDIT = 'POST_EDIT';
public const DELETE = 'POST_DELETE';
public function __construct(private readonly Security $security){}
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE]) && $subject instanceof Posts;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
if($this->security->isGranted('ROLE_ADMIN')) return true;
switch ($attribute) {
case self::EDIT:
case self::DELETE:
// On vérifie si l'utilisateur est l'auteur du post
return $this->isOwner($subject, $user);
}
return false;
}
private function isOwner(Posts $post, $user): bool
{
return $user === $post->getUsers();
}
}
Explication
Constantes et Constructeur :
public const EDIT = 'POST_EDIT';
public const DELETE = 'POST_DELETE';
public function __construct(private readonly Security $security){}
- Définit les constantes pour les attributs de voter et le constructeur pour injecter le service de sécurité.
Méthode supports :
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE]) && $subject instanceof Posts;
}
- Vérifie si l'attribut et le sujet sont supportés par le voter.
Méthode voteOnAttribute :
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
if($this->security->isGranted('ROLE_ADMIN')) return true;
switch ($attribute) {
case self::EDIT:
case self::DELETE:
return $this->isOwner($subject, $user);
}
return false;
}
- Vérifie les permissions en fonction de l'attribut et du sujet.
- Si l'utilisateur est un administrateur, il a toujours la permission.
- Sinon, vérifie si l'utilisateur est l'auteur de l'article.
Méthode isOwner :
private function isOwner(Posts $post, $user): bool
{
return $user === $post->getUsers();
}
- Vérifie si l'utilisateur est l'auteur de l'article.
Étape 4 : Création de la Vue pour Afficher les Articles de l'Utilisateur
Enfin, nous allons créer une vue pour afficher les articles de l'utilisateur.
profile/index.html.twig
{% extends "base.html.twig" %}
{% block title %}Profil de {{ app.user.nickname }}{% endblock %}
{% block body %}
<main class="container">
<section>
<h1>Profil de {{ app.user.nickname }}</h1>
<h2 class="text-center my-5">Tous vos articles</h2>
<div class="grid">
{% for post in app.user.posts %}
<article class="card">
<img src="{{ asset('uploads/articles/mini/300x300-') ~ post.featuredImage }}" alt="{{ post.title }}">
<div class="card-body">
{% for category in post.categories %}
<span class="badge badge-primary">{{ category.name }}</span>
{% endfor %}
<h3><a href="{{ path('app_posts_details', {slug: post.slug}) }}">{{ post.title }}</a></h3>
<p>{{ post.users.nickname }}</p>
</div>
<div class="card-footer">
<a href="{{ path('app_profile_posts_edit', {id: post.id}) }}">Modifier</a>
</div>
</article>
{% endfor %}
</div>
</section>
</main>
{% endblock %}
Explication
Extension de la Vue de Base :
{% extends "base.html.twig" %}
- Étend la vue de base pour inclure le contenu commun.
Bloc Title :
{% block title %}Profil de {{ app.user.nickname }}{% endblock %}
- Définit le titre de la page avec le pseudonyme de l'utilisateur.
Bloc Body :
{% block body %}
<main class="container">
<section>
<h1>Profil de {{ app.user.nickname }}</h1>
<h2 class="text-center my-5">Tous vos articles</h2>
<div class="grid">
{% for post in app.user.posts %}
<article class="card">
<img src="{{ asset('uploads/articles/mini/300x300-') ~ post.featuredImage }}" alt="{{ post.title }}">
<div class="card-body">
{% for category in post.categories %}
<span class="badge badge-primary">{{ category.name }}</span>
{% endfor %}
<h3><a href="{{ path('app_posts_details', {slug: post.slug}) }}">{{ post.title }}</a></h3>
<p>{{ post.users.nickname }}</p>
</div>
<div class="card-footer">
<a href="{{ path('app_profile_posts_edit', {id: post.id}) }}">Modifier</a>
</div>
</article>
{% endfor %}
</div>
</section>
</main>
{% endblock %}
- Définit le contenu principal de la page.
- Affiche le pseudonyme de l'utilisateur et tous ses articles.
- Pour chaque article, affiche l'image, les catégories, le titre, l'auteur, et un lien pour modifier l'article.
Conclusion
Vous avez maintenant un système complet pour gérer les articles de blog avec Symfony 7. Vous avez configuré la sécurité, créé un contrôleur pour modifier les articles, mis en place un voter pour gérer les permissions, et créé une vue pour afficher les articles de l'utilisateur. Vous pouvez personnaliser davantage ce système en fonction de vos besoins spécifiques.
Obtenir de l'aide
Pour obtenir de l'aide, vous pouvez accéder au serveur Discord pour une entraide par chat.
