Plusieurs questions Symfony

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je connais bien PHP avec lequel j'ai commencé en amateur depuis 2005, mais depuis peu j'ai décidé de monter en puissance et de me mettre à Symfony. J'ai la version 3.0.1, et je tourne sous WAMP avec PHP 5.5 pour le moment (le site n'est pas encore en ligne). Inévitablement, j'arrive avec plein de questions à mesure que je découvre ce framework, somme toute assez complexe et bien différent des petits bricolages habituels.

Aujourd'hui j'ai donc trois questions, qui sont liés à FOSUserBundle. J'en aurai sûrement d'autres par la suite.

Question 1

J'ai importé le FOSUserBundle pour avoir une gestion des utilisateurs toute faite, et je souhaite écraser les templates par défaut qu'il propose parce que forcément, ils sont ultra basiques. Pour ce faire, j'ai suivi la marche à suivre qui est expliquée ici: https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_templates.html

J'ai donc créé deux fichiers app/resources/views/FOSUserBundle/views/layout.html.twig et app/resources/views/FOSUserBundle/views/Security/login.html.twig. Mais ça ne marche pas. Quand je vais à l'URL /login, je vois toujours le template par défaut.

J'ai essayé de supprimer le dossier var/cache complètement, comme plusieurs réponses stackoverflow le suggèrent. Mais ça ne change rien. J'ai même intégralement copié le dossier /vendor/friendsofsymfony/user-bundle/resources/views dans app/resources/views/FOSUserBundle/views et ai fait des modifications à partir de là pour être sûr d'avoir l'arborescence et les noms des fichiers corrects, mais ça ne fonctionne pas plus. Si je modifie les templates directement dans /vendor/friendsofsymfony/user-bundle/resources/views/, par contre ça fonctionne, je vois bien mes modifications. Mais normalement je ne suis pas censé les modifier à cet endroit.

Où est le problème ? Qu'est-ce que j'ai raté ?

Question 2

J'aimerais bien que les utilisateurs puissent se logger directement sur la page d'accueil, plutôt que de devoir aller sur /login. Quelle est la meilleure façon d'y parvenir ?

Pour le moment j'ai fait ça :

