20 - Gérer les Permissions et les Accès (Symfony 7)

Temps de lecture : 26 minutes environ.

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éthode editArticle 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.

20 - Gérer les Permissions et les Accès (Symfony 7)
Article publié le

Catégorie : Symfony

Mots-clés : Symfony 7 Tutoriel Symfony 7

Partager : Partager sur Facebook Partager sur Twitter Partager sur LinkedIn