Laravel 8

Créer un blog – le profil

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é !

Print Friendly, PDF & Email

4 commentaires

  • braice

    Salut,

    Merci pour ce tuto très complet et tes explications qui sont très claires.

    N’ayant jamais déployé un site laravel, est-ce que ce serait un point que tu pourrais faire ? Idem un exemple de respect du RGPD européen pourrait être intéressant.

    Bonne continuation !

    • bestmomo

      Salut,

      Pour le déploiement c’est une idée effectivement, je peux montrer ma précédure personnelle mais il en existe évidemment bien d’autres.

      Pour le RGPD je l’avais intégré à mon exemple de site e-commerce, on pourrait aussi l’ajouter au blog.

  • Michel

    Bonsoir,

    Quand je créé un article, la photo est obligatoire et lorsque je clique sur button -> image puis en haut a gauche pour accéder au dossier, que ce soit sur dossier ou sur share, je n’ai pas de redirection vers le dossier photo.
    Est ce normal ?
    Je n’arrive pas non plus à activer le module spreadsheet de ckeditor.

    Bonne soirée.

    • bestmomo

      Bonsoir,
      J’ai défini qu’on utilisait le dossier image (donc photos) dans le Javascript de la vue back.shared.editorScript à cette ligne :
      $('#lfm').filemanager('image');
      Pour Spreadsheets je ne sais pas, je ne l’ai jamais utilisé.

Laisser un commentaire