Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Créer un blog - le profil
Dimanche 28 février 2021 16:22

Nous avons dans le précédent article codé les pages, donc la partie CMS de notre blog. Je vous ai alors dit que la série était terminée mais je vous ai aussi sollicité pour des ajouts éventuels. Il y a eu des propositions. J'ai retenu celle de créer une page de profil pour l'utilisateur, pour qu'il puisse changer son nom, son email ou son mot de passe. Ce n'est pas un gros ajout mais ça permet de montrer une fonctionnalité installée par Breeze : la confirmation du mot de passe. Quand l'utilisateur va dans une zone critique du site c'est une bonne idée de lui demander de retaper son mot de passe, même s'il est connecté. On n'est jamais trop prudent !

Vous pouvez télécharger le code final de cet article ici.

Petit correctif

Mais avant de commencer je vous signale un petit bug que j'ai découvert et je suis d'ailleurs surpris que personne ne soit tombé dessus. Dans l'état actuel du code si on veut voir les routes avec Artisan on tombe sur cette erreur :

php artisan route:list -c

   Symfony\Component\HttpKernel\Exception\HttpException



  at E:\laragon\www\monblog\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:1101
    1097▕         if ($code == 404) {
    1098▕             throw new NotFoundHttpException($message);
    1099▕         }
    1100▕
  ➜ 1101▕         throw new HttpException($code, $message, null, $headers);
    1102▕     }
    1103▕
    1104▕     /**
    1105▕      * Register a terminating callback with the application.

  1   E:\laragon\www\monblog\vendor\laravel\framework\src\Illuminate\Foundation\helpers.php:44
      Illuminate\Foundation\Application::abort("", [])

  2   E:\laragon\www\monblog\app\Http\Controllers\Front\CommentController.php:13
      abort()

Que se passe-t-il ? On voit que le souci vient du contrôleur CommentController. Dans le constructeur on a prévu de vérifier que la requête est bien en Ajax avec ce code :

public function __construct()
{
    if(!request()->ajax()) {
        abort(403);
    }
}

Le souci c'est que la commande route:list d'Artisan est obligée d'activer les contrôleurs et n'est bien sûr pas en Ajax, d'où l'erreur rencontrée. On va donc compléter le code pour que ça fonctionne :

if(!app()->runningInConsole() && !request()->ajax()) {
    abort(403);
}
Et maintenant c'est bon !

Les routes

Pour les routes il nous en faut 2 :

use App\Http\Controllers\Auth\RegisteredUserController;

...

// Profile
Route::middleware('auth')->group(function () {
    Route::view('profile', 'auth.profile');
    Route::name('profile')->put('profile', [RegisteredUserController::class, 'update']);
});
On a :
  • une première route pour afficher la vue avec le formulaire
  • une seconde route pour traiter la soumission du formulaire
Deux remarques sur ces routes :

Pour la première c'est une route un peu particulière qui se contente de renvoyer une vue à partir d'une url. C'est bien pratique quand on n'a aucun traitement particulier à effectuer, ce qui est notre cas.

Pour la seconde je vais utiliser le contrôleur existant RegisteredUserController en lui ajoutant une méthode update. Ca m'a paru judicieux puisque c'est déjà le contrôleur chargé d'enregistrer les utilisateurs.

Les deux routes sont réservées aux utilisateurs authentifiés (middleware auth).

Le contrôleur

Donc dans le contrôleur RegisteredUserController on ajoute la méthode update :
public function update(Request $request)
{
    $values = $request->only(['name', 'email']);

    $rules = [
        'name' => 'required|max:255|unique:users,name,' . $request->user()->id,
        'email' => 'required|email|max:255|unique:users,email,' . $request->user()->id,
    ];

    if($request->password) {
        $rules['password'] = 'string|confirmed|min:8';
        $values['password'] =  Hash::make($request->password);
    }

    $request->validate($rules);

    $request->user()->update($values);
    
    return back()->with('status', __('You have been successfully updated.'));
}

On va rendre la mise à jour du mot de passe optionnelle. Du coup il faut tester la valeur correspondante. Si elle est présente on ajoute l'entrée à la validation et aux valeurs à mettre à jour, en encryptant ce mot de passe.

Il faut compléter le fichier de langue (resources/lang/fr.json) pour le message :

"You have been successfully updated": "Vos modifications ont bien été enregistrées",

La vue

On ajoute la vue pour le formulaire :

@extends('front.layout')

@section('main')
    <div class="row row-x-center s-styles">
        <div class="column large-6 tab-12">

            <!-- Session Status -->
            <x-auth.session-status :status="session('status')" />

            <!-- Validation Errors -->
            <x-auth.validation-errors :errors="$errors" />

            <h3 class="h-add-bottom">@lang('Profile')</h3>
            <form class="h-add-bottom" method="POST" action="{{ route('profile') }}">
                @csrf
                @method('PUT')
                
                <!-- Name -->
                <div>
                  <label for="name">@lang('Name')</label>  
                  <input 
                      id="name" 
                      class="h-full-width" 
                      type="text" 
                      name="name" 
                      placeholder="@lang('Your name')" 
                      value="{{ old('name', auth()->user()->name) }}" 
                      required 
                      autofocus>
                </div>

                <!-- Email Address -->
                 <div>
                  <label for="email">@lang('Email')</label>  
                  <input 
                      id="email" 
                      class="h-full-width" 
                      type="email" 
                      name="email" 
                      placeholder="@lang('Your email')" 
                      value="{{ old('email', auth()->user()->email) }}" 
                      required>
                </div>
                
                <!-- Password -->
                <div>
                    <label for="password">@lang('Password') (@lang('optional'))</label> 
                    <input 
                        id="password" 
                        class="h-full-width" 
                        type="password" 
                        name="password" 
                        placeholder="@lang('Your Password if you want to change it')">
                </div>
                
                <!-- Confirm Password -->
                <div>
                    <label for="password_confirmation">@lang('Confirm Password')</label> 
                    <input 
                        id="password_confirmation" 
                        class="h-full-width" 
                        type="password" 
                        name="password_confirmation" 
                        placeholder="@lang('Confirm your Password')">
                </div>

                <x-auth.submit title="Save" />
                 
            </form>
        </div>
    </div>

@endsection
On retrouve le code des vues de l'authentification.

Il faut encore compléter le fichier de langue (resources/lang/fr.json) et j'en profite pour faire une petite correction en passant (mettez aussi une majuscule à Password dans le composant auth.input.password) :

"optional": "optionnel",

...

"Your Password": "Votre mot de passe",
"Your Password if you want to change it": "Votre mot de passe si vous désirez le changer",

Maintenant avec l'url monblog.ext/profile on obtient le formulaire :

Avec la validation qui fonctionne : Et le message si ça se passe bien :

Le menu

On va mettre à jour le menu pour rendre le formulaire facilement accessible. Pour le moment on a juste un lien pour la déconnexion :

On va plutôt mettre le nom de la personne authentifiée avec un menu déroulant pour accéder soit à la déconnexion, soit au profil. Ca se passe dans la vue front.layout on va remplacer ce code :

<li>                                
    <form action="{{ route('logout') }}" method="POST" hidden>
        @csrf                                
    </form>
    <a 
        href="{{ route('logout') }}"
        onclick="event.preventDefault(); this.previousElementSibling.submit();">
        @lang('Logout')
    </a>
</li>
Par celui-ci :
<li class="has-children">
    <a href="#" title="">{{ auth()->user()->name }}</a>
    <ul class="sub-menu">
        <li>
            <form action="{{ route('logout') }}" method="POST" hidden>
                @csrf                                
            </form>
            <a 
                href="{{ route('logout') }}"
                onclick="event.preventDefault(); this.previousElementSibling.submit();">
                @lang('Logout')
            </a>
        </li>
        <li><a href="{{ url('profile') }}">@lang('Profile')</a></li>
    </ul>
</li>
Avec cet aspect quand on déroule :

La confirmation du mot de passe

Comme je l'ai annoncé en introduction on va protéger le profil avec la confirmation du mot de passe. On profite du fait que Breeze ajoute déjà cette possibilité, il nous suffit de créer une vue :

@extends('front.layout')

@section('main')
    <div class="row row-x-center s-styles">
        <div class="column large-6 tab-12">          

            <p class="h-add-bottom">@lang('This is a secure area of the application. Please confirm your password before continuing.')</p>

            <!-- Validation Errors -->
            <x-auth.validation-errors :errors="$errors" />

            <form class="h-add-bottom" method="POST" action="{{ route('password.confirm') }}">
                @csrf                

                <!-- Password -->
                <x-auth.input-password />

                <x-auth.submit title="Confirm" />              
            </form>
        </div>
    </div>

@endsection

Enfin on ajoute le middleware à nos deux routes pour les protéger :

Route::middleware(['auth', 'password.confirm'])->group(function () {

Maintenant quand on veut accéder au profil on tombe sur cette page :

Et il vaut mieux entrer le bon mot de passe :

Si c'est le cas on va dans le profil.

Conclusion

On n'a eu aucun mal pour ajouter la gestion du profil de l'utilisateur parce que notre code est bien organisé !



Par bestmomo

Nombre de commentaires : 8