Le site se compose de diverses sections, incluant les biens, les propriétaires, les types de biens, les contacts et autres éléments clés.
Mon rôle principal concerne les propriétaires et le développement d’un formulaire de connexion. Nous aborderons chaque caractéristique en détaillant la navigation sur le site, la gestion des propriétés, les interactions avec les propriétaires, l’exploration des différentes catégories de biens, et plus encore.
Pour conclure, nous dresserons un bilan des leçons apprises au cours de ce projet, en soulignant les points forts, les défis rencontrés et les solutions adoptées. Cette analyse nous aidera à comprendre nos apprentissages et à identifier des opportunités d’amélioration pour l’avenir.
L’agence immobilière Immo’Bill, basée à Pau, envisage de développer un site web pour exposer ses biens immobiliers à la vente. Ce site offrira aux visiteurs la possibilité de parcourir les propriétés disponibles et d’obtenir des détails sur chacune d’elles. En parallèle, il est prévu un espace dédié à l’équipe pour l’ajout de nouveaux biens, la gestion des membres, des propriétaires, et des différentes catégories de propriétés.
Pour satisfaire ces exigences, il est essentiel d’intégrer un formulaire de connexion. Ce formulaire permettra aux utilisateurs de se connecter en tant qu’administrateur ou membre standard, garantissant ainsi la sécurité et une gestion efficace du site.
Il est important de s’assurer que l’administrateur accède à ses droits spécifiques uniquement après s’être connecté. Pour garantir cette sécurité, nous installerons un formulaire de connexion exclusivement réservé aux administrateurs. Ce processus leur permettra d’accéder aux fonctionnalités exclusives après une authentification réussie. Nous aborderons aussi la création d’adresses e-mail, la génération de mots de passe sécurisés, et le développement d’un formulaire de connexion approprié.
Avant de développer le formulaire de connexion, j’ai commencé par mettre en place une entité « User » en utilisant la commande appropriée
Symfony console make:user
Après avoir répondu aux questions de la console, comme le nom de l’entité, l’utilisation de Doctrine, et la sécurisation du mot de passe, et suite à la mise à jour de la table dans la base de données, j’ai procédé à l’ajout de deux utilisateurs : un administrateur doté du rôle « Rôle_Admin » et un utilisateur standard.
Le mot de passe a une apparence inhabituelle car il est haché. En effet, cela évite d’afficher
le mot de passe en clair, ce qui renforce la sécurité. Pour créer un mot de passe haché, j’ai
utilisé la commande
composer require symfony/password-hasher
Pour créer un formulaire de connexion, nous utiliserons une commande spécifique qui génère automatiquement les fichiers et contrôleurs nécessaires.
Symfony console make:auth
Des fichiers sont créés, on les modifiera par la suite. Ces fichiers devront être modifiés pour améliorer leur rendu visuel et rediriger les utilisateurs
connectés vers les bons endroits. De plus, nous pouvons afficher les outils nécessaires aux
utilisateurs, tels que les outils de gestion pour l’administrateur.
symfony console make:auth
What style of authentication do you want? [Empty authenticator]:
[0] Empty authenticator
[1] Login form authenticator
> 1
The class name of the authenticator to create (e.g. AppCustomAuthenticator):
> LoginFormAuthenticator
Choose a name for the controller class (e.g. SecurityController) [SecurityController]:
> SecurityController
Do you want to generate a '/logout' URL? (yes/no) [yes]:
> yes
created: src/Security/LoginFormAuthenticator.php
updated: config/packages/security.yaml
created: src/Controller/SecurityController.php
created: templates/security/login.html.twig
{% if app.user %}
Connecté en tant que
{{ app.user.userIdentifier }}
-
Déconnexion
{% endif %}
Hey! Veuillez vous connecter.
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('accueil'));
// throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
Maintenant que l’administrateur est configuré et que le formulaire de connexion est sécurisé, nous pouvons aborder la manière d’afficher des fonctionnalités spécifiques en fonction de ses rôles. Cela inclut la gestion des biens, des membres, et des propriétaires. Dans l’interface du menu, les utilisateurs standards verront des options limitées. Une condition spécifique est intégrée pour que seule une personne avec le rôle d’administrateur puisse accéder à la gestion des membres.
{% if app.user %}
Gestion
-
Biens
-
Propriétaires
-
Types
{% if is_granted('ROLE_ADMIN') %}
-
Membres
{% endif %}
{% endif %}
En conclusion, le formulaire de connexion a été mis en place avec succès, garantissant l’attribution des droits appropriés aux différents utilisateurs. Un administrateur bénéficie de droits exclusifs après s’être connecté via un formulaire spécialement conçu à cet effet. Le processus a inclus l’établissement d’une entité utilisateur avec des rôles définis, la sécurisation des mots de passe par hachage, la mise en place du formulaire de connexion avec Symfony, et l’adaptation de l’affichage selon les rôles de chaque utilisateur.
La sécurité CSRF a également été intégrée pour protéger le formulaire contre les attaques de falsification de requêtes intersites. Des fonctionnalités spéciales, comme la gestion des membres, sont exclusivement accessibles aux administrateurs, grâce à une gestion conditionnelle des droits dans l’interface du menu.
class AdminProprioController extends AbstractController
{
#[Route('/admin/proprietaires', name: 'lesProprietairesAdmin')]
public function index(ProprietairesRepository $lesProprio): Response
{
$lesProprio = $lesProprio->findAll();
return $this->render('admin/admin_proprio/adminProprio.html.twig', [
'lesProprio' => $lesProprio,
]);
}
}
La route admin/propriétaires est ce qui s’affichera dans notre barre d’URL. La fonction
Index() a pour paramètre $lesProprio de la classe ProprietairesRepository. Cette classe
possède la méthode findAll(), qui récupère toutes les données liées à la classe Proprietaire,
agissant ainsi sur les données des propriétaires. La variable $lesProprio contient les
données récupérées.
return $this->render('admin/admin_proprio/adminProprio.html.twig', [
'lesProprio' => $lesProprio,
]);
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Proprietaires::class);
}
return $this->render('admin/admin_proprio/adminProprio.html.twig', [
'lesProprio' => $lesProprio,
]);
class Proprietaires
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $nom = null;
class AdminProprioController extends AbstractController
{
#[Route('/admin/proprietaires', name: 'lesProprietairesAdmin')]
public function index(ProprietairesRepository $lesProprio): Response
{
$lesProprio = $lesProprio->findAll();
return $this->render('admin/admin_proprio/adminProprio.html.twig', [
'lesProprio' => $lesProprio,
]);
}
}
Une fois les données récupérées, la fonction redirige l’utilisateur vers une interface dont le
chemin est spécifié dans le rendu (render). Dans ce cas, l’utilisateur sera dirigé vers
« adminProprio.html.twig », qui représente la vue globale des propriétaires. Il est important
d’afficher les données récupérées, qui sont stockées dans la variable $lesProprio, dans
cette vue.
En résumé, après avoir sélectionné l’option « Propriétaire » dans le menu, nous avons été redirigés vers « lesProprietairesAdmin ». Au sein de cette fonction, nous avons récupéré les données relatives aux propriétaires, les avons stockées dans une variable, puis les avons présentées dans la vue. Désormais, plusieurs actions sont à notre disposition, notamment Ajouter, Modifier et Supprimer.
#[Route('/admin/proprietaire/ajout', name: 'admin_proprio_ajout')]
#[Route('/admin/proprietaire/modifier/{id}', name: 'admin_proprio_modification')]
//, methods: ['GET', 'POST']
public function ajoutEtModif(Proprietaires $lesProprio = null, Request $request, EntityManagerInterface $manager): Response
{
if (!$lesProprio) {
$lesProprio = new Proprietaires();
}
$form = $this->createForm(ProprioType::class, $lesProprio);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$manager->persist($lesProprio);
$manager->flush();
$this->addFlash('success', 'Action effectué avec succès!');
return $this->redirectToRoute("lesProprietairesAdmin");
}
return $this->render('admin/admin_proprio/modificationProprio.html.twig', [
'proprio' => $lesProprio,
'form' => $form->createView(),
"isModification" => $lesProprio->getId() !== null
]);
}
La redirection se fait vers `admin_proprio_ajout`, qui exploite la fonction `ajoutEtModif()`. Cette fonction est versatile, car elle permet à la fois d’ajouter et de modifier des éléments :
1. Ajout : Si aucun objet « Proprietaires » n’est fourni, cela indique qu’il s’agit d’une opération d’ajout. Dans ce cas, un nouvel objet « Proprietaires » est créé pour permettre l’insertion de nouvelles données.
2. Modification : Si un objet « Proprietaires » est déjà fourni, cela signifie qu’il s’agit d’une modification. La fonction utilise alors cet objet existant pour remplir préalablement le formulaire de modification avec les données existantes.
Dans le contexte actuel, nous nous orientons principalement vers la première option, c’est-à-dire l’ajout d’un nouveau propriétaire. Nous serons donc redirigés vers notre formulaire qui, initialement, sera vide. Il est important de noter que dans notre structure de fichiers, le formulaire destiné aux propriétaires est nommé `ProprioType`.
Dans ce cas, la variable `$form` sera instanciée à l’intérieur de la classe `ProprioType`. Elle intégrera les champs spécifiés dans cette classe, tels que le nom, le prénom, l’e-mail, le téléphone et l’adresse. Ces champs seront utilisés pour collecter les informations nécessaires lors de la création ou de la modification d’un propriétaire dans le système.
$form = $this->createForm(ProprioType::class, $lesProprio);
$form->handleRequest($request);
class ProprioType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom', TextType::class)
->add('prenom', TextType::class)
->add('mail', TextType::class)
->add('tel', TextType::class)
->add('adresse', TextType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Proprietaires::class,
]);
}
}
Nous serons dirigés vers la vue `modificationProprio.html.twig` pour notre formulaire. Il est essentiel de noter que, malgré son nom, cette vue ne se limite pas uniquement à la modification. Nous l’utilisons également pour ajouter un nouveau propriétaire. Étant donné que l’objet `Proprietaires` n’est pas fourni dans ce cas, nous savons qu’il s’agit d’un ajout, ce qui signifie que le formulaire sera initialement vide, prêt à accueillir les informations du nouveau propriétaire.
if (!$lesProprio) {
$lesProprio = new Proprietaires();
}
$form = $this->createForm(ProprioType::class, $lesProprio);
$form->handleRequest($request);
'form' => $form->createView(),
{{ form_start(form) }}
{{ form_widget(form) }}
Retour
{{ form_end(form) }}
Lorsque les données sont renseignées et validées, la variable `$manager` de la classe `EntityManagerInterface` joue un rôle clé. Elle permet de transférer les données collectées dans le formulaire, stockées temporairement dans la variable `$lesProprio`, vers la base de données. Ce processus garantit que toutes les informations saisies soient correctement enregistrées et gérées au sein de la base de données.
if ($form->isSubmitted() && $form->isValid()) {
$manager->persist($lesProprio);
$manager->flush();
$this->addFlash('success', 'Action effectué avec succès!');
return $this->redirectToRoute("lesProprietairesAdmin");
}
Lorsque l’action est effectuée, un message est affiché pour confirmer visuellement à l’utilisateur que l’opération a été réalisée avec succès. Cela aide à fournir une réponse claire et immédiate, renforçant la convivialité de l’interface utilisateur.
Notre ajout a bien été effectué.
En résumé, le bouton AJOUTER nous redirige vers la fonction `ajoutEtModif()`, qui est conçue pour remplir deux fonctions simultanément. Si l’objet `propriétaire` est vide, cela signifie que le formulaire présenté sera également vide, indiquant qu’il s’agit d’un formulaire d’ajout pour un nouveau propriétaire. À l’inverse, si l’objet `propriétaire` contient déjà des données, le formulaire sera prérempli avec ces informations, transformant ainsi notre interaction en un formulaire de modification pour un propriétaire existant. Cela permet de passer aisément à l’étape de modification.
Quand on souhaite modifier un propriétaire, il est important de se concentrer uniquement sur ses informations. Pour garantir cela, nous utilisons son identifiant unique spécifié dans le propriétaire. Lorsque l’utilisateur clique sur un propriétaire, son identifiant est récupéré via `leProprio.id`. Cet identifiant est essentiel pour cibler uniquement les données de ce propriétaire spécifique. Après que l’utilisateur a cliqué sur Modifier et que l’identifiant a été récupéré, il est redirigé vers le formulaire précédent. La différence est que le formulaire est déjà prérempli avec les données récupérées grâce à l’identifiant. La fonction `ajoutEtModif()` utilise la méthode GET pour récupérer l’identifiant du propriétaire, comme indiqué dans l’URL.
"isModification" => $lesProprio->getId() !== null
La variable `isModification` sert de signal pour indiquer qu’il s’agit d’une opération de modification, ce qui permet d’implémenter des conditions spécifiques dans notre code. Comme observé précédemment, cette variable est utilisée dans le code de notre vue. Lors d’une modification, le titre « Modification de Nom Prénom » est ajouté, clarifiant pour l’utilisateur qu’il est en train de mettre à jour les informations d’un propriétaire existant.
{% if isModification %}
Modification de
{{ proprio.nom }}
{{ proprio.prenom }}
{% else %}
Ajout d'un propriétaire
{% endif %}
Pour se prémunir contre les attaques CSRF (Cross-Site Request Forgery), une menace où un attaquant peut amener un utilisateur légitime à effectuer des actions involontaires sur un site web, Symfony met en place un mécanisme de défense robuste. Lors de la création de formulaires, Symfony génère un jeton CSRF, un identifiant unique qui accompagne chaque formulaire. Ce jeton est impératif lors de la soumission du formulaire; il est vérifié pour authentifier que la requête provient bien de l’utilisateur attendu. Si le jeton est invalide ou absent, la soumission du formulaire est bloquée, empêchant ainsi l’exécution de l’action non désirée.
Pour activer cette protection dans Symfony, nous avons utilisé une commande spécifique qui configure et intègre la sécurité CSRF dans nos formulaires.
composer require symfony/security-bundle
Dans notre code sur le Template il nous reste le bouton supprimer à mettre en action, analysons ce bouton
Lorsqu’on sélectionne l’option « Supprimer », la redirection se fait vers `admin_proprio_suppression`, qui déclenche à son tour la fonction `suppression()`. Le processus pour identifier le propriétaire spécifique à supprimer est similaire à celui utilisé pour la modification. En effet, nous récupérons également l’ID du propriétaire via la méthode GET. Cette méthode permet d’assurer que nous manipulons les données du bon propriétaire, en évitant toute action sur un mauvais enregistrement.
#[Route('/admin/proprietaire/supprimer/{id}', name: 'admin_proprio_suppression', methods:"delete")]
public function suppression(Proprietaires $proprio=null, Request $request, EntityManagerInterface $manager): Response
{
if($this->isCsrfTokenValid("SUP".$proprio->getId(), $request->get('_token'))){
$manager->remove($proprio);
$manager->flush();
$this->addFlash("success", "Suppression effectuée");
}
else{
$this->addFlash("failed", "Suppression non effectuée");
}
return $this->redirectToRoute("lesProprietairesAdmin");
}
Après avoir récupéré l’ID du propriétaire, tout comme dans le processus de modification, nous appliquons une condition qui vérifie la validité du jeton CSRF. Cette étape est importante pour assurer la sécurité de l’opération en confirmant que la requête provient bien de l’utilisateur authentique et qu’elle n’est pas le résultat d’une manipulation externe. Si le jeton CSRF est valide, nous procédons à la suppression; dans le cas contraire, l’action est refusée pour prévenir toute action non autorisée.
if($this->isCsrfTokenValid("SUP".$proprio->getId(), $request->get('_token')))
Si le jeton CSRF est confirmé comme valide, nous procédons à la suppression de l’entité spécifiée en utilisant la variable `$manager` avec la méthode `remove()` pour marquer l’entité pour suppression. Une fois l’entité marquée, nous appelons la méthode `flush()` pour appliquer effectivement les modifications dans la base de données. Si cette opération se déroule sans accroc, un message de succès est alors affiché, informant l’utilisateur que la suppression a été effectuée avec succès.
$manager->remove($proprio);
$manager->flush();
$this->addFlash("success", "Suppression effectuée");
Sinon, un message d’échec est affiché.
else{
$this->addFlash("failed", "Suppression non effectuée");
}
Pour finir l’utilisateur est redirigé vers notre liste des propriétaires via lesProprietairesAdmin comme vue à la première étape.
return $this->redirectToRoute("lesProprietairesAdmin");
Pour un exemple, prenons pour cobaye Chloé.
Il faut retenir que avant chaque suppression, notre interface affichera un message de confirmation à cette suppression.
Une fois confirmé la suppression, un message comme pour les autres va s’afficher.
Durand a bien été modifié.
La gestion des propriétaires dans notre application est structurée autour d’un processus clair et bien défini. Dès que l’administrateur se connecte, il a la possibilité de consulter les données des propriétaires, d’en ajouter de nouveaux, de modifier leurs informations existantes, et de supprimer ceux qui ne sont plus nécessaires. Cette suite d’actions assure une administration efficace et sécurisée des données des propriétaires au sein de l’application.
© Eren Varli