1
2
3
4
5
6
7
8
{# app/resources/views/index.html.twig #}
{% extends 'base.html.twig' %}
{%block body%}
<p>Du blabla qui n'a pas d'utilité ici</p>
{% if not is_granted("IS_AUTHENTICATED_REMEMBERED") %}
{{render(controller('FOSUserBundle:Security:login'))}}
{% endif %}
 {%endblock%}

Ca fonctionne, je peux me logger, mais j'entrevois un problème. Si je vais voir le code source HTML généré, je vois qu'il m'a inclus à double <!DOCTYPE HTML>, <html>, <body> et tout le reste. Logique vu que dans le layout.html.twig importé par login.html.twig ils y sont. Si j'arrive à résoudre la question 1, je devrais pouvoir modifier login.html.twig et grosso modo supprimer cette ligne qui en est responsable :
{% extends "FOSUserBundle::layout.html.twig" %}

Mais alors, si je supprime cette ligne, ça signifie que l'utilisateur qui ira sur /login n'aura que le formulaire brut et plus le reste de la page HTML autour. Pas bon vu qu'on atterrit sur cette page à chaque fois qu'on essaie d'aller sur une page sécurisée et qu'on n'est pas encore connecté.

J'en conclus donc que
{{render(controller('FOSUserBundle:Security:login'))}}
n'est pas la bonne solution pour intégrer le formulaire de login directement sur la page d'accueil.

Cependant, si je fais un include :
{{include('FOSUserBundle/views/Security/login.html.twig')}}
Alors je me ramasse des erreurs m'indiquant que des variables ne sont pas définies, des tokens CSRF par exemple. Donc ça ne doit sûrement pas être la bonne solution non plus.

Quelle est la bonne marche à suivre pour intégrer le formulaire de connexion directement sur la page d'accueil ?

Question bonus, faire en sorte que si on rentre un mauvais login, on reste sur la page où était le formulaire (qui ne sera pas forcément la page d'accueil dans le futur) et qu'on ne soit pas renvoyé vers /login dans ce cas.

Question 3 (résolue)

Réponse dans ce post

Mon site a vocation d'être traduit en plusieurs langues, j'ai donc activé le module de traduction. Il fonctionne très bien (je n'ai pas encore pigé comment utiliser transchoice, mais sinon ça va). JE voudrais que la page de login soit aussi traduisible, et donc qu'elle soit à /{_locale}/login au lieu de /login.

Si je copie le /vendor/friendsofsymfony/user-bundle/resources/config/routing/all.xml fourni avec FOSUserBundle, que je le modifie comme suit, et que j'indique le fichier de configuration mis à jour dans app/resources/routing.yml, j'obtiens bien le formulaire de connexion dans la bonne langue en allant sur /fr/login, /en/login, etc. et /login tout court ne fonctionne logiquement plus :

1
2
    <import
        resource="@FOSUserBundle/Resources/config/routing/security.xml" prefix="/{_locale}/login" />

Par contre, si j'essaie d'aller sur une page protégée sans être connecté, je suis toujours renvoyé vers /login pour m'identifier, et c'est donc une erreur 404 que j'obtiens à la place du formulaire de login. Il faut modifier security.yml pour lui indiquer la bonne page de login :

1
2
3
4
5
6
# app/resources/config/security.yml
            form_login: 
                provider: fos_userbundle 
                login_path: ici le nouveau chemin à la place de /login
                use_referer: true
                csrf_token_generator: security.csrf.token_manager 

D'après ma compréhension de Symfony jusqu'ici, je pense avoir essayé assez logiquement :
login_path: "/{_locale}/login"
et
login_path: "/%_locale%/login"
mais ça ne fonctionne pas, il ne semble pas interpréter les variables.

Evidemment il faut que si j'essaie d'aller sur /fr/admin, je sois redirigé vers /fr/login pour me connecter, et similairement, avec /en/admin qui doit me renvoyer sur /en/login.

Je pourrais probablement ajouter une route alias sur /login pointant sur le même contrôleur pour m'éviter cette configuration, mais alors une autre question se pose: comment savoir en quelle langue le formulaire doit s'afficher ? Apparament il ne stocke pas la langue dans la session, il ne se base que sur l'URL; donc s'il n'y a pas de langue spécifiée dans l'URL, il s'affichera toujours en anglais par défaut.

Comment faire ?

Merci pour vos réponses

P.S. Merci de ne pas troller en me disant de passer à Django parce que python c'est bien et PHP c'est pourri. C'est un choix assumé.

+0 -0

Salut !

Pour le point 1, j'ai déjà eu un cas similaire, mais comme je réponds alors que je viens de voir ta question et que je n'ai pas le code sous la main, ça attendra que je puisse aller regarder  :D
Du peu que je me souviens, la structure ne doit pas être exactement la même entre le bundle et le dossier app, un des niveaux disparaît.

Pour le point 2, je crois que tu pourrais tester si la requête est interne (car Symfony, quand tu utilises {{ render() }}, s'envoie une nouvelle requête), et étendre une base ou l'autre selon le résultat. La base quand c'est une requête interne peut être complètement vide  ;)

Pour le troisième point, si je me souviens bien, tu peux spécifier le nom d'une route plutôt que simplement un chemin dans login_path.

+1 -0

Pour le troisième point, si je me souviens bien, tu peux spécifier le nom d'une route plutôt que simplement un chemin dans login_path.

Je me suis par conséquent empressé d'essayer:
login_path: fos_user_security_login

Et ça fonctionne ! Merci ! Voilà déjà un problème résolu.

Pour le point 2, je crois que tu pourrais tester si la requête est interne (car Symfony, quand tu utilises {{ render() }}, s'envoie une nouvelle requête), et étendre une base ou l'autre selon le résultat. La base quand c'est une requête interne peut être complètement vide 

Par contre ici j'en doute, on peut lire ça dans la doc:

If you use {% extends %} in a template, it must be the first tag in that template;

Vu que {%extends%} doit être la première instruction, j'en déduis qu'on ne peut pas mettre de condition avant. Et en fait c'est logique, en PHP comme dans n'importe quel autre langage OO, on ne peut pas étendre une classe A ou une classe B selon une condition; ça n'a aucun sens.

De toute façon, tu viens de me dire que quand tu utilises {{ render() }}, s'envoie une nouvelle requête. Par conséquent niveau performance ça doit probablement être pourri non ? Donc ce n'est sûrement pas la bonne solution à long terme.

Par ailleurs, c'est assez étonnant qu'il n'y ait pas de LoginFormType à disposition. Si on va voir dans /vendor/friendsofsymfony/user-bundle/Form/Type, on trouve des formulaires prêts à être réutilisés pour créer un compte, pour changer ou demander un nouveau mot de passe, pour éditer son profil, mais pas pour se logger. Je trouve que c'est assez curieux. Ca serait sûrement plus facile !

Question 3 résolue, mais je risque d'en ajouter d'autres avant la fin de la journée.

+0 -0

Salut,

Pour le point 2, je pourrais te proposer de faire le formulaire de login dans un fichier autre, par exemple form_login.html.twig, ensuite tu l’inclue dans ton fichier login.html.twig, et ainsi sur ta page d’accueil aussi tu fais un autre include de ce fichier form_login.html.twig.

+0 -0

Pour le point 2, je pourrais te proposer de faire le formulaire de login dans un fichier autre, par exemple form_login.html.twig, ensuite tu l’inclue dans ton fichier login.html.twig, et ainsi sur ta page d’accueil aussi tu fais un autre include de ce fichier form_login.html.twig.

J'ai essayé de copier le code du formulaire de login proposé par défaut dans un autre fichier comme tu le préconises, mais ça me donne des erreurs. IL y a entres autres une variable de token CSRF qui n'est pas définie.

Du coup il y a sûrement aussi quelque chose à ajouter/modifier dans le contrôleur, encore faut-il savoir quoi exactement.

+0 -0

Tu peux tout à fait faire {% extends (app.request.internal ? '::base_internal.html.twig' : '::base.html.twig') %} (j'ai mis app.request.internal au bol), c'est un des points qui était mentionné dans la documentation de Symfony pour les extensions conditionnelles.

Mais oui, le fait d'utiliser {{ render() }} est pas terrible au niveau performances, seulement c'est un peu la seule solution que tu aies pour pouvoir avoir une logique séparée et réutilisable partout. Après, il y a les services, mais cela implique de créer aussi un listener quelconque plutôt que de l'appeler dans chaque action, j'imagine.

Edit

Apparemment, le test serait app.request.parentRequest is not null pour savoir si c'est une requête interne ou pas.

En revanche, désolé, je me souvenais mal, je n'ai pas de projet où j'ai étendu FOSUserBundle sans créer un bundle enfant.

+0 -0

OK donc pour la question 2 je crois qu'on a fait le tour, merci.

En revanche, désolé, je me souvenais mal, je n'ai pas de projet où j'ai étendu FOSUserBundle sans créer un bundle enfant.

JE ne l'ai pas réellement étendu, j'ai simplement fait la procédure décrite pour bénéficier de la gestion des utilisateurs. Les seuls endroits où FOSUserBundle est déclaré c'est dans le app kernel et quelque part dans la config.

Il ne reste donc plus qu'à savoir pourquoi l'écrasement des templates ne fonctionne pas. En dernier recours je passe en mode bidouille donc modification directe, mais ce serait dommage, quand on utilise un framework comme Symfony c'est justement pour arrêter de faire de la bidouille à 2€.

+0 -0

Bon, finalement pour ma question 1, j'ai été obligé d'utiliser la technique n°2 décrite comme compliquée sur la page du book: ajouter une méthode getParent dans la classe AppBundle. C'est la seule solution qui marche et je n'ai toujours pas compris pourquoi la solution n°1 (facile) ne fonctionne pas.

Mais bon, cette solution n°2 fonctionne donc on va dire que le problème est résolu. JE risque d'être embêté si je décide d'ajouter un login par FB/G+/Twitter.

Pour ma question n°2, j'ai finalement copié le template du formulaire afin de faire un include simple, et j'ai aussi copié le code de SecurityController::loginAction. Sur chaque page où je veux afficher le formulaire de login, j'ai créé une méthode getLoginFormParams dans un BaseController qui me renvoie les paramètres à ajouter à $this->render pour que le token CSRF puisse fonctionner. C'est peut-être moins pro que d'utiliser la fonction render de twig, mais ça marche et ça ne fait pas de requête supplémentaire.

J'en ai profité pour faire en sorte que quand on se plante de login, on ne se retrouve pas sur la page de connexion mais qu'on reste sur la page où il y a le formulaire avec lequel on a essayé de se connecter. ON ne voit la page de connexion que quand on essaie d'aller sur une page protégée nécéssitant de se logger.

Voilà ! Je clos donc ce sujet ici. Si j'ai d'autres questions sur Symfony, elles iront dans un nouveau. Merci pour votre aide.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte